summaryrefslogtreecommitdiffstats
path: root/contrib/python/Pillow/py3
diff options
context:
space:
mode:
authorshumkovnd <[email protected]>2023-11-10 14:39:34 +0300
committershumkovnd <[email protected]>2023-11-10 16:42:24 +0300
commit77eb2d3fdcec5c978c64e025ced2764c57c00285 (patch)
treec51edb0748ca8d4a08d7c7323312c27ba1a8b79a /contrib/python/Pillow/py3
parentdd6d20cadb65582270ac23f4b3b14ae189704b9d (diff)
KIKIMR-19287: add task_stats_drawing script
Diffstat (limited to 'contrib/python/Pillow/py3')
-rw-r--r--contrib/python/Pillow/py3/.dist-info/METADATA176
-rw-r--r--contrib/python/Pillow/py3/.dist-info/top_level.txt1
-rw-r--r--contrib/python/Pillow/py3/CHANGES.rst7360
-rw-r--r--contrib/python/Pillow/py3/LICENSE30
-rw-r--r--contrib/python/Pillow/py3/PIL/BdfFontFile.py122
-rw-r--r--contrib/python/Pillow/py3/PIL/BlpImagePlugin.py474
-rw-r--r--contrib/python/Pillow/py3/PIL/BmpImagePlugin.py471
-rw-r--r--contrib/python/Pillow/py3/PIL/BufrStubImagePlugin.py73
-rw-r--r--contrib/python/Pillow/py3/PIL/ContainerIO.py120
-rw-r--r--contrib/python/Pillow/py3/PIL/CurImagePlugin.py75
-rw-r--r--contrib/python/Pillow/py3/PIL/DcxImagePlugin.py79
-rw-r--r--contrib/python/Pillow/py3/PIL/DdsImagePlugin.py295
-rw-r--r--contrib/python/Pillow/py3/PIL/EpsImagePlugin.py480
-rw-r--r--contrib/python/Pillow/py3/PIL/ExifTags.py380
-rw-r--r--contrib/python/Pillow/py3/PIL/FitsImagePlugin.py73
-rw-r--r--contrib/python/Pillow/py3/PIL/FliImagePlugin.py171
-rw-r--r--contrib/python/Pillow/py3/PIL/FontFile.py110
-rw-r--r--contrib/python/Pillow/py3/PIL/FpxImagePlugin.py253
-rw-r--r--contrib/python/Pillow/py3/PIL/FtexImagePlugin.py113
-rw-r--r--contrib/python/Pillow/py3/PIL/GbrImagePlugin.py102
-rw-r--r--contrib/python/Pillow/py3/PIL/GdImageFile.py97
-rw-r--r--contrib/python/Pillow/py3/PIL/GifImagePlugin.py1060
-rw-r--r--contrib/python/Pillow/py3/PIL/GimpGradientFile.py137
-rw-r--r--contrib/python/Pillow/py3/PIL/GimpPaletteFile.py56
-rw-r--r--contrib/python/Pillow/py3/PIL/GribStubImagePlugin.py73
-rw-r--r--contrib/python/Pillow/py3/PIL/Hdf5StubImagePlugin.py73
-rw-r--r--contrib/python/Pillow/py3/PIL/IcnsImagePlugin.py399
-rw-r--r--contrib/python/Pillow/py3/PIL/IcoImagePlugin.py358
-rw-r--r--contrib/python/Pillow/py3/PIL/ImImagePlugin.py371
-rw-r--r--contrib/python/Pillow/py3/PIL/Image.py3940
-rw-r--r--contrib/python/Pillow/py3/PIL/ImageChops.py303
-rw-r--r--contrib/python/Pillow/py3/PIL/ImageCms.py1009
-rw-r--r--contrib/python/Pillow/py3/PIL/ImageColor.py313
-rw-r--r--contrib/python/Pillow/py3/PIL/ImageDraw.py1062
-rw-r--r--contrib/python/Pillow/py3/PIL/ImageDraw2.py193
-rw-r--r--contrib/python/Pillow/py3/PIL/ImageEnhance.py103
-rw-r--r--contrib/python/Pillow/py3/PIL/ImageFile.py773
-rw-r--r--contrib/python/Pillow/py3/PIL/ImageFilter.py566
-rw-r--r--contrib/python/Pillow/py3/PIL/ImageFont.py1242
-rw-r--r--contrib/python/Pillow/py3/PIL/ImageGrab.py177
-rw-r--r--contrib/python/Pillow/py3/PIL/ImageMath.py263
-rw-r--r--contrib/python/Pillow/py3/PIL/ImageMode.py90
-rw-r--r--contrib/python/Pillow/py3/PIL/ImageMorph.py254
-rw-r--r--contrib/python/Pillow/py3/PIL/ImageOps.py658
-rw-r--r--contrib/python/Pillow/py3/PIL/ImagePalette.py266
-rw-r--r--contrib/python/Pillow/py3/PIL/ImagePath.py19
-rw-r--r--contrib/python/Pillow/py3/PIL/ImageSequence.py76
-rw-r--r--contrib/python/Pillow/py3/PIL/ImageShow.py323
-rw-r--r--contrib/python/Pillow/py3/PIL/ImageStat.py148
-rw-r--r--contrib/python/Pillow/py3/PIL/ImageTransform.py102
-rw-r--r--contrib/python/Pillow/py3/PIL/ImageWin.py230
-rw-r--r--contrib/python/Pillow/py3/PIL/ImtImagePlugin.py101
-rw-r--r--contrib/python/Pillow/py3/PIL/IptcImagePlugin.py230
-rw-r--r--contrib/python/Pillow/py3/PIL/Jpeg2KImagePlugin.py399
-rw-r--r--contrib/python/Pillow/py3/PIL/JpegImagePlugin.py861
-rw-r--r--contrib/python/Pillow/py3/PIL/JpegPresets.py240
-rw-r--r--contrib/python/Pillow/py3/PIL/McIdasImagePlugin.py75
-rw-r--r--contrib/python/Pillow/py3/PIL/MicImagePlugin.py103
-rw-r--r--contrib/python/Pillow/py3/PIL/MpegImagePlugin.py82
-rw-r--r--contrib/python/Pillow/py3/PIL/MpoImagePlugin.py197
-rw-r--r--contrib/python/Pillow/py3/PIL/MspImagePlugin.py194
-rw-r--r--contrib/python/Pillow/py3/PIL/PSDraw.py229
-rw-r--r--contrib/python/Pillow/py3/PIL/PaletteFile.py51
-rw-r--r--contrib/python/Pillow/py3/PIL/PalmImagePlugin.py225
-rw-r--r--contrib/python/Pillow/py3/PIL/PcdImagePlugin.py62
-rw-r--r--contrib/python/Pillow/py3/PIL/PcfFontFile.py256
-rw-r--r--contrib/python/Pillow/py3/PIL/PcxImagePlugin.py221
-rw-r--r--contrib/python/Pillow/py3/PIL/PdfImagePlugin.py302
-rw-r--r--contrib/python/Pillow/py3/PIL/PdfParser.py996
-rw-r--r--contrib/python/Pillow/py3/PIL/PixarImagePlugin.py69
-rw-r--r--contrib/python/Pillow/py3/PIL/PngImagePlugin.py1452
-rw-r--r--contrib/python/Pillow/py3/PIL/PpmImagePlugin.py347
-rw-r--r--contrib/python/Pillow/py3/PIL/PsdImagePlugin.py303
-rw-r--r--contrib/python/Pillow/py3/PIL/PyAccess.py363
-rw-r--r--contrib/python/Pillow/py3/PIL/QoiImagePlugin.py105
-rw-r--r--contrib/python/Pillow/py3/PIL/SgiImagePlugin.py231
-rw-r--r--contrib/python/Pillow/py3/PIL/SpiderImagePlugin.py318
-rw-r--r--contrib/python/Pillow/py3/PIL/SunImagePlugin.py139
-rw-r--r--contrib/python/Pillow/py3/PIL/TarIO.py66
-rw-r--r--contrib/python/Pillow/py3/PIL/TgaImagePlugin.py255
-rw-r--r--contrib/python/Pillow/py3/PIL/TiffImagePlugin.py2156
-rw-r--r--contrib/python/Pillow/py3/PIL/TiffTags.py560
-rw-r--r--contrib/python/Pillow/py3/PIL/WalImageFile.py123
-rw-r--r--contrib/python/Pillow/py3/PIL/WebPImagePlugin.py361
-rw-r--r--contrib/python/Pillow/py3/PIL/WmfImagePlugin.py178
-rw-r--r--contrib/python/Pillow/py3/PIL/XVThumbImagePlugin.py78
-rw-r--r--contrib/python/Pillow/py3/PIL/XbmImagePlugin.py94
-rw-r--r--contrib/python/Pillow/py3/PIL/XpmImagePlugin.py128
-rw-r--r--contrib/python/Pillow/py3/PIL/__init__.py84
-rw-r--r--contrib/python/Pillow/py3/PIL/__main__.py9
-rw-r--r--contrib/python/Pillow/py3/PIL/_binary.py102
-rw-r--r--contrib/python/Pillow/py3/PIL/_deprecate.py69
-rw-r--r--contrib/python/Pillow/py3/PIL/_util.py19
-rw-r--r--contrib/python/Pillow/py3/PIL/_version.py2
-rw-r--r--contrib/python/Pillow/py3/PIL/features.py329
-rw-r--r--contrib/python/Pillow/py3/README.md121
-rw-r--r--contrib/python/Pillow/py3/RELEASING.md134
-rw-r--r--contrib/python/Pillow/py3/_imaging.c4406
-rw-r--r--contrib/python/Pillow/py3/_imagingcms.c1563
-rw-r--r--contrib/python/Pillow/py3/_imagingft.c1522
-rw-r--r--contrib/python/Pillow/py3/_imagingmath.c294
-rw-r--r--contrib/python/Pillow/py3/_imagingmorph.c273
-rw-r--r--contrib/python/Pillow/py3/_webp.c988
-rw-r--r--contrib/python/Pillow/py3/decode.c925
-rw-r--r--contrib/python/Pillow/py3/display.c916
-rw-r--r--contrib/python/Pillow/py3/encode.c1373
-rw-r--r--contrib/python/Pillow/py3/libImaging/Access.c239
-rw-r--r--contrib/python/Pillow/py3/libImaging/AlphaComposite.c85
-rw-r--r--contrib/python/Pillow/py3/libImaging/Bands.c315
-rw-r--r--contrib/python/Pillow/py3/libImaging/Bcn.h3
-rw-r--r--contrib/python/Pillow/py3/libImaging/BcnDecode.c897
-rw-r--r--contrib/python/Pillow/py3/libImaging/Bit.h29
-rw-r--r--contrib/python/Pillow/py3/libImaging/BitDecode.c138
-rw-r--r--contrib/python/Pillow/py3/libImaging/Blend.c79
-rw-r--r--contrib/python/Pillow/py3/libImaging/BoxBlur.c324
-rw-r--r--contrib/python/Pillow/py3/libImaging/Chops.c162
-rw-r--r--contrib/python/Pillow/py3/libImaging/ColorLUT.c187
-rw-r--r--contrib/python/Pillow/py3/libImaging/Convert.c1742
-rw-r--r--contrib/python/Pillow/py3/libImaging/Convert.h2
-rw-r--r--contrib/python/Pillow/py3/libImaging/ConvertYCbCr.c363
-rw-r--r--contrib/python/Pillow/py3/libImaging/Copy.c57
-rw-r--r--contrib/python/Pillow/py3/libImaging/Crop.c63
-rw-r--r--contrib/python/Pillow/py3/libImaging/Dib.c313
-rw-r--r--contrib/python/Pillow/py3/libImaging/Draw.c1948
-rw-r--r--contrib/python/Pillow/py3/libImaging/Effects.c160
-rw-r--r--contrib/python/Pillow/py3/libImaging/EpsEncode.c77
-rw-r--r--contrib/python/Pillow/py3/libImaging/File.c78
-rw-r--r--contrib/python/Pillow/py3/libImaging/Fill.c139
-rw-r--r--contrib/python/Pillow/py3/libImaging/Filter.c412
-rw-r--r--contrib/python/Pillow/py3/libImaging/FliDecode.c268
-rw-r--r--contrib/python/Pillow/py3/libImaging/Geometry.c1157
-rw-r--r--contrib/python/Pillow/py3/libImaging/GetBBox.c356
-rw-r--r--contrib/python/Pillow/py3/libImaging/Gif.h100
-rw-r--r--contrib/python/Pillow/py3/libImaging/GifDecode.c285
-rw-r--r--contrib/python/Pillow/py3/libImaging/GifEncode.c361
-rw-r--r--contrib/python/Pillow/py3/libImaging/HexDecode.c63
-rw-r--r--contrib/python/Pillow/py3/libImaging/Histo.c201
-rw-r--r--contrib/python/Pillow/py3/libImaging/ImDib.h64
-rw-r--r--contrib/python/Pillow/py3/libImaging/ImPlatform.h98
-rw-r--r--contrib/python/Pillow/py3/libImaging/Imaging.h693
-rw-r--r--contrib/python/Pillow/py3/libImaging/ImagingUtils.h42
-rw-r--r--contrib/python/Pillow/py3/libImaging/Jpeg.h116
-rw-r--r--contrib/python/Pillow/py3/libImaging/Jpeg2K.h113
-rw-r--r--contrib/python/Pillow/py3/libImaging/Jpeg2KDecode.c999
-rw-r--r--contrib/python/Pillow/py3/libImaging/Jpeg2KEncode.c659
-rw-r--r--contrib/python/Pillow/py3/libImaging/JpegDecode.c304
-rw-r--r--contrib/python/Pillow/py3/libImaging/JpegEncode.c354
-rw-r--r--contrib/python/Pillow/py3/libImaging/Matrix.c78
-rw-r--r--contrib/python/Pillow/py3/libImaging/ModeFilter.c81
-rw-r--r--contrib/python/Pillow/py3/libImaging/Negative.c42
-rw-r--r--contrib/python/Pillow/py3/libImaging/Offset.c64
-rw-r--r--contrib/python/Pillow/py3/libImaging/Pack.c693
-rw-r--r--contrib/python/Pillow/py3/libImaging/PackDecode.c92
-rw-r--r--contrib/python/Pillow/py3/libImaging/Palette.c307
-rw-r--r--contrib/python/Pillow/py3/libImaging/Paste.c628
-rw-r--r--contrib/python/Pillow/py3/libImaging/PcdDecode.c74
-rw-r--r--contrib/python/Pillow/py3/libImaging/PcxDecode.c89
-rw-r--r--contrib/python/Pillow/py3/libImaging/PcxEncode.c187
-rw-r--r--contrib/python/Pillow/py3/libImaging/Point.c270
-rw-r--r--contrib/python/Pillow/py3/libImaging/Quant.c1859
-rw-r--r--contrib/python/Pillow/py3/libImaging/QuantHash.c336
-rw-r--r--contrib/python/Pillow/py3/libImaging/QuantHash.h55
-rw-r--r--contrib/python/Pillow/py3/libImaging/QuantHeap.c176
-rw-r--r--contrib/python/Pillow/py3/libImaging/QuantHeap.h31
-rw-r--r--contrib/python/Pillow/py3/libImaging/QuantOctree.c538
-rw-r--r--contrib/python/Pillow/py3/libImaging/QuantOctree.h9
-rw-r--r--contrib/python/Pillow/py3/libImaging/QuantPngQuant.c132
-rw-r--r--contrib/python/Pillow/py3/libImaging/QuantPngQuant.h17
-rw-r--r--contrib/python/Pillow/py3/libImaging/QuantTypes.h32
-rw-r--r--contrib/python/Pillow/py3/libImaging/RankFilter.c132
-rw-r--r--contrib/python/Pillow/py3/libImaging/Raw.h14
-rw-r--r--contrib/python/Pillow/py3/libImaging/RawDecode.c91
-rw-r--r--contrib/python/Pillow/py3/libImaging/RawEncode.c87
-rw-r--r--contrib/python/Pillow/py3/libImaging/Reduce.c1483
-rw-r--r--contrib/python/Pillow/py3/libImaging/Resample.c708
-rw-r--r--contrib/python/Pillow/py3/libImaging/Sgi.h39
-rw-r--r--contrib/python/Pillow/py3/libImaging/SgiRleDecode.c288
-rw-r--r--contrib/python/Pillow/py3/libImaging/Storage.c577
-rw-r--r--contrib/python/Pillow/py3/libImaging/SunRleDecode.c139
-rw-r--r--contrib/python/Pillow/py3/libImaging/TgaRleDecode.c129
-rw-r--r--contrib/python/Pillow/py3/libImaging/TgaRleEncode.c157
-rw-r--r--contrib/python/Pillow/py3/libImaging/TiffDecode.c997
-rw-r--r--contrib/python/Pillow/py3/libImaging/TiffDecode.h66
-rw-r--r--contrib/python/Pillow/py3/libImaging/Unpack.c1821
-rw-r--r--contrib/python/Pillow/py3/libImaging/UnpackYCC.c163
-rw-r--r--contrib/python/Pillow/py3/libImaging/UnsharpMask.c97
-rw-r--r--contrib/python/Pillow/py3/libImaging/XbmDecode.c78
-rw-r--r--contrib/python/Pillow/py3/libImaging/XbmEncode.c96
-rw-r--r--contrib/python/Pillow/py3/libImaging/ZipCodecs.h58
-rw-r--r--contrib/python/Pillow/py3/libImaging/ZipDecode.c299
-rw-r--r--contrib/python/Pillow/py3/libImaging/ZipEncode.c367
-rw-r--r--contrib/python/Pillow/py3/libImaging/codec_fd.c69
-rw-r--r--contrib/python/Pillow/py3/map.c146
-rw-r--r--contrib/python/Pillow/py3/outline.c187
-rw-r--r--contrib/python/Pillow/py3/path.c615
-rw-r--r--contrib/python/Pillow/py3/ya.make265
196 files changed, 79974 insertions, 0 deletions
diff --git a/contrib/python/Pillow/py3/.dist-info/METADATA b/contrib/python/Pillow/py3/.dist-info/METADATA
new file mode 100644
index 00000000000..bdad4e521be
--- /dev/null
+++ b/contrib/python/Pillow/py3/.dist-info/METADATA
@@ -0,0 +1,176 @@
+Metadata-Version: 2.1
+Name: Pillow
+Version: 10.1.0
+Summary: Python Imaging Library (Fork)
+Home-page: https://python-pillow.org
+Author: Jeffrey A. Clark (Alex)
+Author-email: [email protected]
+License: HPND
+Project-URL: Documentation, https://pillow.readthedocs.io
+Project-URL: Source, https://github.com/python-pillow/Pillow
+Project-URL: Funding, https://tidelift.com/subscription/pkg/pypi-pillow?utm_source=pypi-pillow&utm_medium=pypi
+Project-URL: Release notes, https://pillow.readthedocs.io/en/stable/releasenotes/index.html
+Project-URL: Changelog, https://github.com/python-pillow/Pillow/blob/main/CHANGES.rst
+Project-URL: Twitter, https://twitter.com/PythonPillow
+Project-URL: Mastodon, https://fosstodon.org/@pillow
+Keywords: Imaging
+Classifier: Development Status :: 6 - Mature
+Classifier: License :: OSI Approved :: Historical Permission Notice and Disclaimer (HPND)
+Classifier: Programming Language :: Python :: 3
+Classifier: Programming Language :: Python :: 3 :: Only
+Classifier: Programming Language :: Python :: 3.8
+Classifier: Programming Language :: Python :: 3.9
+Classifier: Programming Language :: Python :: 3.10
+Classifier: Programming Language :: Python :: 3.11
+Classifier: Programming Language :: Python :: 3.12
+Classifier: Programming Language :: Python :: Implementation :: CPython
+Classifier: Programming Language :: Python :: Implementation :: PyPy
+Classifier: Topic :: Multimedia :: Graphics
+Classifier: Topic :: Multimedia :: Graphics :: Capture :: Digital Camera
+Classifier: Topic :: Multimedia :: Graphics :: Capture :: Screen Capture
+Classifier: Topic :: Multimedia :: Graphics :: Graphics Conversion
+Classifier: Topic :: Multimedia :: Graphics :: Viewers
+Requires-Python: >=3.8
+Description-Content-Type: text/markdown
+License-File: LICENSE
+Provides-Extra: docs
+Requires-Dist: furo ; extra == 'docs'
+Requires-Dist: olefile ; extra == 'docs'
+Requires-Dist: sphinx (>=2.4) ; extra == 'docs'
+Requires-Dist: sphinx-copybutton ; extra == 'docs'
+Requires-Dist: sphinx-inline-tabs ; extra == 'docs'
+Requires-Dist: sphinx-removed-in ; extra == 'docs'
+Requires-Dist: sphinxext-opengraph ; extra == 'docs'
+Provides-Extra: tests
+Requires-Dist: check-manifest ; extra == 'tests'
+Requires-Dist: coverage ; extra == 'tests'
+Requires-Dist: defusedxml ; extra == 'tests'
+Requires-Dist: markdown2 ; extra == 'tests'
+Requires-Dist: olefile ; extra == 'tests'
+Requires-Dist: packaging ; extra == 'tests'
+Requires-Dist: pyroma ; extra == 'tests'
+Requires-Dist: pytest ; extra == 'tests'
+Requires-Dist: pytest-cov ; extra == 'tests'
+Requires-Dist: pytest-timeout ; extra == 'tests'
+
+<p align="center">
+ <img width="248" height="250" src="https://raw.githubusercontent.com/python-pillow/pillow-logo/main/pillow-logo-248x250.png" alt="Pillow logo">
+</p>
+
+# Pillow
+
+## Python Imaging Library (Fork)
+
+Pillow is the friendly PIL fork by [Jeffrey A. Clark (Alex) and
+contributors](https://github.com/python-pillow/Pillow/graphs/contributors).
+PIL is the Python Imaging Library by Fredrik Lundh and Contributors.
+As of 2019, Pillow development is
+[supported by Tidelift](https://tidelift.com/subscription/pkg/pypi-pillow?utm_source=pypi-pillow&utm_medium=readme&utm_campaign=enterprise).
+
+<table>
+ <tr>
+ <th>docs</th>
+ <td>
+ <a href="https://pillow.readthedocs.io/?badge=latest"><img
+ alt="Documentation Status"
+ src="https://readthedocs.org/projects/pillow/badge/?version=latest"></a>
+ </td>
+ </tr>
+ <tr>
+ <th>tests</th>
+ <td>
+ <a href="https://github.com/python-pillow/Pillow/actions/workflows/lint.yml"><img
+ alt="GitHub Actions build status (Lint)"
+ src="https://github.com/python-pillow/Pillow/workflows/Lint/badge.svg"></a>
+ <a href="https://github.com/python-pillow/Pillow/actions/workflows/test.yml"><img
+ alt="GitHub Actions build status (Test Linux and macOS)"
+ src="https://github.com/python-pillow/Pillow/workflows/Test/badge.svg"></a>
+ <a href="https://github.com/python-pillow/Pillow/actions/workflows/test-windows.yml"><img
+ alt="GitHub Actions build status (Test Windows)"
+ src="https://github.com/python-pillow/Pillow/workflows/Test%20Windows/badge.svg"></a>
+ <a href="https://github.com/python-pillow/Pillow/actions/workflows/test-mingw.yml"><img
+ alt="GitHub Actions build status (Test MinGW)"
+ src="https://github.com/python-pillow/Pillow/workflows/Test%20MinGW/badge.svg"></a>
+ <a href="https://github.com/python-pillow/Pillow/actions/workflows/test-cygwin.yml"><img
+ alt="GitHub Actions build status (Test Cygwin)"
+ src="https://github.com/python-pillow/Pillow/workflows/Test%20Cygwin/badge.svg"></a>
+ <a href="https://github.com/python-pillow/Pillow/actions/workflows/test-docker.yml"><img
+ alt="GitHub Actions build status (Test Docker)"
+ src="https://github.com/python-pillow/Pillow/workflows/Test%20Docker/badge.svg"></a>
+ <a href="https://ci.appveyor.com/project/python-pillow/Pillow"><img
+ alt="AppVeyor CI build status (Windows)"
+ src="https://img.shields.io/appveyor/build/python-pillow/Pillow/main.svg?label=Windows%20build"></a>
+ <a href="https://github.com/python-pillow/Pillow/actions/workflows/wheels.yml"><img
+ alt="GitHub Actions build status (Wheels)"
+ src="https://github.com/python-pillow/Pillow/workflows/Wheels/badge.svg"></a>
+ <a href="https://app.travis-ci.com/github/python-pillow/Pillow"><img
+ alt="Travis CI wheels build status (aarch64)"
+ src="https://img.shields.io/travis/com/python-pillow/Pillow/main.svg?label=aarch64%20wheels"></a>
+ <a href="https://app.codecov.io/gh/python-pillow/Pillow"><img
+ alt="Code coverage"
+ src="https://codecov.io/gh/python-pillow/Pillow/branch/main/graph/badge.svg"></a>
+ <a href="https://bugs.chromium.org/p/oss-fuzz/issues/list?sort=-opened&can=1&q=proj:pillow"><img
+ alt="Fuzzing Status"
+ src="https://oss-fuzz-build-logs.storage.googleapis.com/badges/pillow.svg"></a>
+ </td>
+ </tr>
+ <tr>
+ <th>package</th>
+ <td>
+ <a href="https://zenodo.org/badge/latestdoi/17549/python-pillow/Pillow"><img
+ alt="Zenodo"
+ src="https://zenodo.org/badge/17549/python-pillow/Pillow.svg"></a>
+ <a href="https://tidelift.com/subscription/pkg/pypi-pillow?utm_source=pypi-pillow&utm_medium=badge"><img
+ alt="Tidelift"
+ src="https://tidelift.com/badges/package/pypi/Pillow?style=flat"></a>
+ <a href="https://pypi.org/project/Pillow/"><img
+ alt="Newest PyPI version"
+ src="https://img.shields.io/pypi/v/pillow.svg"></a>
+ <a href="https://pypi.org/project/Pillow/"><img
+ alt="Number of PyPI downloads"
+ src="https://img.shields.io/pypi/dm/pillow.svg"></a>
+ <a href="https://www.bestpractices.dev/projects/6331"><img
+ alt="OpenSSF Best Practices"
+ src="https://www.bestpractices.dev/projects/6331/badge"></a>
+ </td>
+ </tr>
+ <tr>
+ <th>social</th>
+ <td>
+ <a href="https://gitter.im/python-pillow/Pillow?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge"><img
+ alt="Join the chat at https://gitter.im/python-pillow/Pillow"
+ src="https://badges.gitter.im/python-pillow/Pillow.svg"></a>
+ <a href="https://twitter.com/PythonPillow"><img
+ alt="Follow on https://twitter.com/PythonPillow"
+ src="https://img.shields.io/badge/tweet-on%20Twitter-00aced.svg"></a>
+ <a href="https://fosstodon.org/@pillow"><img
+ alt="Follow on https://fosstodon.org/@pillow"
+ src="https://img.shields.io/badge/publish-on%20Mastodon-595aff.svg"
+ rel="me"></a>
+ </td>
+ </tr>
+</table>
+
+## Overview
+
+The Python Imaging Library adds image processing capabilities to your Python interpreter.
+
+This library provides extensive file format support, an efficient internal representation, and fairly powerful image processing capabilities.
+
+The core image library is designed for fast access to data stored in a few basic pixel formats. It should provide a solid foundation for a general image processing tool.
+
+## More Information
+
+- [Documentation](https://pillow.readthedocs.io/)
+ - [Installation](https://pillow.readthedocs.io/en/latest/installation.html)
+ - [Handbook](https://pillow.readthedocs.io/en/latest/handbook/index.html)
+- [Contribute](https://github.com/python-pillow/Pillow/blob/main/.github/CONTRIBUTING.md)
+ - [Issues](https://github.com/python-pillow/Pillow/issues)
+ - [Pull requests](https://github.com/python-pillow/Pillow/pulls)
+- [Release notes](https://pillow.readthedocs.io/en/stable/releasenotes/index.html)
+- [Changelog](https://github.com/python-pillow/Pillow/blob/main/CHANGES.rst)
+ - [Pre-fork](https://github.com/python-pillow/Pillow/blob/main/CHANGES.rst#pre-fork)
+
+## Report a Vulnerability
+
+To report a security vulnerability, please follow the procedure described in the [Tidelift security policy](https://tidelift.com/docs/security).
diff --git a/contrib/python/Pillow/py3/.dist-info/top_level.txt b/contrib/python/Pillow/py3/.dist-info/top_level.txt
new file mode 100644
index 00000000000..b338169ce0c
--- /dev/null
+++ b/contrib/python/Pillow/py3/.dist-info/top_level.txt
@@ -0,0 +1 @@
+PIL
diff --git a/contrib/python/Pillow/py3/CHANGES.rst b/contrib/python/Pillow/py3/CHANGES.rst
new file mode 100644
index 00000000000..d2f2bb46231
--- /dev/null
+++ b/contrib/python/Pillow/py3/CHANGES.rst
@@ -0,0 +1,7360 @@
+
+Changelog (Pillow)
+==================
+
+10.1.0 (2023-10-15)
+-------------------
+
+- Added TrueType default font to allow for different sizes #7354
+ [radarhere]
+
+- Fixed invalid argument warning #7442
+ [radarhere]
+
+- Added ImageOps cover method #7412
+ [radarhere, hugovk]
+
+- Catch struct.error from truncated EXIF when reading JPEG DPI #7458
+ [radarhere]
+
+- Consider default image when selecting mode for PNG save_all #7437
+ [radarhere]
+
+- Support BGR;15, BGR;16 and BGR;24 access, unpacking and putdata #7303
+ [radarhere]
+
+- Added CMYK to RGB unpacker #7310
+ [radarhere]
+
+- Improved flexibility of XMP parsing #7274
+ [radarhere]
+
+- Support reading 8-bit YCbCr TIFF images #7415
+ [radarhere]
+
+- Allow saving I;16B images as PNG #7302
+ [radarhere]
+
+- Corrected drawing I;16 points and writing I;16 text #7257
+ [radarhere]
+
+- Set blue channel to 128 for BC5S #7413
+ [radarhere]
+
+- Increase flexibility when reading IPTC fields #7319
+ [radarhere]
+
+- Set C palette to be empty by default #7289
+ [radarhere]
+
+- Added gs_binary to control Ghostscript use on all platforms #7392
+ [radarhere]
+
+- Read bounding box information from the trailer of EPS files if specified #7382
+ [nopperl, radarhere]
+
+- Added reading 8-bit color DDS images #7426
+ [radarhere]
+
+- Added has_transparency_data #7420
+ [radarhere, hugovk]
+
+- Fixed bug when reading BC5S DDS images #7401
+ [radarhere]
+
+- Prevent TIFF orientation from being applied more than once #7383
+ [radarhere]
+
+- Use previous pixel alpha for QOI_OP_RGB #7357
+ [radarhere]
+
+- Added BC5U reading #7358
+ [radarhere]
+
+- Allow getpixel() to accept a list #7355
+ [radarhere, homm]
+
+- Allow GaussianBlur and BoxBlur to accept a sequence of x and y radii #7336
+ [radarhere]
+
+- Expand JPEG buffer size when saving optimized or progressive #7345
+ [radarhere]
+
+- Added session type check for Linux in ImageGrab.grabclipboard() #7332
+ [TheNooB2706, radarhere, hugovk]
+
+- Allow "loop=None" when saving GIF images #7329
+ [radarhere]
+
+- Fixed transparency when saving P mode images to PDF #7323
+ [radarhere]
+
+- Added saving LA images as PDFs #7299
+ [radarhere]
+
+- Set SMaskInData to 1 for PDFs with alpha #7316, #7317
+ [radarhere]
+
+- Changed Image mode property to be read-only by default #7307
+ [radarhere]
+
+- Silence exceptions in _repr_jpeg_ and _repr_png_ #7266
+ [mtreinish, radarhere]
+
+- Do not use transparency when saving GIF if it has been removed when normalizing mode #7284
+ [radarhere]
+
+- Fix missing symbols when libtiff depends on libjpeg #7270
+ [heitbaum]
+
+10.0.1 (2023-09-15)
+-------------------
+
+- Updated libwebp to 1.3.2 #7395
+ [radarhere]
+
+- Updated zlib to 1.3 #7344
+ [radarhere]
+
+10.0.0 (2023-07-01)
+-------------------
+
+- Fixed deallocating mask images #7246
+ [radarhere]
+
+- Added ImageFont.MAX_STRING_LENGTH #7244
+ [radarhere, hugovk]
+
+- Fix Windows build with pyproject.toml #7230
+ [hugovk, nulano, radarhere]
+
+- Do not close provided file handles with libtiff #7199
+ [radarhere]
+
+- Convert to HSV if mode is HSV in getcolor() #7226
+ [radarhere]
+
+- Added alpha_only argument to getbbox() #7123
+ [radarhere. hugovk]
+
+- Prioritise speed in _repr_png_ #7242
+ [radarhere]
+
+- Do not use CFFI access by default on PyPy #7236
+ [radarhere]
+
+- Limit size even if one dimension is zero in decompression bomb check #7235
+ [radarhere]
+
+- Use --config-settings instead of deprecated --global-option #7171
+ [radarhere]
+
+- Better C integer definitions #6645
+ [Yay295, hugovk]
+
+- Fixed finding dependencies on Cygwin #7175
+ [radarhere]
+
+- Changed grabclipboard() to use PNG instead of JPG compression on macOS #7219
+ [abey79, radarhere]
+
+- Added in_place argument to ImageOps.exif_transpose() #7092
+ [radarhere]
+
+- Fixed calling putpalette() on L and LA images before load() #7187
+ [radarhere]
+
+- Fixed saving TIFF multiframe images with LONG8 tag types #7078
+ [radarhere]
+
+- Fixed combining single duration across duplicate APNG frames #7146
+ [radarhere]
+
+- Remove temporary file when error is raised #7148
+ [radarhere]
+
+- Do not use temporary file when grabbing clipboard on Linux #7200
+ [radarhere]
+
+- If the clipboard fails to open on Windows, wait and try again #7141
+ [radarhere]
+
+- Fixed saving multiple 1 mode frames to GIF #7181
+ [radarhere]
+
+- Replaced absolute PIL import with relative import #7173
+ [radarhere]
+
+- Replaced deprecated Py_FileSystemDefaultEncoding for Python >= 3.12 #7192
+ [radarhere]
+
+- Improved wl-paste mimetype handling in ImageGrab #7094
+ [rrcgat, radarhere]
+
+- Added _repr_jpeg_() for IPython display_jpeg #7135
+ [n3011, radarhere, nulano]
+
+- Use "/sbin/ldconfig" if ldconfig is not found #7068
+ [radarhere]
+
+- Prefer screenshots using XCB over gnome-screenshot #7143
+ [nulano, radarhere]
+
+- Fixed joined corners for ImageDraw rounded_rectangle() odd dimensions #7151
+ [radarhere]
+
+- Support reading signed 8-bit TIFF images #7111
+ [radarhere]
+
+- Added width argument to ImageDraw regular_polygon #7132
+ [radarhere]
+
+- Support I mode for ImageFilter.BuiltinFilter #7108
+ [radarhere]
+
+- Raise error from stderr of Linux ImageGrab.grabclipboard() command #7112
+ [radarhere]
+
+- Added unpacker from I;16B to I;16 #7125
+ [radarhere]
+
+- Support float font sizes #7107
+ [radarhere]
+
+- Use later value for duplicate xref entries in PdfParser #7102
+ [radarhere]
+
+- Load before getting size in __getstate__ #7105
+ [bigcat88, radarhere]
+
+- Fixed type handling for include and lib directories #7069
+ [adisbladis, radarhere]
+
+- Remove deprecations for Pillow 10.0.0 #7059, #7080
+ [hugovk, radarhere]
+
+- Drop support for soon-EOL Python 3.7 #7058
+ [hugovk, radarhere]
+
+9.5.0 (2023-04-01)
+------------------
+
+- Added ImageSourceData to TAGS_V2 #7053
+ [radarhere]
+
+- Clear PPM half token after use #7052
+ [radarhere]
+
+- Removed absolute path to ldconfig #7044
+ [radarhere]
+
+- Support custom comments and PLT markers when saving JPEG2000 images #6903
+ [joshware, radarhere, hugovk]
+
+- Load before getting size in __array_interface__ #7034
+ [radarhere]
+
+- Support creating BGR;15, BGR;16 and BGR;24 images, but drop support for BGR;32 #7010
+ [radarhere]
+
+- Consider transparency when applying APNG blend mask #7018
+ [radarhere]
+
+- Round duration when saving animated WebP images #6996
+ [radarhere]
+
+- Added reading of JPEG2000 comments #6909
+ [radarhere]
+
+- Decrement reference count #7003
+ [radarhere, nulano]
+
+- Allow libtiff_support_custom_tags to be missing #7020
+ [radarhere]
+
+- Improved I;16N support #6834
+ [radarhere]
+
+- Added QOI reading #6852
+ [radarhere, hugovk]
+
+- Added saving RGBA images as PDFs #6925
+ [radarhere]
+
+- Do not raise an error if os.environ does not contain PATH #6935
+ [radarhere, hugovk]
+
+- Close OleFileIO instance when closing or exiting FPX or MIC #7005
+ [radarhere]
+
+- Added __int__ to IFDRational for Python >= 3.11 #6998
+ [radarhere]
+
+- Added memoryview support to Dib.frombytes() #6988
+ [radarhere, nulano]
+
+- Close file pointer copy in the libtiff encoder if still open #6986
+ [fcarron, radarhere]
+
+- Raise an error if ImageDraw co-ordinates are incorrectly ordered #6978
+ [radarhere]
+
+- Added "corners" argument to ImageDraw rounded_rectangle() #6954
+ [radarhere]
+
+- Added memoryview support to frombytes() #6974
+ [radarhere]
+
+- Allow comments in FITS images #6973
+ [radarhere]
+
+- Support saving PDF with different X and Y resolutions #6961
+ [jvanderneutstulen, radarhere, hugovk]
+
+- Fixed writing int as UNDEFINED tag #6950
+ [radarhere]
+
+- Raise an error if EXIF data is too long when saving JPEG #6939
+ [radarhere]
+
+- Handle more than one directory returned by pkg-config #6896
+ [sebastic, radarhere]
+
+- Do not retry past formats when loading all formats for the first time #6902
+ [radarhere]
+
+- Do not retry specified formats if they failed when opening #6893
+ [radarhere]
+
+- Do not unintentionally load TIFF format at first #6892
+ [radarhere]
+
+- Stop reading when EPS line becomes too long #6897
+ [radarhere]
+
+- Allow writing IFDRational to BYTE tag #6890
+ [radarhere]
+
+- Raise ValueError for BoxBlur filter with negative radius #6874
+ [hugovk, radarhere]
+
+- Support arbitrary number of loaded modules on Windows #6761
+ [javidcf, radarhere, nulano]
+
+9.4.0 (2023-01-02)
+------------------
+
+- Fixed null pointer dereference crash with malformed font #6846
+ [wiredfool, radarhere]
+
+- Return from ImagingFill early if image has a zero dimension #6842
+ [radarhere]
+
+- Reversed deprecations for Image constants, except for duplicate Resampling attributes #6830
+ [radarhere]
+
+- Improve exception traceback readability #6836
+ [hugovk, radarhere]
+
+- Do not attempt to read IFD1 if absent #6840
+ [radarhere]
+
+- Fixed writing int as ASCII tag #6800
+ [radarhere]
+
+- If available, use wl-paste or xclip for grabclipboard() on Linux #6783
+ [radarhere]
+
+- Added signed option when saving JPEG2000 images #6709
+ [radarhere]
+
+- Patch OpenJPEG to include ARM64 fix #6718
+ [radarhere]
+
+- Added support for I;16 modes in putdata() #6825
+ [radarhere]
+
+- Added conversion from RGBa to RGB #6708
+ [radarhere]
+
+- Added DDS support for uncompressed L and LA images #6820
+ [radarhere, REDxEYE]
+
+- Added LightSource tag values to ExifTags #6749
+ [radarhere]
+
+- Fixed PyAccess after changing ICO size #6821
+ [radarhere]
+
+- Do not use EXIF from info when saving PNG images #6819
+ [radarhere]
+
+- Fixed saving EXIF data to MPO #6817
+ [radarhere]
+
+- Added Exif hide_offsets() #6762
+ [radarhere]
+
+- Only compare to previous frame when checking for duplicate GIF frames while saving #6787
+ [radarhere]
+
+- Always initialize all plugins in registered_extensions() #6811
+ [radarhere]
+
+- Ignore non-opaque WebP background when saving as GIF #6792
+ [radarhere]
+
+- Only set tile in ImageFile __setstate__ #6793
+ [radarhere]
+
+- When reading BLP, do not trust JPEG decoder to determine image is CMYK #6767
+ [radarhere]
+
+- Added IFD enum to ExifTags #6748
+ [radarhere]
+
+- Fixed bug combining GIF frame durations #6779
+ [radarhere]
+
+- Support saving JPEG comments #6774
+ [smason, radarhere]
+
+- Added getxmp() to WebPImagePlugin #6758
+ [radarhere]
+
+- Added "exact" option when saving WebP #6747
+ [ashafaei, radarhere]
+
+- Use fractional coordinates when drawing text #6722
+ [radarhere]
+
+- Fixed writing int as BYTE tag #6740
+ [radarhere]
+
+- Added MP Format Version when saving MPO #6735
+ [radarhere]
+
+- Added Interop to ExifTags #6724
+ [radarhere]
+
+- CVE-2007-4559 patch when building on Windows #6704
+ [TrellixVulnTeam, nulano, radarhere]
+
+- Fix compiler warning: accessing 64 bytes in a region of size 48 #6714
+ [wiredfool]
+
+- Use verbose flag for pip install #6713
+ [wiredfool, radarhere]
+
+9.3.0 (2022-10-29)
+------------------
+
+- Limit SAMPLESPERPIXEL to avoid runtime DOS #6700
+ [wiredfool]
+
+- Initialize libtiff buffer when saving #6699
+ [radarhere]
+
+- Inline fname2char to fix memory leak #6329
+ [nulano]
+
+- Fix memory leaks related to text features #6330
+ [nulano]
+
+- Use double quotes for version check on old CPython on Windows #6695
+ [hugovk]
+
+- Remove backup implementation of Round for Windows platforms #6693
+ [cgohlke]
+
+- Fixed set_variation_by_name offset #6445
+ [radarhere]
+
+- Fix malloc in _imagingft.c:font_setvaraxes #6690
+ [cgohlke]
+
+- Release Python GIL when converting images using matrix operations #6418
+ [hmaarrfk]
+
+- Added ExifTags enums #6630
+ [radarhere]
+
+- Do not modify previous frame when calculating delta in PNG #6683
+ [radarhere]
+
+- Added support for reading BMP images with RLE4 compression #6674
+ [npjg, radarhere]
+
+- Decode JPEG compressed BLP1 data in original mode #6678
+ [radarhere]
+
+- Added GPS TIFF tag info #6661
+ [radarhere]
+
+- Added conversion between RGB/RGBA/RGBX and LAB #6647
+ [radarhere]
+
+- Do not attempt normalization if mode is already normal #6644
+ [radarhere]
+
+- Fixed seeking to an L frame in a GIF #6576
+ [radarhere]
+
+- Consider all frames when selecting mode for PNG save_all #6610
+ [radarhere]
+
+- Don't reassign crc on ChunkStream close #6627
+ [wiredfool, radarhere]
+
+- Raise a warning if NumPy failed to raise an error during conversion #6594
+ [radarhere]
+
+- Show all frames in ImageShow #6611
+ [radarhere]
+
+- Allow FLI palette chunk to not be first #6626
+ [radarhere]
+
+- If first GIF frame has transparency for RGB_ALWAYS loading strategy, use RGBA mode #6592
+ [radarhere]
+
+- Round box position to integer when pasting embedded color #6517
+ [radarhere, nulano]
+
+- Removed EXIF prefix when saving WebP #6582
+ [radarhere]
+
+- Pad IM palette to 768 bytes when saving #6579
+ [radarhere]
+
+- Added DDS BC6H reading #6449
+ [ShadelessFox, REDxEYE, radarhere]
+
+- Added support for opening WhiteIsZero 16-bit integer TIFF images #6642
+ [JayWiz, radarhere]
+
+- Raise an error when allocating translucent color to RGB palette #6654
+ [jsbueno, radarhere]
+
+- Added reading of TIFF child images #6569
+ [radarhere]
+
+- Improved ImageOps palette handling #6596
+ [PososikTeam, radarhere]
+
+- Defer parsing of palette into colors #6567
+ [radarhere]
+
+- Apply transparency to P images in ImageTk.PhotoImage #6559
+ [radarhere]
+
+- Use rounding in ImageOps contain() and pad() #6522
+ [bibinhashley, radarhere]
+
+- Fixed GIF remapping to palette with duplicate entries #6548
+ [radarhere]
+
+- Allow remap_palette() to return an image with less than 256 palette entries #6543
+ [radarhere]
+
+- Corrected BMP and TGA palette size when saving #6500
+ [radarhere]
+
+- Do not call load() before draft() in Image.thumbnail #6539
+ [radarhere]
+
+- Copy palette when converting from P to PA #6497
+ [radarhere]
+
+- Allow RGB and RGBA values for PA image putpixel #6504
+ [radarhere]
+
+- Removed support for tkinter in PyPy before Python 3.6 #6551
+ [nulano]
+
+- Do not use CCITTFaxDecode filter if libtiff is not available #6518
+ [radarhere]
+
+- Fallback to not using mmap if buffer is not large enough #6510
+ [radarhere]
+
+- Fixed writing bytes as ASCII tag #6493
+ [radarhere]
+
+- Open 1 bit EPS in mode 1 #6499
+ [radarhere]
+
+- Removed support for tkinter before Python 1.5.2 #6549
+ [radarhere]
+
+- Allow default ImageDraw font to be set #6484
+ [radarhere, hugovk]
+
+- Save 1 mode PDF using CCITTFaxDecode filter #6470
+ [radarhere]
+
+- Added support for RGBA PSD images #6481
+ [radarhere]
+
+- Parse orientation from XMP tag contents #6463
+ [bigcat88, radarhere]
+
+- Added support for reading ATI1/ATI2 (BC4/BC5) DDS images #6457
+ [REDxEYE, radarhere]
+
+- Do not clear GIF tile when checking number of frames #6455
+ [radarhere]
+
+- Support saving multiple MPO frames #6444
+ [radarhere]
+
+- Do not double quote Pillow version for setuptools >= 60 #6450
+ [radarhere]
+
+- Added ABGR BMP mask mode #6436
+ [radarhere]
+
+- Fixed PSDraw rectangle #6429
+ [radarhere]
+
+- Raise ValueError if PNG sRGB chunk is truncated #6431
+ [radarhere]
+
+- Handle missing Python executable in ImageShow on macOS #6416
+ [bryant1410, radarhere]
+
+9.2.0 (2022-07-01)
+------------------
+
+- Deprecate ImageFont.getsize and related functions #6381
+ [nulano, radarhere]
+
+- Fixed null check for fribidi_version_info in FriBiDi shim #6376
+ [nulano]
+
+- Added GIF decompression bomb check #6402
+ [radarhere]
+
+- Handle PCF fonts files with less than 256 characters #6386
+ [dawidcrivelli, radarhere]
+
+- Improved GIF optimize condition #6378
+ [raygard, radarhere]
+
+- Reverted to __array_interface__ with the release of NumPy 1.23 #6394
+ [radarhere]
+
+- Pad PCX palette to 768 bytes when saving #6391
+ [radarhere]
+
+- Fixed bug with rounding pixels to palette colors #6377
+ [btrekkie, radarhere]
+
+- Use gnome-screenshot on Linux if available #6361
+ [radarhere, nulano]
+
+- Fixed loading L mode BMP RLE8 images #6384
+ [radarhere]
+
+- Fixed incorrect operator in ImageCms error #6370
+ [LostBenjamin, hugovk, radarhere]
+
+- Limit FPX tile size to avoid extending outside image #6368
+ [radarhere]
+
+- Added support for decoding plain PPM formats #5242
+ [Piolie, radarhere]
+
+- Added apply_transparency() #6352
+ [radarhere]
+
+- Fixed behaviour change from endian fix #6197
+ [radarhere]
+
+- Allow remapping P images with RGBA palettes #6350
+ [radarhere]
+
+- Fixed drawing translucent 1px high polygons #6278
+ [radarhere]
+
+- Pad COLORMAP to 768 items when saving TIFF #6232
+ [radarhere]
+
+- Fix P -> PA conversion #6337
+ [RedShy, radarhere]
+
+- Once exif data is parsed, do not reload unless it changes #6335
+ [radarhere]
+
+- Only try to connect discontiguous corners at the end of edges #6303
+ [radarhere]
+
+- Improve transparency handling when saving GIF images #6176
+ [radarhere]
+
+- Do not update GIF frame position until local image is found #6219
+ [radarhere]
+
+- Netscape GIF extension belongs after the global color table #6211
+ [radarhere]
+
+- Only write GIF comments at the beginning of the file #6300
+ [raygard, radarhere]
+
+- Separate multiple GIF comment blocks with newlines #6294
+ [raygard, radarhere]
+
+- Always use GIF89a for comments #6292
+ [raygard, radarhere]
+
+- Ignore compression value from BMP info dictionary when saving as TIFF #6231
+ [radarhere]
+
+- If font is file-like object, do not re-read from object to get variant #6234
+ [radarhere]
+
+- Raise ValueError when trying to access internal fp after close #6213
+ [radarhere]
+
+- Support more affine expression forms in im.point() #6254
+ [benrg, radarhere]
+
+- Populate Python palette in fromarray() #6283
+ [radarhere]
+
+- Raise ValueError if PNG chunks are truncated #6253
+ [radarhere]
+
+- Use durations from each frame by default when saving GIFs #6265
+ [radarhere]
+
+- Adjust BITSPERSAMPLE to match SAMPLESPERPIXEL when opening TIFFs #6270
+ [radarhere]
+
+- Search pkgconf system libs/cflags #6138
+ [jameshilliard, radarhere]
+
+- Raise ValueError for invalid PPM maxval #6242
+ [radarhere]
+
+- Corrected screencapture argument in ImageGrab.grab() #6244
+ [axt-one]
+
+- Deprecate support for Qt 5 (PyQt5 and PySide2) #6237
+ [hugovk, radarhere]
+
+- Increase wait time of temporary file deletion on Windows #6224
+ [AlexTedeschi]
+
+- Deprecate FreeTypeFont.getmask2 fill parameter #6220
+ [nulano, radarhere, hugovk]
+
+- Round lut values where necessary #6188
+ [radarhere]
+
+- Load before getting size in resize() #6190
+ [radarhere]
+
+- Load image before performing size calculations in thumbnail() #6186
+ [radarhere]
+
+- Deprecated PhotoImage.paste() box parameter #6178
+ [radarhere]
+
+9.1.1 (2022-05-17)
+------------------
+
+- When reading past the end of a TGA scan line, reduce bytes left. CVE-2022-30595
+ [radarhere]
+
+- Do not open images with zero or negative height #6269
+ [radarhere]
+
+9.1.0 (2022-04-01)
+------------------
+
+- Add support for multiple component transformation to JPEG2000 #5500
+ [scaramallion, radarhere, hugovk]
+
+- Fix loading FriBiDi on Alpine #6165
+ [nulano]
+
+- Added setting for converting GIF P frames to RGB #6150
+ [radarhere]
+
+- Allow 1 mode images to be inverted #6034
+ [radarhere]
+
+- Raise ValueError when trying to save empty JPEG #6159
+ [radarhere]
+
+- Always save TIFF with contiguous planar configuration #5973
+ [radarhere]
+
+- Connected discontiguous polygon corners #5980
+ [radarhere]
+
+- Ensure Tkinter hook is activated for getimage() #6032
+ [radarhere]
+
+- Use screencapture arguments to crop on macOS #6152
+ [radarhere]
+
+- Do not mark L mode JPEG as 1 bit in PDF #6151
+ [radarhere]
+
+- Added support for reading I;16R TIFF images #6132
+ [radarhere]
+
+- If an error occurs after creating a file, remove the file #6134
+ [radarhere]
+
+- Fixed calling DisplayViewer or XVViewer without a title #6136
+ [radarhere]
+
+- Retain RGBA transparency when saving multiple GIF frames #6128
+ [radarhere]
+
+- Save additional ICO frames with other bit depths if supplied #6122
+ [radarhere]
+
+- Handle EXIF data truncated to just the header #6124
+ [radarhere]
+
+- Added support for reading BMP images with RLE8 compression #6102
+ [radarhere]
+
+- Support Python distributions where _tkinter is compiled in #6006
+ [lukegb]
+
+- Added support for PPM arbitrary maxval #6119
+ [radarhere]
+
+- Added BigTIFF reading #6097
+ [radarhere]
+
+- When converting, clip I;16 to be unsigned, not signed #6112
+ [radarhere]
+
+- Fixed loading L mode GIF with transparency #6086
+ [radarhere]
+
+- Improved handling of PPM header #5121
+ [Piolie, radarhere]
+
+- Reset size when seeking away from "Large Thumbnail" MPO frame #6101
+ [radarhere]
+
+- Replace requirements.txt with extras #6072
+ [hugovk, radarhere]
+
+- Added PyEncoder and support BLP saving #6069
+ [radarhere]
+
+- Handle TGA images with packets that cross scan lines #6087
+ [radarhere]
+
+- Added FITS reading #6056
+ [radarhere, hugovk]
+
+- Added rawmode argument to Image.getpalette() #6061
+ [radarhere]
+
+- Fixed BUFR, GRIB and HDF5 stub saving #6071
+ [radarhere]
+
+- Do not automatically remove temporary ImageShow files on Unix #6045
+ [radarhere]
+
+- Correctly read JPEG compressed BLP images #4685
+ [Meithal, radarhere]
+
+- Merged _MODE_CONV typ into ImageMode as typestr #6057
+ [radarhere]
+
+- Consider palette size when converting and in getpalette() #6060
+ [radarhere]
+
+- Added enums #5954
+ [radarhere]
+
+- Ensure image is opaque after converting P to PA with RGB palette #6052
+ [radarhere]
+
+- Attach RGBA palettes from putpalette() when suitable #6054
+ [radarhere]
+
+- Added get_photoshop_blocks() to parse Photoshop TIFF tag #6030
+ [radarhere]
+
+- Drop excess values in BITSPERSAMPLE #6041
+ [mikhail-iurkov]
+
+- Added unpacker from RGBA;15 to RGB #6031
+ [radarhere]
+
+- Enable arm64 for MSVC on Windows #5811
+ [gaborkertesz-linaro, gaborkertesz]
+
+- Keep IPython/Jupyter text/plain output stable #5891
+ [shamrin, radarhere]
+
+- Raise an error when performing a negative crop #5972
+ [radarhere, hugovk]
+
+- Deprecated show_file "file" argument in favour of "path" #5959
+ [radarhere]
+
+- Fixed SPIDER images for use with Bio-formats library #5956
+ [radarhere]
+
+- Ensure duplicated file pointer is closed #5946
+ [radarhere]
+
+- Added specific error if path coordinate type is incorrect #5942
+ [radarhere]
+
+- Return an empty bytestring from tobytes() for an empty image #5938
+ [radarhere]
+
+- Remove readonly from Image.__eq__ #5930
+ [hugovk]
+
+9.0.1 (2022-02-03)
+------------------
+
+- In show_file, use os.remove to remove temporary images. CVE-2022-24303 #6010
+ [radarhere, hugovk]
+
+- Restrict builtins within lambdas for ImageMath.eval. CVE-2022-22817 #6009
+ [radarhere]
+
+9.0.0 (2022-01-02)
+------------------
+
+- Restrict builtins for ImageMath.eval(). CVE-2022-22817 #5923
+ [radarhere]
+
+- Ensure JpegImagePlugin stops at the end of a truncated file #5921
+ [radarhere]
+
+- Fixed ImagePath.Path array handling. CVE-2022-22815, CVE-2022-22816 #5920
+ [radarhere]
+
+- Remove consecutive duplicate tiles that only differ by their offset #5919
+ [radarhere]
+
+- Improved I;16 operations on big endian #5901
+ [radarhere]
+
+- Limit quantized palette to number of colors #5879
+ [radarhere]
+
+- Fixed palette index for zeroed color in FASTOCTREE quantize #5869
+ [radarhere]
+
+- When saving RGBA to GIF, make use of first transparent palette entry #5859
+ [radarhere]
+
+- Pass SAMPLEFORMAT to libtiff #5848
+ [radarhere]
+
+- Added rounding when converting P and PA #5824
+ [radarhere]
+
+- Improved putdata() documentation and data handling #5910
+ [radarhere]
+
+- Exclude carriage return in PDF regex to help prevent ReDoS #5912
+ [hugovk]
+
+- Fixed freeing pointer in ImageDraw.Outline.transform #5909
+ [radarhere]
+
+- Added ImageShow support for xdg-open #5897
+ [m-shinder, radarhere]
+
+- Support 16-bit grayscale ImageQt conversion #5856
+ [cmbruns, radarhere]
+
+- Convert subsequent GIF frames to RGB or RGBA #5857
+ [radarhere]
+
+- Do not prematurely return in ImageFile when saving to stdout #5665
+ [infmagic2047, radarhere]
+
+- Added support for top right and bottom right TGA orientations #5829
+ [radarhere]
+
+- Corrected ICNS file length in header #5845
+ [radarhere]
+
+- Block tile TIFF tags when saving #5839
+ [radarhere]
+
+- Added line width argument to polygon #5694
+ [radarhere]
+
+- Do not redeclare class each time when converting to NumPy #5844
+ [radarhere]
+
+- Only prevent repeated polygon pixels when drawing with transparency #5835
+ [radarhere]
+
+- Add support for pickling TrueType fonts #5826
+ [hugovk, radarhere]
+
+- Only prefer command line tools SDK on macOS over default MacOSX SDK #5828
+ [radarhere]
+
+- Drop support for soon-EOL Python 3.6 #5768
+ [hugovk, nulano, radarhere]
+
+- Fix compilation on 64-bit Termux #5793
+ [landfillbaby]
+
+- Use title for display in ImageShow #5788
+ [radarhere]
+
+- Remove support for FreeType 2.7 and older #5777
+ [hugovk, radarhere]
+
+- Fix for PyQt6 #5775
+ [hugovk, radarhere]
+
+- Removed deprecated PILLOW_VERSION, Image.show command parameter, Image._showxv and ImageFile.raise_ioerror #5776
+ [radarhere]
+
+8.4.0 (2021-10-15)
+------------------
+
+- Prefer global transparency in GIF when replacing with background color #5756
+ [radarhere]
+
+- Added "exif" keyword argument to TIFF saving #5575
+ [radarhere]
+
+- Copy Python palette to new image in quantize() #5696
+ [radarhere]
+
+- Read ICO AND mask from end #5667
+ [radarhere]
+
+- Actually check the framesize in FliDecode.c #5659
+ [wiredfool]
+
+- Determine JPEG2000 mode purely from ihdr header box #5654
+ [radarhere]
+
+- Fixed using info dictionary when writing multiple APNG frames #5611
+ [radarhere]
+
+- Allow saving 1 and L mode TIFF with PhotometricInterpretation 0 #5655
+ [radarhere]
+
+- For GIF save_all with palette, do not include palette with each frame #5603
+ [radarhere]
+
+- Keep transparency when converting from P to LA or PA #5606
+ [radarhere]
+
+- Copy palette to new image in transform() #5647
+ [radarhere]
+
+- Added "transparency" argument to EpsImagePlugin load() #5620
+ [radarhere]
+
+- Corrected pathlib.Path detection when saving #5633
+ [radarhere]
+
+- Added WalImageFile class #5618
+ [radarhere]
+
+- Consider I;16 pixel size when drawing text #5598
+ [radarhere]
+
+- If default conversion from P is RGB with transparency, convert to RGBA #5594
+ [radarhere]
+
+- Speed up rotating square images by 90 or 270 degrees #5646
+ [radarhere]
+
+- Add support for reading DPI information from JPEG2000 images
+ [rogermb, radarhere]
+
+- Catch TypeError from corrupted DPI value in EXIF #5639
+ [homm, radarhere]
+
+- Do not close file pointer when saving SGI images #5645
+ [farizrahman4u, radarhere]
+
+- Deprecate ImagePalette size parameter #5641
+ [radarhere, hugovk]
+
+- Prefer command line tools SDK on macOS #5624
+ [radarhere]
+
+- Added tags when saving YCbCr TIFF #5597
+ [radarhere]
+
+- PSD layer count may be negative #5613
+ [radarhere]
+
+- Fixed ImageOps expand with tuple border on P image #5615
+ [radarhere]
+
+- Fixed error saving APNG with duplicate frames and different duration times #5609
+ [thak1411, radarhere]
+
+8.3.2 (2021-09-02)
+------------------
+
+- CVE-2021-23437 Raise ValueError if color specifier is too long
+ [hugovk, radarhere]
+
+- Fix 6-byte OOB read in FliDecode
+ [wiredfool]
+
+- Add support for Python 3.10 #5569, #5570
+ [hugovk, radarhere]
+
+- Ensure TIFF ``RowsPerStrip`` is multiple of 8 for JPEG compression #5588
+ [kmilos, radarhere]
+
+- Updates for ``ImagePalette`` channel order #5599
+ [radarhere]
+
+- Hide FriBiDi shim symbols to avoid conflict with real FriBiDi library #5651
+ [nulano]
+
+8.3.1 (2021-07-06)
+------------------
+
+- Catch OSError when checking if fp is sys.stdout #5585
+ [radarhere]
+
+- Handle removing orientation from alternate types of EXIF data #5584
+ [radarhere]
+
+- Make Image.__array__ take optional dtype argument #5572
+ [t-vi, radarhere]
+
+8.3.0 (2021-07-01)
+------------------
+
+- Use snprintf instead of sprintf. CVE-2021-34552 #5567
+ [radarhere]
+
+- Limit TIFF strip size when saving with LibTIFF #5514
+ [kmilos]
+
+- Allow ICNS save on all operating systems #4526
+ [baletu, radarhere, newpanjing, hugovk]
+
+- De-zigzag JPEG's DQT when loading; deprecate convert_dict_qtables #4989
+ [gofr, radarhere]
+
+- Replaced xml.etree.ElementTree #5565
+ [radarhere]
+
+- Moved CVE image to pillow-depends #5561
+ [radarhere]
+
+- Added tag data for IFD groups #5554
+ [radarhere]
+
+- Improved ImagePalette #5552
+ [radarhere]
+
+- Add DDS saving #5402
+ [radarhere]
+
+- Improved getxmp() #5455
+ [radarhere]
+
+- Convert to float for comparison with float in IFDRational __eq__ #5412
+ [radarhere]
+
+- Allow getexif() to access TIFF tag_v2 data #5416
+ [radarhere]
+
+- Read FITS image mode and size #5405
+ [radarhere]
+
+- Merge parallel horizontal edges in ImagingDrawPolygon #5347
+ [radarhere, hrdrq]
+
+- Use transparency behind first GIF frame and when disposing to background #5557
+ [radarhere, zewt]
+
+- Avoid unstable nature of qsort in Quant.c #5367
+ [radarhere]
+
+- Copy palette to new images in ImageOps expand #5551
+ [radarhere]
+
+- Ensure palette string matches RGB mode #5549
+ [radarhere]
+
+- Do not modify EXIF of original image instance in exif_transpose() #5547
+ [radarhere]
+
+- Fixed default numresolution for small JPEG2000 images #5540
+ [radarhere]
+
+- Added DDS BC5 reading #5501
+ [radarhere]
+
+- Raise an error if ImageDraw.textbbox is used without a TrueType font #5510
+ [radarhere]
+
+- Added ICO saving in BMP format #5513
+ [radarhere]
+
+- Ensure PNG seeks to end of previous chunk at start of load_end #5493
+ [radarhere]
+
+- Do not allow TIFF to seek to a past frame #5473
+ [radarhere]
+
+- Avoid race condition when displaying images with eog #5507
+ [mconst]
+
+- Added specific error messages when ink has incorrect number of bands #5504
+ [radarhere]
+
+- Allow converting an image to a numpy array to raise errors #5379
+ [radarhere]
+
+- Removed DPI rounding from BMP, JPEG, PNG and WMF loading #5476, #5470
+ [radarhere]
+
+- Remove spikes when drawing thin pieslices #5460
+ [xtsm]
+
+- Updated default value for SAMPLESPERPIXEL TIFF tag #5452
+ [radarhere]
+
+- Removed TIFF DPI rounding #5446
+ [radarhere, hugovk]
+
+- Include code in WebP error #5471
+ [radarhere]
+
+- Do not alter pixels outside mask when drawing text on an image with transparency #5434
+ [radarhere]
+
+- Reset handle when seeking backwards in TIFF #5443
+ [radarhere]
+
+- Replace sys.stdout with sys.stdout.buffer when saving #5437
+ [radarhere]
+
+- Fixed UNDEFINED TIFF tag of length 0 being changed in roundtrip #5426
+ [radarhere]
+
+- Fixed bug when checking FreeType2 version if it is not installed #5445
+ [radarhere]
+
+- Do not round dimensions when saving PDF #5459
+ [radarhere]
+
+- Added ImageOps contain() #5417
+ [radarhere, hugovk]
+
+- Changed WebP default "method" value to 4 #5450
+ [radarhere]
+
+- Switched to saving 1-bit PDFs with DCTDecode #5430
+ [radarhere]
+
+- Use bpp from ICO header #5429
+ [radarhere]
+
+- Corrected JPEG APP14 transform value #5408
+ [radarhere]
+
+- Changed TIFF tag 33723 length to 1 #5425
+ [radarhere]
+
+- Changed ImageMorph incorrect mode errors to ValueError #5414
+ [radarhere]
+
+- Add EXIF tags specified in EXIF 2.32 #5419
+ [gladiusglad]
+
+- Treat previous contents of first GIF frame as transparent #5391
+ [radarhere]
+
+- For special image modes, revert default resize resampling to NEAREST #5411
+ [radarhere]
+
+- JPEG2000: Support decoding subsampled RGB and YCbCr images #4996
+ [nulano, radarhere]
+
+- Stop decoding BC1 punchthrough alpha in BC2&3 #4144
+ [jansol]
+
+- Use zero if GIF background color index is missing #5390
+ [radarhere]
+
+- Fixed ensuring that GIF previous frame was loaded #5386
+ [radarhere]
+
+- Valgrind fixes #5397
+ [wiredfool]
+
+- Round down the radius in rounded_rectangle #5382
+ [radarhere]
+
+- Fixed reading uncompressed RGB data from DDS #5383
+ [radarhere]
+
+8.2.0 (2021-04-01)
+------------------
+
+- Added getxmp() method #5144
+ [UrielMaD, radarhere]
+
+- Add ImageShow support for GraphicsMagick #5349
+ [latosha-maltba, radarhere]
+
+- Do not load transparent pixels from subsequent GIF frames #5333
+ [zewt, radarhere]
+
+- Use LZW encoding when saving GIF images #5291
+ [raygard]
+
+- Set all transparent colors to be equal in quantize() #5282
+ [radarhere]
+
+- Allow PixelAccess to use Python __int__ when parsing x and y #5206
+ [radarhere]
+
+- Removed Image._MODEINFO #5316
+ [radarhere]
+
+- Add preserve_tone option to autocontrast #5350
+ [elejke, radarhere]
+
+- Fixed linear_gradient and radial_gradient I and F modes #5274
+ [radarhere]
+
+- Add support for reading TIFFs with PlanarConfiguration=2 #5364
+ [kkopachev, wiredfool, nulano]
+
+- Deprecated categories #5351
+ [radarhere]
+
+- Do not premultiply alpha when resizing with Image.NEAREST resampling #5304
+ [nulano]
+
+- Dynamically link FriBiDi instead of Raqm #5062
+ [nulano]
+
+- Allow fewer PNG palette entries than the bit depth maximum when saving #5330
+ [radarhere]
+
+- Use duration from info dictionary when saving WebP #5338
+ [radarhere]
+
+- Stop flattening EXIF IFD into getexif() #4947
+ [radarhere, kkopachev]
+
+- Replaced tiff_deflate with tiff_adobe_deflate compression when saving TIFF images #5343
+ [radarhere]
+
+- Save ICC profile from TIFF encoderinfo #5321
+ [radarhere]
+
+- Moved RGB fix inside ImageQt class #5268
+ [radarhere]
+
+- Allow alpha_composite destination to be negative #5313
+ [radarhere]
+
+- Ensure file is closed if it is opened by ImageQt.ImageQt #5260
+ [radarhere]
+
+- Added ImageDraw rounded_rectangle method #5208
+ [radarhere]
+
+- Added IPythonViewer #5289
+ [radarhere, Kipkurui-mutai]
+
+- Only draw each rectangle outline pixel once #5183
+ [radarhere]
+
+- Use mmap instead of built-in Win32 mapper #5224
+ [radarhere, cgohlke]
+
+- Handle PCX images with an odd stride #5214
+ [radarhere]
+
+- Only read different sizes for "Large Thumbnail" MPO frames #5168
+ [radarhere]
+
+- Added PyQt6 support #5258
+ [radarhere]
+
+- Changed Image.open formats parameter to be case-insensitive #5250
+ [Piolie, radarhere]
+
+- Deprecate Tk/Tcl 8.4, to be removed in Pillow 10 (2023-07-01) #5216
+ [radarhere]
+
+- Added tk version to pilinfo #5226
+ [radarhere, nulano]
+
+- Support for ignoring tests when running valgrind #5150
+ [wiredfool, radarhere, hugovk]
+
+- OSS-Fuzz support #5189
+ [wiredfool, radarhere]
+
+8.1.2 (2021-03-06)
+------------------
+
+- Fix Memory DOS in BLP (CVE-2021-27921), ICNS (CVE-2021-27922) and ICO (CVE-2021-27923) Image Plugins
+ [wiredfool]
+
+8.1.1 (2021-03-01)
+------------------
+
+- Use more specific regex chars to prevent ReDoS. CVE-2021-25292
+ [hugovk]
+
+- Fix OOB Read in TiffDecode.c, and check the tile validity before reading. CVE-2021-25291
+ [wiredfool]
+
+- Fix negative size read in TiffDecode.c. CVE-2021-25290
+ [wiredfool]
+
+- Fix OOB read in SgiRleDecode.c. CVE-2021-25293
+ [wiredfool]
+
+- Incorrect error code checking in TiffDecode.c. CVE-2021-25289
+ [wiredfool]
+
+- PyModule_AddObject fix for Python 3.10 #5194
+ [radarhere]
+
+8.1.0 (2021-01-02)
+------------------
+
+- Fix TIFF OOB Write error. CVE-2020-35654 #5175
+ [wiredfool]
+
+- Fix for Read Overflow in PCX Decoding. CVE-2020-35653 #5174
+ [wiredfool, radarhere]
+
+- Fix for SGI Decode buffer overrun. CVE-2020-35655 #5173
+ [wiredfool, radarhere]
+
+- Fix OOB Read when saving GIF of xsize=1 #5149
+ [wiredfool]
+
+- Makefile updates #5159
+ [wiredfool, radarhere]
+
+- Add support for PySide6 #5161
+ [hugovk]
+
+- Use disposal settings from previous frame in APNG #5126
+ [radarhere]
+
+- Added exception explaining that _repr_png_ saves to PNG #5139
+ [radarhere]
+
+- Use previous disposal method in GIF load_end #5125
+ [radarhere]
+
+- Allow putpalette to accept 1024 integers to include alpha values #5089
+ [radarhere]
+
+- Fix OOB Read when writing TIFF with custom Metadata #5148
+ [wiredfool]
+
+- Added append_images support for ICO #4568
+ [ziplantil, radarhere]
+
+- Block TIFFTAG_SUBIFD #5120
+ [radarhere]
+
+- Fixed dereferencing potential null pointers #5108, #5111
+ [cgohlke, radarhere]
+
+- Deprecate FreeType 2.7 #5098
+ [hugovk, radarhere]
+
+- Moved warning to end of execution #4965
+ [radarhere]
+
+- Removed unused fromstring and tostring C methods #5026
+ [radarhere]
+
+- init() if one of the formats is unrecognised #5037
+ [radarhere]
+
+- Moved string_dimension CVE image to pillow-depends #4993
+ [radarhere]
+
+- Support raw rgba8888 for DDS #4760
+ [qiankanglai]
+
+8.0.1 (2020-10-22)
+------------------
+
+- Update FreeType used in binary wheels to 2.10.4 to fix CVE-2020-15999.
+ [radarhere]
+
+- Moved string_dimension image to pillow-depends #4993
+ [radarhere]
+
+8.0.0 (2020-10-15)
+------------------
+
+- Drop support for EOL Python 3.5 #4746, #4794
+ [hugovk, radarhere, nulano]
+
+- Drop support for PyPy3 < 7.2.0 #4964
+ [nulano]
+
+- Remove ImageCms.CmsProfile attributes deprecated since 3.2.0 #4768
+ [hugovk, radarhere]
+
+- Remove long-deprecated Image.py functions #4798
+ [hugovk, nulano, radarhere]
+
+- Add support for 16-bit precision JPEG quantization values #4918
+ [gofr]
+
+- Added reading of IFD tag type #4979
+ [radarhere]
+
+- Initialize offset memory for PyImagingPhotoPut #4806
+ [nqbit]
+
+- Fix TiffDecode comparison warnings #4756
+ [nulano]
+
+- Docs: Add dark mode #4968
+ [hugovk, nulano]
+
+- Added macOS SDK install path to library and include directories #4974
+ [radarhere, fxcoudert]
+
+- Imaging.h: prevent confusion with system #4923
+ [ax3l, ,radarhere]
+
+- Avoid using pkg_resources in PIL.features.pilinfo #4975
+ [nulano]
+
+- Add getlength and getbbox functions for TrueType fonts #4959
+ [nulano, radarhere, hugovk]
+
+- Allow tuples with one item to give single color value in getink #4927
+ [radarhere, nulano]
+
+- Add support for CBDT and COLR fonts #4955
+ [nulano, hugovk]
+
+- Removed OSError in favour of DecompressionBombError for BMP #4966
+ [radarhere]
+
+- Implemented another ellipse drawing algorithm #4523
+ [xtsm, radarhere]
+
+- Removed unused JpegImagePlugin._fixup_dict function #4957
+ [radarhere]
+
+- Added reading and writing of private PNG chunks #4292
+ [radarhere]
+
+- Implement anchor for TrueType fonts #4930
+ [nulano, hugovk]
+
+- Fixed bug in Exif __delitem__ #4942
+ [radarhere]
+
+- Fix crash in ImageTk.PhotoImage on MinGW 64-bit #4946
+ [nulano]
+
+- Moved CVE images to pillow-depends #4929
+ [radarhere]
+
+- Refactor font_getsize and font_render #4910
+ [nulano]
+
+- Fixed loading profile with non-ASCII path on Windows #4914
+ [radarhere]
+
+- Fixed effect_spread bug for zero distance #4908
+ [radarhere, hugovk]
+
+- Added formats parameter to Image.open #4837
+ [nulano, radarhere]
+
+- Added regular_polygon draw method #4846
+ [comhar]
+
+- Raise proper TypeError in putpixel #4882
+ [nulano, hugovk]
+
+- Added writing of subIFDs #4862
+ [radarhere]
+
+- Fix IFDRational __eq__ bug #4888
+ [luphord, radarhere]
+
+- Fixed duplicate variable name #4885
+ [liZe, radarhere]
+
+- Added homebrew zlib include directory #4842
+ [radarhere]
+
+- Corrected inverted PDF CMYK colors #4866
+ [radarhere]
+
+- Do not try to close file pointer if file pointer is empty #4823
+ [radarhere]
+
+- ImageOps.autocontrast: add mask parameter #4843
+ [navneeth, hugovk]
+
+- Read EXIF data tEXt chunk into info as bytes instead of string #4828
+ [radarhere]
+
+- Replaced distutils with setuptools #4797, #4809, #4814, #4817, #4829, #4890
+ [hugovk, radarhere]
+
+- Add MIME type to PsdImagePlugin #4788
+ [samamorgan]
+
+- Allow ImageOps.autocontrast to specify low and high cutoffs separately #4749
+ [millionhz, radarhere]
+
+7.2.0 (2020-07-01)
+------------------
+
+- Do not convert I;16 images when showing PNGs #4744
+ [radarhere]
+
+- Fixed ICNS file pointer saving #4741
+ [radarhere]
+
+- Fixed loading non-RGBA mode APNGs with dispose background #4742
+ [radarhere]
+
+- Deprecated _showxv #4714
+ [radarhere]
+
+- Deprecate Image.show(command="...") #4646
+ [nulano, hugovk, radarhere]
+
+- Updated JPEG magic number #4707
+ [Cykooz, radarhere]
+
+- Change STRIPBYTECOUNTS to LONG if necessary when saving #4626
+ [radarhere, hugovk]
+
+- Write JFIF header when saving JPEG #4639
+ [radarhere]
+
+- Replaced tiff_jpeg with jpeg compression when saving TIFF images #4627
+ [radarhere]
+
+- Writing TIFF tags: improved BYTE, added UNDEFINED #4605
+ [radarhere]
+
+- Consider transparency when pasting text on an RGBA image #4566
+ [radarhere]
+
+- Added method argument to single frame WebP saving #4547
+ [radarhere]
+
+- Use ImageFileDirectory_v2 in Image.Exif #4637
+ [radarhere]
+
+- Corrected reading EXIF metadata without prefix #4677
+ [radarhere]
+
+- Fixed drawing a jointed line with a sequence of numeric values #4580
+ [radarhere]
+
+- Added support for 1-D NumPy arrays #4608
+ [radarhere]
+
+- Parse orientation from XMP tags #4560
+ [radarhere]
+
+- Speed up text layout by not rendering glyphs #4652
+ [nulano]
+
+- Fixed ZeroDivisionError in Image.thumbnail #4625
+ [radarhere]
+
+- Replaced TiffImagePlugin DEBUG with logging #4550
+ [radarhere]
+
+- Fix repeatedly loading .gbr #4620
+ [ElinksFr, radarhere]
+
+- JPEG: Truncate icclist instead of setting to None #4613
+ [homm]
+
+- Fixes default offset for Exif #4594
+ [rodrigob, radarhere]
+
+- Fixed bug when unpickling TIFF images #4565
+ [radarhere]
+
+- Fix pickling WebP #4561
+ [hugovk, radarhere]
+
+- Replace IOError and WindowsError aliases with OSError #4536
+ [hugovk, radarhere]
+
+7.1.2 (2020-04-25)
+------------------
+
+- Raise an EOFError when seeking too far in PNG #4528
+ [radarhere]
+
+7.1.1 (2020-04-02)
+------------------
+
+- Fix regression seeking and telling PNGs #4512 #4514
+ [hugovk, radarhere]
+
+7.1.0 (2020-04-01)
+------------------
+
+- Fix multiple OOB reads in FLI decoding #4503
+ [wiredfool]
+
+- Fix buffer overflow in SGI-RLE decoding #4504
+ [wiredfool, hugovk]
+
+- Fix bounds overflow in JPEG 2000 decoding #4505
+ [wiredfool]
+
+- Fix bounds overflow in PCX decoding #4506
+ [wiredfool]
+
+- Fix 2 buffer overflows in TIFF decoding #4507
+ [wiredfool]
+
+- Add APNG support #4243
+ [pmrowla, radarhere, hugovk]
+
+- ImageGrab.grab() for Linux with XCB #4260
+ [nulano, radarhere]
+
+- Added three new channel operations #4230
+ [dwastberg, radarhere]
+
+- Prevent masking of Image reduce method in Jpeg2KImagePlugin #4474
+ [radarhere, homm]
+
+- Added reading of earlier ImageMagick PNG EXIF data #4471
+ [radarhere]
+
+- Fixed endian handling for I;16 getextrema #4457
+ [radarhere]
+
+- Release buffer if function returns prematurely #4381
+ [radarhere]
+
+- Add JPEG comment to info dictionary #4455
+ [radarhere]
+
+- Fix size calculation of Image.thumbnail() #4404
+ [orlnub123]
+
+- Fixed stroke on FreeType < 2.9 #4401
+ [radarhere]
+
+- If present, only use alpha channel for bounding box #4454
+ [radarhere]
+
+- Warn if an unknown feature is passed to features.check() #4438
+ [jdufresne]
+
+- Fix Name field length when saving IM images #4424
+ [hugovk, radarhere]
+
+- Allow saving of zero quality JPEG images #4440
+ [radarhere]
+
+- Allow explicit zero width to hide outline #4334
+ [radarhere]
+
+- Change ContainerIO return type to match file object mode #4297
+ [jdufresne, radarhere]
+
+- Only draw each polygon pixel once #4333
+ [radarhere]
+
+- Add support for shooting situation Exif IFD tags #4398
+ [alexagv]
+
+- Handle multiple and malformed JPEG APP13 markers #4370
+ [homm]
+
+- Depends: Update libwebp to 1.1.0 #4342, libjpeg to 9d #4352
+ [radarhere]
+
+7.0.0 (2020-01-02)
+------------------
+
+- Drop support for EOL Python 2.7 #4109
+ [hugovk, radarhere, jdufresne]
+
+- Fix rounding error on RGB to L conversion #4320
+ [homm]
+
+- Exif writing fixes: Rational boundaries and signed/unsigned types #3980
+ [kkopachev, radarhere]
+
+- Allow loading of WMF images at a given DPI #4311
+ [radarhere]
+
+- Added reduce operation #4251
+ [homm]
+
+- Raise ValueError for io.StringIO in Image.open #4302
+ [radarhere, hugovk]
+
+- Fix thumbnail geometry when DCT scaling is used #4231
+ [homm, radarhere]
+
+- Use default DPI when exif provides invalid x_resolution #4147
+ [beipang2, radarhere]
+
+- Change default resize resampling filter from NEAREST to BICUBIC #4255
+ [homm]
+
+- Fixed black lines on upscaled images with the BOX filter #4278
+ [homm]
+
+- Better thumbnail aspect ratio preservation #4256
+ [homm]
+
+- Add La mode packing and unpacking #4248
+ [homm]
+
+- Include tests in coverage reports #4173
+ [hugovk]
+
+- Handle broken Photoshop data #4239
+ [radarhere]
+
+- Raise a specific exception if no data is found for an MPO frame #4240
+ [radarhere]
+
+- Fix Unicode support for PyPy #4145
+ [nulano]
+
+- Added UnidentifiedImageError #4182
+ [radarhere, hugovk]
+
+- Remove deprecated __version__ from plugins #4197
+ [hugovk, radarhere]
+
+- Fixed freeing unallocated pointer when resizing with height too large #4116
+ [radarhere]
+
+- Copy info in Image.transform #4128
+ [radarhere]
+
+- Corrected DdsImagePlugin setting info gamma #4171
+ [radarhere]
+
+- Depends: Update libtiff to 4.1.0 #4195, Tk Tcl to 8.6.10 #4229, libimagequant to 2.12.6 #4318
+ [radarhere]
+
+- Improve handling of file resources #3577
+ [jdufresne]
+
+- Removed CI testing of Fedora 29 #4165
+ [hugovk]
+
+- Added pypy3 to tox envlist #4137
+ [jdufresne]
+
+- Drop support for EOL PyQt4 and PySide #4108
+ [hugovk, radarhere]
+
+- Removed deprecated setting of TIFF image sizes #4114
+ [radarhere]
+
+- Removed deprecated PILLOW_VERSION #4107
+ [hugovk]
+
+- Changed default frombuffer raw decoder args #1730
+ [radarhere]
+
+6.2.2 (2020-01-02)
+------------------
+
+- This is the last Pillow release to support Python 2.7 #3642
+
+- Overflow checks for realloc for tiff decoding. CVE-2020-5310
+ [wiredfool, radarhere]
+
+- Catch SGI buffer overrun. CVE-2020-5311
+ [radarhere]
+
+- Catch PCX P mode buffer overrun. CVE-2020-5312
+ [radarhere]
+
+- Catch FLI buffer overrun. CVE-2020-5313
+ [radarhere]
+
+- Raise an error for an invalid number of bands in FPX image. CVE-2019-19911
+ [wiredfool, radarhere]
+
+6.2.1 (2019-10-21)
+------------------
+
+- Add support for Python 3.8 #4141
+ [hugovk]
+
+6.2.0 (2019-10-01)
+------------------
+
+- Catch buffer overruns #4104
+ [radarhere]
+
+- Initialize rows_per_strip when RowsPerStrip tag is missing #4034
+ [cgohlke, radarhere]
+
+- Raise error if TIFF dimension is a string #4103
+ [radarhere]
+
+- Added decompression bomb checks #4102
+ [radarhere]
+
+- Fix ImageGrab.grab DPI scaling on Windows 10 version 1607+ #4000
+ [nulano, radarhere]
+
+- Corrected negative seeks #4101
+ [radarhere]
+
+- Added argument to capture all screens on Windows #3950
+ [nulano, radarhere]
+
+- Updated warning to specify when Image.frombuffer defaults will change #4086
+ [radarhere]
+
+- Changed WindowsViewer format to PNG #4080
+ [radarhere]
+
+- Use TIFF orientation #4063
+ [radarhere]
+
+- Raise the same error if a truncated image is loaded a second time #3965
+ [radarhere]
+
+- Lazily use ImageFileDirectory_v1 values from Exif #4031
+ [radarhere]
+
+- Improved HSV conversion #4004
+ [radarhere]
+
+- Added text stroking #3978
+ [radarhere, hugovk]
+
+- No more deprecated bdist_wininst .exe installers #4029
+ [hugovk]
+
+- Do not allow floodfill to extend into negative coordinates #4017
+ [radarhere]
+
+- Fixed arc drawing bug for a non-whole number of degrees #4014
+ [radarhere]
+
+- Fix bug when merging identical images to GIF with a list of durations #4003
+ [djy0, radarhere]
+
+- Fix bug in TIFF loading of BufferedReader #3998
+ [chadawagner]
+
+- Added fallback for finding ld on MinGW Cygwin #4019
+ [radarhere]
+
+- Remove indirect dependencies from requirements.txt #3976
+ [hugovk]
+
+- Depends: Update libwebp to 1.0.3 #3983, libimagequant to 2.12.5 #3993, freetype to 2.10.1 #3991
+ [radarhere]
+
+- Change overflow check to use PY_SSIZE_T_MAX #3964
+ [radarhere]
+
+- Report reason for pytest skips #3942
+ [hugovk]
+
+6.1.0 (2019-07-01)
+------------------
+
+- Deprecate Image.__del__ #3929
+ [jdufresne]
+
+- Tiff: Add support for JPEG quality #3886
+ [olt]
+
+- Respect the PKG_CONFIG environment variable when building #3928
+ [chewi]
+
+- Use explicit memcpy() to avoid unaligned memory accesses #3225
+ [DerDakon]
+
+- Improve encoding of TIFF tags #3861
+ [olt]
+
+- Update Py_UNICODE to Py_UCS4 #3780
+ [nulano]
+
+- Consider I;16 pixel size when drawing #3899
+ [radarhere]
+
+- Add TIFFTAG_SAMPLEFORMAT to blocklist #3926
+ [cgohlke, radarhere]
+
+- Create GIF deltas from background colour of GIF frames if disposal mode is 2 #3708
+ [sircinnamon, radarhere]
+
+- Added ImageSequence all_frames #3778
+ [radarhere]
+
+- Use unsigned int to store TIFF IFD offsets #3923
+ [cgohlke]
+
+- Include CPPFLAGS when searching for libraries #3819
+ [jefferyto]
+
+- Updated TIFF tile descriptors to match current decoding functionality #3795
+ [dmnisson]
+
+- Added an ``image.entropy()`` method (second revision) #3608
+ [fish2000]
+
+- Pass the correct types to PyArg_ParseTuple #3880
+ [QuLogic]
+
+- Fixed crash when loading non-font bytes #3912
+ [radarhere]
+
+- Fix SPARC memory alignment issues in Pack/Unpack functions #3858
+ [kulikjak]
+
+- Added CMYK;16B and CMYK;16N unpackers #3913
+ [radarhere]
+
+- Fixed bugs in calculating text size #3864
+ [radarhere]
+
+- Add __main__.py to output basic format and support information #3870
+ [jdufresne]
+
+- Added variation font support #3802
+ [radarhere]
+
+- Do not down-convert if image is LA when showing with PNG format #3869
+ [radarhere]
+
+- Improve handling of PSD frames #3759
+ [radarhere]
+
+- Improved ICO and ICNS loading #3897
+ [radarhere]
+
+- Changed Preview application path so that it is no longer static #3896
+ [radarhere]
+
+- Corrected ttb text positioning #3856
+ [radarhere]
+
+- Handle unexpected ICO image sizes #3836
+ [radarhere]
+
+- Fixed bits value for RGB;16N unpackers #3837
+ [kkopachev]
+
+- Travis CI: Add Fedora 30, remove Fedora 28 #3821
+ [hugovk]
+
+- Added reading of CMYK;16L TIFF images #3817
+ [radarhere]
+
+- Fixed dimensions of 1-bit PDFs #3827
+ [radarhere]
+
+- Fixed opening mmap image through Path on Windows #3825
+ [radarhere]
+
+- Fixed ImageDraw arc gaps #3824
+ [radarhere]
+
+- Expand GIF to include frames with extents outside the image size #3822
+ [radarhere]
+
+- Fixed ImageTk getimage #3814
+ [radarhere]
+
+- Fixed bug in decoding large images #3791
+ [radarhere]
+
+- Fixed reading APP13 marker without Photoshop data #3771
+ [radarhere]
+
+- Added option to include layered windows in ImageGrab.grab on Windows #3808
+ [radarhere]
+
+- Detect libimagequant when installed by pacman on MingW #3812
+ [radarhere]
+
+- Fixed raqm layout bug #3787
+ [radarhere]
+
+- Fixed loading font with non-Unicode path on Windows #3785
+ [radarhere]
+
+- Travis CI: Upgrade PyPy from 6.0.0 to 7.1.1 #3783
+ [hugovk, johnthagen]
+
+- Depends: Updated openjpeg to 2.3.1 #3794, raqm to 0.7.0 #3877, libimagequant to 2.12.3 #3889
+ [radarhere]
+
+- Fix numpy bool bug #3790
+ [radarhere]
+
+6.0.0 (2019-04-01)
+------------------
+
+- Python 2.7 support will be removed in Pillow 7.0.0 #3682
+ [hugovk]
+
+- Add EXIF class #3625
+ [radarhere]
+
+- Add ImageOps exif_transpose method #3687
+ [radarhere]
+
+- Added warnings to deprecated CMSProfile attributes #3615
+ [hugovk]
+
+- Documented reading TIFF multiframe images #3720
+ [akuchling]
+
+- Improved speed of opening an MPO file #3658
+ [Glandos]
+
+- Update palette in quantize #3721
+ [radarhere]
+
+- Improvements to TIFF is_animated and n_frames #3714
+ [radarhere]
+
+- Fixed incompatible pointer type warnings #3754
+ [radarhere]
+
+- Improvements to PA and LA conversion and palette operations #3728
+ [radarhere]
+
+- Consistent DPI rounding #3709
+ [radarhere]
+
+- Change size of MPO image to match frame #3588
+ [radarhere]
+
+- Read Photoshop resolution data #3701
+ [radarhere]
+
+- Ensure image is mutable before saving #3724
+ [radarhere]
+
+- Correct remap_palette documentation #3740
+ [radarhere]
+
+- Promote P images to PA in putalpha #3726
+ [radarhere]
+
+- Allow RGB and RGBA values for new P images #3719
+ [radarhere]
+
+- Fixed TIFF bug when seeking backwards and then forwards #3713
+ [radarhere]
+
+- Cache EXIF information #3498
+ [Glandos]
+
+- Added transparency for all PNG greyscale modes #3744
+ [radarhere]
+
+- Fix deprecation warnings in Python 3.8 #3749
+ [radarhere]
+
+- Fixed GIF bug when rewinding to a non-zero frame #3716
+ [radarhere]
+
+- Only close original fp in __del__ and __exit__ if original fp is exclusive #3683
+ [radarhere]
+
+- Fix BytesWarning in Tests/test_numpy.py #3725
+ [jdufresne]
+
+- Add missing MIME types and extensions #3520
+ [pirate486743186]
+
+- Add I;16 PNG save #3566
+ [radarhere]
+
+- Add support for BMP RGBA bitfield compression #3705
+ [radarhere]
+
+- Added ability to set language for text rendering #3693
+ [iwsfutcmd]
+
+- Only close exclusive fp on Image __exit__ #3698
+ [radarhere]
+
+- Changed EPS subprocess stdout from devnull to None #3635
+ [radarhere]
+
+- Add reading old-JPEG compressed TIFFs #3489
+ [kkopachev]
+
+- Add EXIF support for PNG #3674
+ [radarhere]
+
+- Add option to set dither param on quantize #3699
+ [glasnt]
+
+- Add reading of DDS uncompressed RGB data #3673
+ [radarhere]
+
+- Correct length of Tiff BYTE tags #3672
+ [radarhere]
+
+- Add DIB saving and loading through Image open #3691
+ [radarhere]
+
+- Removed deprecated VERSION #3624
+ [hugovk]
+
+- Fix 'BytesWarning: Comparison between bytes and string' in PdfDict #3580
+ [jdufresne]
+
+- Do not resize in Image.thumbnail if already the destination size #3632
+ [radarhere]
+
+- Replace .seek() magic numbers with io.SEEK_* constants #3572
+ [jdufresne]
+
+- Make ContainerIO.isatty() return a bool, not int #3568
+ [jdufresne]
+
+- Add support to all transpose operations for I;16 modes #3563, #3741
+ [radarhere]
+
+- Deprecate support for PyQt4 and PySide #3655
+ [hugovk, radarhere]
+
+- Add TIFF compression codecs: LZMA, Zstd, WebP #3555
+ [cgohlke]
+
+- Fixed pickling of iTXt class with protocol > 1 #3537
+ [radarhere]
+
+- _util.isPath returns True for pathlib.Path objects #3616
+ [wbadart]
+
+- Remove unnecessary unittest.main() boilerplate from test files #3631
+ [jdufresne]
+
+- Exif: Seek to IFD offset #3584
+ [radarhere]
+
+- Deprecate PIL.*ImagePlugin.__version__ attributes #3628
+ [jdufresne]
+
+- Docs: Add note about ImageDraw operations that exceed image bounds #3620
+ [radarhere]
+
+- Allow for unknown PNG chunks after image data #3558
+ [radarhere]
+
+- Changed EPS subprocess stdin from devnull to None #3611
+ [radarhere]
+
+- Fix possible integer overflow #3609
+ [cgohlke]
+
+- Catch BaseException for resource cleanup handlers #3574
+ [jdufresne]
+
+- Improve pytest configuration to allow specific tests as CLI args #3579
+ [jdufresne]
+
+- Drop support for Python 3.4 #3596
+ [hugovk]
+
+- Remove deprecated PIL.OleFileIO #3598
+ [hugovk]
+
+- Remove deprecated ImageOps undocumented functions #3599
+ [hugovk]
+
+- Depends: Update libwebp to 1.0.2 #3602
+ [radarhere]
+
+- Detect MIME types #3525
+ [radarhere]
+
+5.4.1 (2019-01-06)
+------------------
+
+- File closing: Only close __fp if not fp #3540
+ [radarhere]
+
+- Fix build for Termux #3529
+ [pslacerda]
+
+- PNG: Detect MIME types #3525
+ [radarhere]
+
+- PNG: Handle IDAT chunks after image end #3532
+ [radarhere]
+
+5.4.0 (2019-01-01)
+------------------
+
+- Docs: Improved ImageChops documentation #3522
+ [radarhere]
+
+- Allow RGB and RGBA values for P image putpixel #3519
+ [radarhere]
+
+- Add APNG extension to PNG plugin #3501
+ [pirate486743186, radarhere]
+
+- Lookup ld.so.cache instead of hardcoding search paths #3245
+ [pslacerda]
+
+- Added custom string TIFF tags #3513
+ [radarhere]
+
+- Improve setup.py configuration #3395
+ [diorcety]
+
+- Read textual chunks located after IDAT chunks for PNG #3506
+ [radarhere]
+
+- Performance: Don't try to hash value if enum is empty #3503
+ [Glandos]
+
+- Added custom int and float TIFF tags #3350
+ [radarhere]
+
+- Fixes for issues reported by static code analysis #3393
+ [frenzymadness]
+
+- GIF: Wait until mode is normalized to copy im.info into encoderinfo #3187
+ [radarhere]
+
+- Docs: Add page of deprecations and removals #3486
+ [hugovk]
+
+- Travis CI: Upgrade PyPy from 5.8.0 to 6.0 #3488
+ [hugovk]
+
+- Travis CI: Allow lint job to fail #3467
+ [hugovk]
+
+- Resolve __fp when closing and deleting #3261
+ [radarhere]
+
+- Close exclusive fp before discarding #3461
+ [radarhere]
+
+- Updated open files documentation #3490
+ [radarhere]
+
+- Added libjpeg_turbo to check_feature #3493
+ [radarhere]
+
+- Change color table index background to tuple when saving as WebP #3471
+ [radarhere]
+
+- Allow arbitrary number of comment extension subblocks #3479
+ [radarhere]
+
+- Ensure previous FLI frame is loaded before seeking to the next #3478
+ [radarhere]
+
+- ImageShow improvements #3450
+ [radarhere]
+
+- Depends: Update libimagequant to 2.12.2 #3442, libtiff to 4.0.10 #3458, libwebp to 1.0.1 #3468, Tk Tcl to 8.6.9 #3465
+ [radarhere]
+
+- Check quality_layers type #3464
+ [radarhere]
+
+- Add context manager, __del__ and close methods to TarIO #3455
+ [radarhere]
+
+- Test: Do not play sound when running screencapture command #3454
+ [radarhere]
+
+- Close exclusive fp on open exception #3456
+ [radarhere]
+
+- Only close existing fp in WebP if fp is exclusive #3418
+ [radarhere]
+
+- Docs: Re-add the downloads badge #3443
+ [hugovk]
+
+- Added negative index to PixelAccess #3406
+ [Nazime]
+
+- Change tuple background to global color table index when saving as GIF #3385
+ [radarhere]
+
+- Test: Improved ImageGrab tests #3424
+ [radarhere]
+
+- Flake8 fixes #3422, #3440
+ [radarhere, hugovk]
+
+- Only ask for YCbCr->RGB libtiff conversion for jpeg-compressed tiffs #3417
+ [kkopachev]
+
+- Optimise ImageOps.fit by combining resize and crop #3409
+ [homm]
+
+5.3.0 (2018-10-01)
+------------------
+
+- Changed Image size property to be read-only by default #3203
+ [radarhere]
+
+- Add warnings if image file identification fails due to lack of WebP support #3169
+ [radarhere, hugovk]
+
+- Hide the Ghostscript progress dialog popup on Windows #3378
+ [hugovk]
+
+- Adding support to reading tiled and YcbCr jpeg tiffs through libtiff #3227
+ [kkopachev]
+
+- Fixed None as TIFF compression argument #3310
+ [radarhere]
+
+- Changed GIF seek to remove previous info items #3324
+ [radarhere]
+
+- Improved PDF document info #3274
+ [radarhere]
+
+- Add line width parameter to rectangle and ellipse-based shapes #3094
+ [hugovk, radarhere]
+
+- Fixed decompression bomb check in _crop #3313
+ [dinkolubina, hugovk]
+
+- Added support to ImageDraw.floodfill for non-RGB colors #3377
+ [radarhere]
+
+- Tests: Avoid catching unexpected exceptions in tests #2203
+ [jdufresne]
+
+- Use TextIOWrapper.detach() instead of NoCloseStream #2214
+ [jdufresne]
+
+- Added transparency to matrix conversion #3205
+ [radarhere]
+
+- Added ImageOps pad method #3364
+ [radarhere]
+
+- Give correct extrema for I;16 format images #3359
+ [bz2]
+
+- Added PySide2 #3279
+ [radarhere]
+
+- Corrected TIFF tags #3369
+ [radarhere]
+
+- CI: Install CFFI and pycparser without any PYTHONOPTIMIZE #3374
+ [hugovk]
+
+- Read/Save RGB webp as RGB (instead of RGBX) #3298
+ [kkopachev]
+
+- ImageDraw: Add line joints #3250
+ [radarhere]
+
+- Improved performance of ImageDraw floodfill method #3294
+ [yo1995]
+
+- Fix builds with --parallel #3272
+ [hsoft]
+
+- Add more raw Tiff modes (RGBaX, RGBaXX, RGBAX, RGBAXX) #3335
+ [homm]
+
+- Close existing WebP fp before setting new fp #3341
+ [radarhere]
+
+- Add orientation, compression and id_section as TGA save keyword arguments #3327
+ [radarhere]
+
+- Convert int values of RATIONAL TIFF tags to floats #3338
+ [radarhere, wiredfool]
+
+- Fix code for PYTHONOPTIMIZE #3233
+ [hugovk]
+
+- Changed ImageFilter.Kernel to subclass ImageFilter.BuiltinFilter, instead of the other way around #3273
+ [radarhere]
+
+- Remove unused draw.draw_line, draw.draw_point and font.getabc methods #3232
+ [hugovk]
+
+- Tests: Added ImageFilter tests #3295
+ [radarhere]
+
+- Tests: Added ImageChops tests #3230
+ [hugovk, radarhere]
+
+- AppVeyor: Download lib if not present in pillow-depends #3316
+ [radarhere]
+
+- Travis CI: Add Python 3.7 and Xenial #3234
+ [hugovk]
+
+- Docs: Added documentation for NumPy conversion #3301
+ [radarhere]
+
+- Depends: Update libimagequant to 2.12.1 #3281
+ [radarhere]
+
+- Add three-color support to ImageOps.colorize #3242
+ [tsennott]
+
+- Tests: Add LA to TGA test modes #3222
+ [danpla]
+
+- Skip outline if the draw operation fills with the same colour #2922
+ [radarhere]
+
+- Flake8 fixes #3173, #3380
+ [radarhere]
+
+- Avoid deprecated 'U' mode when opening files #2187
+ [jdufresne]
+
+5.2.0 (2018-07-01)
+------------------
+
+- Fixed saving a multiframe image as a single frame PDF #3137
+ [radarhere]
+
+- If a Qt version is already imported, attempt to use it first #3143
+ [radarhere]
+
+- Fix transform fill color for alpha images #3147
+ [fozcode]
+
+- TGA: Add support for writing RLE data #3186
+ [danpla]
+
+- TGA: Read and write LA data #3178
+ [danpla]
+
+- QuantOctree.c: Remove erroneous attempt to average over an empty range #3196
+ [tkoeppe]
+
+- Changed ICNS format tests to pass on OS X 10.11 #3202
+ [radarhere]
+
+- Fixed bug in ImageDraw.multiline_textsize() #3114
+ [tianyu139]
+
+- Added getsize_multiline support for PIL.ImageFont #3113
+ [tianyu139]
+
+- Added ImageFile get_format_mimetype method #3190
+ [radarhere]
+
+- Changed mmap file pointer to use context manager #3216
+ [radarhere]
+
+- Changed ellipse point calculations to be more evenly distributed #3142
+ [radarhere]
+
+- Only extract first Exif segment #2946
+ [hugovk]
+
+- Tests: Test ImageDraw2, WalImageFile #3135, #2989
+ [hugovk]
+
+- Remove unnecessary '#if 0' code #3075
+ [hugovk]
+
+- Tests: Added GD tests #1817
+ [radarhere]
+
+- Fix collections ABCs DeprecationWarning in Python 3.7 #3123
+ [hugovk]
+
+- unpack_from is faster than unpack of slice #3201
+ [landfillbaby]
+
+- Docs: Add coordinate system links and file handling links in documentation #3204, #3214
+ [radarhere]
+
+- Tests: TestFilePng: Fix test_save_l_transparency() #3182
+ [danpla]
+
+- Docs: Correct argument name #3171
+ [radarhere]
+
+- Docs: Update CMake download URL #3166
+ [radarhere]
+
+- Docs: Improve Image.transform documentation #3164
+ [radarhere]
+
+- Fix transform fillcolor argument when image mode is RGBA or LA #3163
+ [radarhere]
+
+- Tests: More specific Exception testing #3158
+ [radarhere]
+
+- Add getrgb HSB/HSV color strings #3148
+ [radarhere]
+
+- Allow float values in getrgb HSL color string #3146
+ [radarhere]
+
+- AppVeyor: Upgrade to Python 2.7.15 and 3.4.4 #3140
+ [radarhere]
+
+- AppVeyor: Upgrade to PyPy 6.0.0 #3133
+ [hugovk]
+
+- Deprecate PILLOW_VERSION and VERSION #3090
+ [hugovk]
+
+- Support Python 3.7 #3076
+ [hugovk]
+
+- Depends: Update freetype to 2.9.1, libjpeg to 9c, libwebp to 1.0.0 #3121, #3136, #3108
+ [radarhere]
+
+- Build macOS wheels with Xcode 6.4, supporting older macOS versions #3068
+ [wiredfool]
+
+- Fix _i2f compilation on some GCC versions #3067
+ [homm]
+
+- Changed encoderinfo to have priority over info when saving GIF images #3086
+ [radarhere]
+
+- Rename PIL.version to PIL._version and remove it from module #3083
+ [homm]
+
+- Enable background colour parameter on rotate #3057
+ [storesource]
+
+- Remove unnecessary ``#if 1`` directive #3072
+ [jdufresne]
+
+- Remove unused Python class, Path #3070
+ [jdufresne]
+
+- Fix dereferencing type-punned pointer will break strict-aliasing #3069
+ [jdufresne]
+
+5.1.0 (2018-04-02)
+------------------
+
+- Close fp before return in ImagingSavePPM #3061
+ [kathryndavies]
+
+- Added documentation for ICNS append_images #3051
+ [radarhere]
+
+- Docs: Move intro text below its header #3021
+ [hugovk]
+
+- CI: Rename appveyor.yml as .appveyor.yml #2978
+ [hugovk]
+
+- Fix TypeError for JPEG2000 parser feed #3042
+ [hugovk]
+
+- Certain corrupted jpegs can result in no data read #3023
+ [kkopachev]
+
+- Add support for BLP file format #3007
+ [jleclanche]
+
+- Simplify version checks #2998
+ [hugovk]
+
+- Fix "invalid escape sequence" warning on Python 3.6+ #2996
+ [timgraham]
+
+- Allow append_images to set .icns scaled images #3005
+ [radarhere]
+
+- Support appending to existing PDFs #2965
+ [vashek]
+
+- Fix and improve efficient saving of ICNS on macOS #3004
+ [radarhere]
+
+- Build: Enable pip cache in AppVeyor build #3009
+ [thijstriemstra]
+
+- Trim trailing whitespace #2985
+ [Metallicow]
+
+- Docs: Correct reference to Image.new method #3000
+ [radarhere]
+
+- Rearrange ImageFilter classes into alphabetical order #2990
+ [radarhere]
+
+- Test: Remove duplicate line #2983
+ [radarhere]
+
+- Build: Update AppVeyor PyPy version #3003
+ [radarhere]
+
+- Tiff: Open 8 bit Tiffs with 5 or 6 channels, discarding extra channels #2938
+ [homm]
+
+- Readme: Added Twitter badge #2930
+ [hugovk]
+
+- Removed __main__ code from ImageCms #2942
+ [radarhere]
+
+- Test: Changed assert statements to unittest calls #2961
+ [radarhere]
+
+- Depends: Update libimagequant to 2.11.10, raqm to 0.5.0, freetype to 2.9 #3036, #3017, #2957
+ [radarhere]
+
+- Remove _imaging.crc32 in favor of builtin Python crc32 implementation #2935
+ [wiredfool]
+
+- Move Tk directory to src directory #2928
+ [hugovk]
+
+- Enable pip cache in Travis CI #2933
+ [jdufresne]
+
+- Remove unused and duplicate imports #2927
+ [radarhere]
+
+- Docs: Changed documentation references to 2.x to 2.7 #2921
+ [radarhere]
+
+- Fix memory leak when opening webp files #2974
+ [wiredfool]
+
+- Setup: Fix "TypeError: 'NoneType' object is not iterable" for PPC and CRUX #2951
+ [hugovk]
+
+- Setup: Add libdirs for ppc64le and armv7l #2968
+ [nehaljwani]
+
+5.0.0 (2018-01-01)
+------------------
+
+- Docs: Added docstrings from documentation #2914
+ [radarhere]
+
+- Test: Switch from nose to pytest #2815
+ [hugovk]
+
+- Rework Source directory layout, preventing accidental import of PIL. #2911
+ [wiredfool]
+
+- Dynamically link libraqm #2753
+ [wiredfool]
+
+- Removed scripts directory #2901
+ [wiredfool]
+
+- TIFF: Run all compressed tiffs through libtiff decoder #2899
+ [wiredfool]
+
+- GIF: Add disposal option when saving GIFs #2902
+ [linnil1, wiredfool]
+
+- EPS: Allow for an empty line in EPS header data #2903
+ [radarhere]
+
+- PNG: Add support for sRGB and cHRM chunks, permit sRGB when no iCCP chunk present #2898
+ [wiredfool]
+
+- Dependencies: Update Tk Tcl to 8.6.8 #2905
+ [radarhere]
+
+- Decompression bomb error now raised for images 2x larger than a decompression bomb warning #2583
+ [wiredfool]
+
+- Test: avoid random failure in test_effect_noise #2894
+ [hugovk]
+
+- Increased epsilon for test_file_eps.py:test_showpage due to Arch update. #2896
+ [wiredfool]
+
+- Removed check parameter from _save in BmpImagePlugin, PngImagePlugin, ImImagePlugin, PalmImagePlugin, and PcxImagePlugin. #2873
+ [radarhere]
+
+- Make PngImagePlugin.add_text() zip argument type bool #2890
+ [jdufresne]
+
+- Depends: Updated libwebp to 0.6.1 #2880
+ [radarhere]
+
+- Remove unnecessary bool() calls in Image.registered_extensions and skipKnownBadTests #2891
+ [jdufresne]
+
+- Fix count of BITSPERSAMPLE items in broken TIFF files #2883
+ [homm]
+
+- Fillcolor parameter for Image.Transform #2852
+ [wiredfool]
+
+- Test: Display differences for test failures #2862
+ [wiredfool]
+
+- Added executable flag to file with shebang line #2884
+ [radarhere]
+
+- Setup: Specify compatible Python versions for pip #2877
+ [hugovk]
+
+- Dependencies: Updated libimagequant to 2.11.4 #2878
+ [radarhere]
+
+- Setup: Warn if trying to install for Py3.7 on Windows #2855
+ [hugovk]
+
+- Doc: Fonts can be loaded from a file-like object, not just filename #2861
+ [robin-norwood]
+
+- Add eog support for Ubuntu Image Viewer #2864
+ [NafisFaysal]
+
+- Test: Test on 3.7-dev on Travis CI #2870
+ [hugovk]
+
+- Dependencies: Update libtiff to 4.0.9 #2871
+ [radarhere]
+
+- Setup: Replace deprecated platform.dist with file existence check #2869
+ [wiredfool]
+
+- Build: Fix setup.py on Debian #2853
+ [wiredfool]
+
+- Docs: Correct error in ImageDraw documentation #2858
+ [meribold]
+
+- Test: Drop Ubuntu Precise, Fedora 24, Fedora 25, add Fedora 27, Centos 7, Amazon v2 CI Support #2854, #2843, #2895, #2897
+ [wiredfool]
+
+- Dependencies: Updated libimagequant to 2.11.3 #2849
+ [radarhere]
+
+- Test: Fix test_image.py to use tempfile #2841
+ [radarhere]
+
+- Replace PIL.OleFileIO deprecation warning with descriptive ImportError #2833
+ [hugovk]
+
+- WebP: Add support for animated WebP files #2761
+ [jd20]
+
+- PDF: Set encoderinfo for images when saving multi-page PDF. Fixes #2804. #2805
+ [ixio]
+
+- Allow the olefile dependency to be optional #2789
+ [jdufresne]
+
+- GIF: Permit LZW code lengths up to 12 bits in GIF decode #2813
+ [wiredfool]
+
+- Fix unterminated string and unchecked exception in _font_text_asBytes. #2825
+ [wiredfool]
+
+- PPM: Use fixed list of whitespace, rather relying on locale, fixes #272. #2831
+ [markmiscavage]
+
+- Added support for generators when using append_images #2829, #2835
+ [radarhere]
+
+- Doc: Correct PixelAccess.rst #2824
+ [hasahmed]
+
+- Depends: Update raqm to 0.3.0 #2822
+ [radarhere]
+
+- Docs: Link to maintained version of aggdraw #2809
+ [hugovk]
+
+- Include license file in the generated wheel packages #2801
+ [jdufresne]
+
+- Depends: Update openjpeg to 2.3.0 #2791
+ [radarhere]
+
+- Add option to Makefile to build and install with C coverage #2781
+ [hugovk]
+
+- Add context manager support to ImageFile.Parser and PngImagePlugin.ChunkStream #2793
+ [radarhere]
+
+- ImageDraw.textsize: fix zero length error #2788
+ [wiredfool, hugovk]
+
+4.3.0 (2017-10-02)
+------------------
+
+- Fix warning on pointer cast in isblock #2775, #2778
+ [cgohlke]
+
+- Doc: Added macOS High Sierra tested Pillow version #2777
+ [radarhere]
+
+- Use correct Windows handle type on 64 bit in imagingcms #2774
+ [cgohlke]
+
+- 64 Bit Windows fix for block storage #2773
+ [cgohlke]
+
+- Fix "expression result unused" warning #2764
+ [radarhere]
+
+- Add 16bit Read/Write and RLE read support to SgiImageFile #2769
+ [jbltx, wiredfool]
+
+- Block & array hybrid storage #2738
+ [homm]
+
+- Common seek frame position check #1849
+ [radarhere]
+
+- Doc: Add note about aspect ratio to Image thumbnail script #2281
+ [wilsonge]
+
+- Fix ValueError: invalid version number '1.0.0rc1' in scipy release candidate #2771
+ [cgohlke]
+
+- Unfreeze requirements.txt #2766
+ [hugovk]
+
+- Test: ResourceWarning tests #2756
+ [hugovk]
+
+- Use n_frames to determine is_animated if possible #2315
+ [radarhere]
+
+- Doc: Corrected parameters in documentation #2768
+ [radarhere]
+
+- Avoid unnecessary Image operations #1891
+ [radarhere]
+
+- Added register_extensions method #1860
+ [radarhere]
+
+- Fix TIFF support for I;16S, I;16BS, and I;32BS rawmodes #2748
+ [wiredfool]
+
+- Fixed doc syntax in ImageDraw #2752
+ [radarhere]
+
+- Fixed support for building on Windows/msys2. Added Appveyor CI coverage for python3 on msys2 #2746
+ [wiredfool]
+
+- Fix ValueError in Exif/Tiff IFD #2719
+ [wiredfool]
+
+- Use pathlib2 for Path objects on Python < 3.4 #2291
+ [asergi]
+
+- Export only required properties in unsafe_ptrs #2740
+ [homm]
+
+- Alpha composite fixes #2709
+ [homm]
+
+- Faster Transpose operations, added 'Transverse' option #2730
+ [homm]
+
+- Deprecate ImageOps undocumented functions gaussian_blur, gblur, unsharp_mask, usm and box_blur in favor of ImageFilter implementations #2735
+ [homm]
+
+- Dependencies: Updated freetype to 2.8.1 #2741
+ [radarhere]
+
+- Bug: Player skipped first image #2742
+ [radarhere]
+
+- Faster filter operations for Kernel, Gaussian, and Unsharp Mask filters #2679
+ [homm]
+
+- EPS: Add showpage to force rendering of some EPS images #2636
+ [kaplun]
+
+- DOC: Fix type of palette parameter in Image.quantize. #2703
+ [kkopachev]
+
+- DOC: Fix Ico docs to match code #2712
+ [hugovk]
+
+- Added file pointer save to SpiderImagePlugin #2647
+ [radarhere]
+
+- Add targa version 2 footer #2713
+ [jhultgre]
+
+- Removed redundant lines #2714
+ [radarhere]
+
+- Travis CI: Use default pypy/pypy3 #2721
+ [hugovk]
+
+- Fix for SystemError when rendering an empty string, added in 4.2.0 #2706
+ [wiredfool]
+
+- Fix for memory leaks in font handling added in 4.2.0 #2634
+ [wiredfool]
+
+- Tests: cleanup, more tests. Fixed WMF save handler #2689
+ [radarhere]
+
+- Removed debugging interface for Image.core.grabclipboard #2708
+ [radarhere]
+
+- Doc syntax fix #2710
+ [radarhere]
+
+- Faster packing and unpacking for RGB, LA, and related storage modes #2693
+ [homm]
+
+- Use RGBX rawmode for RGB JPEG images where possible #1989
+ [homm]
+
+- Remove palettes from non-palette modes in _new #2704
+ [wiredfool]
+
+- Delete transparency info when convert'ing RGB/L to RGBA #2633
+ [olt]
+
+- Code tweaks to ease type annotations #2687
+ [neiljp]
+
+- Fixed incorrect use of 's#' to byteslike object #2691
+ [wiredfool]
+
+- Fix JPEG subsampling labels for subsampling=2 #2698
+ [homm]
+
+- Region of interest (box) for resampling #2254
+ [homm]
+
+- Basic support for Termux (android) in setup.py #2684
+ [wiredfool]
+
+- Bug: Fix Image.fromarray for numpy.bool type. #2683
+ [wiredfool]
+
+- CI: Add Fedora 24 and 26 to Docker tests
+ [wiredfool]
+
+- JPEG: Fix ZeroDivisionError when EXIF contains invalid DPI (0/0). #2667
+ [vytisb]
+
+- Depends: Updated openjpeg to 2.2.0 #2669
+ [radarhere]
+
+- Depends: Updated Tk Tcl to 8.6.7 #2668
+ [radarhere]
+
+- Depends: Updated libimagequant to 2.10.2 #2660
+ [radarhere]
+
+- Test: Added test for ImImagePlugin tell() #2675
+ [radarhere]
+
+- Test: Additional tests for SGIImagePlugin #2659
+ [radarhere]
+
+- New Image.getchannel method #2661
+ [homm]
+
+- Remove unused im.copy2 and core.copy methods #2657
+ [homm]
+
+- Fast Image.merge() #2677
+ [homm]
+
+- Fast Image.split() #2676
+ [homm]
+
+- Fast image allocation #2655
+ [homm]
+
+- Storage cleanup #2654
+ [homm]
+
+- FLI: Use frame count from FLI header #2674
+ [radarhere]
+
+- Test: Test animated FLI file #2650
+ [hugovk]
+
+- Bug: Fixed uninitialized memory in bc5 decoding #2648
+ [ifeherva]
+
+- Moved SgiImagePlugin save error to before the start of write operations #2646
+ [radarhere]
+
+- Move createfontdatachunk.py so isn't installed globally #2645
+ [hugovk]
+
+- Bug: Fix unexpected keyword argument 'align' #2641
+ [hugovk]
+
+- Add newlines to error message for clarity #2640
+ [hugovk]
+
+- Docs: Updated redirected URL #2637
+ [radarhere]
+
+- Bug: Fix JPEG DPI when EXIF is invalid #2632
+ [wiredfool]
+
+- Bug: Fix for font getsize on empty string #2624
+ [radarhere]
+
+- Docs: Improved ImageDraw documentation #2626
+ [radarhere]
+
+- Docs: Corrected alpha_composite args documentation #2627
+ [radarhere]
+
+- Docs: added the description of the filename attribute to images.rst #2621
+ [dasdachs]
+
+- Dependencies: Updated libimagequant to 2.10.1 #2616
+ [radarhere]
+
+- PDF: Renamed parameter to not shadow built-in dict #2612
+ [kijeong]
+
+4.2.1 (2017-07-06)
+------------------
+
+- CI: Fix version specification and test on CI for PyPy/Windows #2608
+ [wiredfool]
+
+4.2.0 (2017-07-01)
+------------------
+
+- Doc: Clarified Image.save:append_images documentation #2604
+ [radarhere]
+
+- CI: Amazon Linux and Centos6 docker images added to Travis CI #2585
+ [wiredfool]
+
+- Image.alpha_composite added #2595
+ [wiredfool]
+
+- Complex Text Support #2576
+ [ShamsaHamed, Fahad-Alsaidi, wiredfool]
+
+- Added threshold parameter to ImageDraw.floodfill #2599
+ [nediamond]
+
+- Added dBATCH parameter to ghostscript command #2588
+ [radarhere]
+
+- JPEG: Adjust buffer size when icc_profile > MAXBLOCK #2596
+ [Darou]
+
+- Specify Pillow Version in one place #2517
+ [wiredfool]
+
+- CI: Change the owner of the TRAVIS_BUILD_DIR, fixing broken docker runs #2587
+ [wiredfool]
+
+- Fix truncated PNG loading for some images, Fix memory leak on truncated PNG images. #2541, #2598
+ [homm]
+
+- Add decompression bomb check to Image.crop #2410
+ [wiredfool]
+
+- ImageFile: Ensure that the ``err_code`` variable is initialized in case of exception. #2363
+ [alexkiro]
+
+- Tiff: Support append_images for saving multipage TIFFs #2406
+ [blochl]
+
+- Doc: Clarify that draft is only implemented for JPEG and PCD #2409
+ [wiredfool]
+
+- Test: MicImagePlugin #2447
+ [hugovk]
+
+- Use round() instead of floor() to eliminate zero coefficients in resample #2558
+ [homm]
+
+- Remove deprecated code #2549
+ [hugovk]
+
+- Added append_images to PDF saving #2526
+ [radarhere]
+
+- Remove unused function core image function new_array #2548
+ [hugovk]
+
+- Remove unnecessary calls to dict.keys() #2551
+ [jdufresne]
+
+- Add more ImageDraw.py tests and remove unused Draw.c code #2533
+ [hugovk]
+
+- Test: More tests for ImageMorph #2554
+ [hugovk]
+
+- Test: McIDAS area file #2552
+ [radarhere]
+
+- Update Feature Detection #2520
+ [wiredfool]
+
+- CI: Update pypy on Travis CI #2573
+ [hugovk]
+
+- ImageMorph: Fix wrong expected size of MRLs read from disk #2561
+ [dov]
+
+- Docs: Update install docs for FreeBSD #2546
+ [wiredfool]
+
+- Build: Ignore OpenJpeg 1.5 on FreeBSD #2544
+ [melvyn-sopacua]
+
+- Remove 'not yet implemented' methods from PIL 1.1.4 #2538
+ [hugovk]
+
+- Dependencies: Update FreeType to 2.8, LibTIFF to 4.0.8 and libimagequant to 2.9.1 #2535 #2537 #2540
+ [radarhere]
+
+- Raise TypeError and not also UnboundLocalError in ImageFile.Parser() #2525
+ [joshblum]
+
+- Test: Use Codecov for coverage #2528
+ [hugovk]
+
+- Use PNG for Image.show() #2527
+ [HinTak, wiredfool]
+
+- Remove WITH_DEBUG compilation flag #2522
+ [wiredfool]
+
+- Fix return value on parameter parse error in _webp.c #2521
+ [adw1n]
+
+- Set executable flag on scripts with shebang line #2295
+ [radarhere]
+
+- Flake8 #2460
+ [radarhere]
+
+- Doc: Release Process Changes #2516
+ [wiredfool]
+
+- CI: Added region for s3 deployment on appveyor #2515
+ [wiredfool]
+
+- Doc: Updated references to point to existing files #2507
+ [radarhere]
+
+- Return copy on Image crop if crop dimensions match the image #2471
+ [radarhere]
+
+- Test: Optimize CI speed #2464, #2466
+ [hugovk]
+
+4.1.1 (2017-04-28)
+------------------
+
+- Undef PySlice_GetIndicesEx, see https://bugs.python.org/issue29943 #2493
+ [cgohlke]
+
+- Fix for file with DPI in EXIF but not metadata, and XResolution is an int rather than tuple #2484
+ [hugovk]
+
+- Docs: Removed broken download counter badge #2487
+ [hugovk]
+
+- Docs: Fixed rst syntax error #2477
+ [thebjorn]
+
+4.1.0 (2017-04-03)
+------------------
+
+- Close files after loading if possible #2330
+ [homm, wiredfool]
+
+- Fix Image Access to be reloadable when embedding the Python interpreter #2296
+ [wiredfool, cgohlke]
+
+- Fetch DPI from EXIF if not specified in JPEG header #2449, #2472
+ [hugovk]
+
+- Removed winbuild checksum verification #2468
+ [radarhere]
+
+- Git: Set ContainerIO test file as binary #2469
+ [cgohlke]
+
+- Remove superfluous import of FixTk #2455
+ [cgohlke)
+
+- Fix import of tkinter/Tkinter #2456
+ [cgohlke)
+
+- Pure Python Decoders, including Python decoder to fix for MSP images #1938
+ [wiredfool, hugovk]
+
+- Reorganized GifImagePlugin, fixes #2314. #2374
+ [radarhere, wiredfool]
+
+- Doc: Reordered operating systems in Compatibility Matrix #2436
+ [radarhere]
+
+- Test: Additional tests for BufrStub, Eps, Container, GribStub, IPTC, Wmf, XVThumb, ImageDraw, ImageMorph, ImageShow #2425
+ [radarhere]
+
+- Health fixes #2437
+ [radarhere]
+
+- Test: Correctness tests ContainerIO, XVThumbImagePlugin, BufrStubImagePlugin, GribStubImagePlugin, FitsStubImagePlugin, Hdf5StubImagePlugin, PixarImageFile, PsdImageFile #2443, #2442, #2441, #2440, #2431, #2430, #2428, #2427
+ [hugovk]
+
+- Remove unused imports #1822
+ [radarhere]
+
+- Replaced KeyError catch with dictionary get method #2424
+ [radarhere]
+
+- Test: Removed unrunnable code in test_image_toqimage #2415
+ [hugovk]
+
+- Removed use of spaces in TIFF kwargs names, deprecated in 2.7 #1390
+ [radarhere]
+
+- Removed deprecated ImageDraw setink, setfill, setfont methods #2220
+ [jdufresne]
+
+- Send unwanted subprocess output to /dev/null #2253
+ [jdufresne]
+
+- Fix division by zero when creating 0x0 image from numpy array #2419
+ [hugovk]
+
+- Test: Added matrix convert tests #2381
+ [hugovk]
+
+- Replaced broken URL to partners.adobe.com #2413
+ [radarhere]
+
+- Removed unused private functions in setup.py and build_dep.py #2414
+ [radarhere]
+
+- Test: Fixed Qt tests for QT5 and saving 1 bit PNG #2394
+ [wiredfool]
+
+- Test: docker builds for Arch and Debian Stretch #2394
+ [wiredfool]
+
+- Updated libwebp to 0.6.0 on appveyor #2395
+ [radarhere]
+
+- More explicit error message when saving to a file with invalid extension #2399
+ [ces42]
+
+- Docs: Update some http urls to https #2403
+ [hugovk]
+
+- Preserve aux/alpha channels when performing Imagecms transforms #2355
+ [gunjambi]
+
+- Test linear and radial gradient effects #2382
+ [hugovk]
+
+- Test ImageDraw.Outline and and ImageDraw.Shape #2389
+ [hugovk]
+
+- Added PySide to ImageQt documentation #2392
+ [radarhere]
+
+- BUG: Empty image mode no longer causes a crash #2380
+ [evalapply]
+
+- Exclude .travis and contents from manifest #2386
+ [radarhere]
+
+- Remove 'MIT-like' from license #2145
+ [wiredfool]
+
+- Tests: Add tests for several Image operations #2379
+ [radarhere]
+
+- PNG: Moved iCCP chunk before PLTE chunk when saving as PNG, restricted chunks known value/ordering #2347
+ [radarhere]
+
+- Default to inch-interpretation for missing ResolutionUnit in TiffImagePlugin #2365
+ [lambdafu]
+
+- Bug: Fixed segfault when using ImagingTk on pypy Issue #2376, #2359.
+ [wiredfool]
+
+- Bug: Fixed Integer overflow using ImagingTk on 32 bit platforms #2359
+ [wiredfool, QuLogic]
+
+- Tests: Added docker images for testing alternate platforms. See also https://github.com/python-pillow/docker-images. #2368
+ [wiredfool]
+
+- Removed PIL 1.0 era TK readme that concerns Windows 95/NT #2360
+ [wiredfool]
+
+- Prevent ``nose -v`` printing docstrings #2369
+ [hugovk]
+
+- Replaced absolute PIL imports with relative imports #2349
+ [radarhere]
+
+- Added context managers for file handling #2307
+ [radarhere]
+
+- Expose registered file extensions in Image #2343
+ [iggomez, radarhere]
+
+- Make mode descriptor cache initialization thread-safe. #2351
+ [gunjambi]
+
+- Updated Windows test dependencies: Freetype 2.7.1, zlib 1.2.11 #2331, #2332, #2357
+ [radarhere]
+
+- Followed upstream pngquant packaging reorg to libimagquant #2354
+ [radarhere]
+
+- Fix invalid string escapes #2352
+ [hugovk]
+
+- Add test for crop operation with no argument #2333
+ [radarhere]
+
+4.0.0 (2017-01-01)
+------------------
+
+- Refactor out postprocessing hack to load_end in PcdImageFile
+ [wiredfool]
+
+- Add center and translate option to Image.rotate. #2328
+ [lambdafu]
+
+- Test: Relax WMF test condition, fixes #2323. #2327
+ [wiredfool]
+
+- Allow 0 size images, Fixes #2259, Reverts to pre-3.4 behavior. #2262
+ [wiredfool]
+
+- SGI: Save uncompressed SGI/BW/RGB/RGBA files #2325
+ [jbltx]
+
+- Depends: Updated pngquant to 2.8.2 #2319
+ [radarhere]
+
+- Test: Added correctness tests for opening SGI images #2324
+ [wiredfool]
+
+- Allow passing a list or tuple of individual frame durations when saving a GIF #2298
+ [Xdynix]
+
+- Unified different GIF optimize conditions #2196
+ [radarhere]
+
+- Build: Refactor dependency installation #2305
+ [hugovk]
+
+- Test: Add python 3.6 to travis, tox #2304
+ [hugovk]
+
+- Test: Fix coveralls coverage for Python+C #2300
+ [hugovk]
+
+- Remove executable bit and shebang from OleFileIO.py #2308
+ [jwilk, radarhere]
+
+- PyPy: Buffer interface workaround #2294
+ [wiredfool]
+
+- Test: Switch to Ubuntu Trusty 14.04 on Travis CI #2294
+
+- Remove vendored version of olefile Python package in favor of upstream #2199
+ [jdufresne]
+
+- Updated comments to use print as a function #2234
+ [radarhere]
+
+- Set executable flag on selftest.py, setup.py and added shebang line #2282, #2277
+ [radarhere, homm]
+
+- Test: Increase epsilon for FreeType 2.7 as rendering is slightly different. #2286
+ [hugovk]
+
+- Test: Faster assert_image_similar #2279
+ [homm]
+
+- Removed deprecated internal "stretch" method #2276
+ [homm]
+
+- Removed the handles_eof flag in decode.c #2223
+ [wiredfool]
+
+- Tiff: Fix for writing Tiff to BytesIO using libtiff #2263
+ [wiredfool]
+
+- Doc: Design docs #2269
+ [wiredfool]
+
+- Test: Move tests requiring libtiff to test_file_libtiff #2273
+ [wiredfool]
+
+- Update Maxblock heuristic #2275
+ [wiredfool]
+
+- Fix for 2-bit palette corruption #2274
+ [pdknsk, wiredfool]
+
+- Tiff: Update info.icc_profile when using libtiff reader. #2193
+ [lambdafu]
+
+- Test: Fix bug in test_ifd_rational_save when libtiff is not available #2270
+ [ChristopherHogan]
+
+- ICO: Only save relevant sizes #2267
+ [hugovk]
+
+- ICO: Allow saving .ico files of 256x256 instead of 255x255 #2265
+ [hugovk]
+
+- Fix TIFFImagePlugin ICC color profile saving. #2087
+ [cskau]
+
+- Doc: Improved description of ImageOps.deform resample parameter #2256
+ [radarhere]
+
+- EMF: support negative bounding box coordinates #2249
+ [glexey]
+
+- Close file if opened in WalImageFile #2216
+ [radarhere]
+
+- Use Image._new() instead of _makeself() #2248
+ [homm]
+
+- SunImagePlugin fixes #2241
+ [wiredfool]
+
+- Use minimal scale for jpeg drafts #2240
+ [homm]
+
+- Updated dependency scripts to use FreeType 2.7, OpenJpeg 2.1.2, WebP 0.5.2 and Tcl/Tk 8.6.6 #2235, #2236, #2237, #2290, #2302
+ [radarhere]
+
+- Fix "invalid escape sequence" bytestring warnings in Python 3.6 #2186
+ [timgraham]
+
+- Removed support for Python 2.6 and Python 3.2 #2192
+ [jdufresne]
+
+- Setup: Raise custom exceptions when required/requested dependencies are not found #2213
+ [wiredfool]
+
+- Use a context manager in FontFile.save() to ensure file is always closed #2226
+ [jdufresne]
+
+- Fixed bug in saving to fp-objects in Python >= 3.4 #2227
+ [radarhere]
+
+- Use a context manager in ImageFont._load_pilfont() to ensure file is always closed #2232
+ [jdufresne]
+
+- Use generator expressions instead of list comprehension #2225
+ [jdufresne]
+
+- Close file after reading in ImagePalette.load() #2215
+ [jdufresne]
+
+- Changed behaviour of default box argument for paste method to match docs #2211
+ [radarhere]
+
+- Add support for another BMP bitfield #2221
+ [jmerdich]
+
+- Added missing top-level test __main__ #2222
+ [radarhere]
+
+- Replaced range(len()) #2197
+ [radarhere]
+
+- Fix for ImageQt Segfault, fixes #1370 #2182
+ [wiredfool]
+
+- Setup: Close file in setup.py after finished reading #2208
+ [jdufresne]
+
+- Setup: optionally use pkg-config (when present) to detect dependencies #2074
+ [garbas]
+
+- Search for tkinter first in builtins #2210
+ [matthew-brett]
+
+- Tests: Replace try/except/fail pattern with TestCase.assertRaises() #2200
+ [jdufresne]
+
+- Tests: Remove unused, open files at top level of tests #2188
+ [jdufresne]
+
+- Replace type() equality checks with isinstance #2184
+ [jdufresne]
+
+- Doc: Move ICO out of the list of read-only file formats #2180
+ [alexwlchan]
+
+- Doc: Fix formatting, too-short title underlines and malformed table #2175
+ [hugovk]
+
+- Fix BytesWarnings #2172
+ [jdufresne]
+
+- Use Integer division to eliminate deprecation warning. #2168
+ [mastermatt]
+
+- Doc: Update compatibility matrix
+ [daavve, wiredfool]
+
+
+3.4.2 (2016-10-18)
+------------------
+
+- Fix Resample coefficient calculation #2162
+ [homm]
+
+
+3.4.1 (2016-10-04)
+------------------
+
+- Allow lists as arguments for Image.new() #2149
+ [homm]
+
+- Fix fix for map.c overflow #2151 (also in 3.3.3)
+ [wiredfool]
+
+3.4.0 (2016-10-03)
+------------------
+
+- Removed Image.core.open_ppm, added negative image size checks in Image.py. #2146
+ [wiredfool]
+
+- Windows build: fetch dependencies from pillow-depends #2095
+ [hugovk]
+
+- Add TIFF save_all writer. #2140
+ [lambdafu, vashek]
+
+- Move libtiff fd duplication to _load_libtiff #2141
+ [sekrause]
+
+- Speed up GIF save optimization step, fixes #2093. #2133
+ [wiredfool]
+
+- Fix for ImageCms Segfault, Issue #2037. #2131
+ [wiredfool]
+
+- Make Image.crop an immediate operation, not lazy. #2138
+ [wiredfool]
+
+- Skip empty values in ImageFileDirectory #2024
+ [homm]
+
+- Force reloading palette when using mmap in ImageFile. #2139
+ [lambdafu]
+
+- Fix "invalid escape sequence" warning in Python 3.6 #2136
+ [timgraham]
+
+- Update documentation about drafts #2137
+ [radarhere]
+
+- Converted documentation parameter format, comments to docstrings #2021
+ [radarhere]
+
+- Fixed typos #2128 #2142
+ [radarhere]
+
+- Renamed references to OS X to macOS #2125 2130
+ [radarhere]
+
+- Use truth value when checking for progressive and optimize option on save #2115, #2129
+ [radarhere]
+
+- Convert DPI to ints when saving as JPEG #2102
+ [radarhere]
+
+- Added append_images parameter to GIF saving #2103
+ [radarhere]
+
+- Speedup paste with masks up to 80% #2015
+ [homm]
+
+- Rewrite DDS decoders in C, add DXT3 and BC7 decoders #2068
+ [Mischanix]
+
+- Fix PyArg_ParseTuple format in getink() #2070
+ [arjennienhuis]
+
+- Fix saving originally missing TIFF tags. #2111
+ [anntzer]
+
+- Allow pathlib.Path in Image.open on Python 2.7 #2110
+ [patricksnape]
+
+- Use modern base64 interface over deprecated #2121
+ [hugovk]
+
+- ImageColor.getrgb hexadecimal RGBA #2114
+ [homm]
+
+- Test fix for bigendian machines #2092
+ [wiredfool]
+
+- Resampling lookups, trailing empty coefficients, precision #2008
+ [homm]
+
+- Add (un)packing between RGBA and BGRa #2057
+ [arjennienhuis]
+
+- Added return for J2k (and fpx) Load to return a pixel access object #2061
+ [wiredfool]
+
+- Skip failing numpy tests on Pypy <= 5.3.1 #2090
+ [arjennienhuis]
+
+- Show warning when trying to save RGBA image as JPEG #2010
+ [homm]
+
+- Respect pixel centers during transform #2022
+ [homm]
+
+- TOC for supported file formats #2056
+ [polarize]
+
+- Fix conversion of bit images to numpy arrays Fixes #350, #2058
+ [matthew-brett]
+
+- Add ImageOps.scale to expand or contract a PIL image by a factor #2011
+ [vlmath]
+
+- Flake8 fixes #2050
+ [hugovk]
+
+- Updated freetype to 2.6.5 on Appveyor builds #2035
+ [radarhere]
+
+- PCX encoder fixes #2023, pr #2041
+ [homm]
+
+- Docs: Windows console prompts are > #2031
+ [techtonik]
+
+- Expose Pillow package version as PIL.__version__ #2027
+ [techtonik]
+
+- Add Box and Hamming filters for resampling #1959
+ [homm]
+
+- Retain a reference to core image object in PyAccess #2009
+ [homm]
+
+3.3.3 (2016-10-04)
+------------------
+
+- Fix fix for map.c overflow #2151
+ [wiredfool]
+
+3.3.2 (2016-10-03)
+------------------
+
+- Fix negative image sizes in Storage.c #2146
+ [wiredfool]
+
+- Fix integer overflow in map.c #2146
+ [wiredfool]
+
+3.3.1 (2016-08-18)
+------------------
+
+- Fix C90 compilation error for Tcl / Tk rewrite #2033
+ [matthew-brett]
+
+- Fix image loading when rotating by 0 deg #2052
+ [homm]
+
+3.3.0 (2016-07-01)
+------------------
+
+- Fixed enums for Resolution Unit and Predictor in TiffTags.py #1998
+ [wiredfool]
+
+- Fix issue converting P mode to LA #1986
+ [didrix]
+
+- Moved test_j2k_overflow to check_j2k_overflow, prevent DOS of our 32bit testing machines #1995
+ [wiredfool]
+
+- Skip CRC checks in PNG files when LOAD_TRUNCATED_IMAGES is enabled #1991
+ [kkopachev]
+
+- Added CMYK mode for opening EPS files #1826
+ [radarhere]
+
+- Docs: OSX build instruction clarification #1994
+ [wiredfool]
+
+- Docs: Filter comparison table #1993
+ [homm]
+
+- Removal of pthread based Incremental.c, new interface for file decoders/encoders to access the python file. Fixes assorted J2k Hangs. #1934
+ [wiredfool]
+
+- Skip unnecessary passes when resizing #1954
+ [homm]
+
+- Removed duplicate code in ImagePalette #1832
+ [radarhere]
+
+- test_imagecms: Reduce precision of extended info due to 32 bit machine precision #1990
+ [AbdealiJK]
+
+- Binary Tiff Metadata/ICC profile. #1988
+ [wiredfool]
+
+- Ignore large text blocks in PNG if LOAD_TRUNCATED_IMAGES is enabled #1970
+ [homm]
+
+- Replace index = index+1 in docs with +=1
+ [cclauss]
+
+- Skip extra 0xff00 in jpeg #1977
+ [kkopachev]
+
+- Use bytearray for palette mutable storage #1985
+ [radarhere, wiredfool]
+
+- Added additional uint modes for Image.fromarray, more extensive tests of fromarray #1984
+ [mairsbw, wiredfool]
+
+- Fix for program importing PyQt4 when PyQt5 also installed #1942
+ [hugovk]
+
+- Changed depends/install_*.sh urls to point to github pillow-depends repo #1983
+ [wiredfool]
+
+- Allow ICC profile from ``encoderinfo`` while saving PNGs #1909
+ [homm]
+
+- Fix integer overflow on ILP32 systems (32-bit Linux). #1975
+ [lambdafu]
+
+- Change function declaration to match Tcl_CmdProc type #1966
+ [homm]
+
+- Integer overflow checks on all calls to \*alloc #1781
+ [wiredfool]
+
+- Change equals method on Image so it short circuits #1967
+ [mattBoros]
+
+- Runtime loading of TCL/TK libraries, eliminating build time dependency. #1932
+ [matthew-brett]
+
+- Cleanup of transform methods #1941
+ [homm]
+
+- Fix "Fatal Python error: UNREF invalid object" in debug builds #1936
+ [wiredfool]
+
+- Setup fixes for Alpine linux #1937
+ [wiredfool]
+
+- Split resample into horizontal + vertical passes #1933
+ [homm]
+
+- Box blur with premultiplied alpha #1914
+ [homm]
+
+- Add libimagequant support in quantize() #1889
+ [rr-]
+
+- Added internal Premultiplied luminosity (La) mode #1912
+ [homm]
+
+- Fixed point integer resample #1881
+ [homm]
+
+- Removed docs/BUILDME script #1924
+ [radarhere]
+
+- Moved comments to docstrings #1926
+ [hugovk]
+
+- Include Python.h before wchar.h so _GNU_SOURCE is set consistently #1906
+ [hugovk]
+
+- Updated example decoder in documentation #1899
+ [radarhere]
+
+- Added support for GIF comment extension #1896
+ [radarhere]
+
+- Removed support for pre- 1.5.2 list form of Image info in Image.new #1897
+ [radarhere]
+
+- Fix typos in TIFF tags #1918
+ [radarhere]
+
+- Skip tests that require libtiff if it is not installed #1893 (fixes #1866)
+ [wiredfool]
+
+- Skip test when icc profile is not available, fixes #1887. #1892
+ [doko42]
+
+- Make deprecated functions raise NotImplementedError instead of Exception. #1862, #1890
+ [daniel-leicht, radarhere]
+
+- Replaced os.system with subprocess.call in setup.py #1879
+ [radarhere]
+
+- Corrected Image show documentation #1886
+ [radarhere]
+
+- Added check for executable permissions to ImageShow #1880
+ [radarhere]
+
+- Fixed tutorial code and added explanation #1877
+ [radarhere]
+
+- Added OS X support for ImageGrab grabclipboard #1837
+ [radarhere]
+
+- Combined duplicate code in ImageTk #1856
+ [radarhere]
+
+- Added --disable-platform-guessing option to setup.py build extension #1861
+ [angeloc]
+
+- Fixed loading Transparent PNGs with a transparent black color #1840
+ [olt]
+
+- Add support for LA mode in Image.fromarray #1865
+ [pierriko]
+
+- Make ImageFile load images in read-only mode #1864
+ [hdante]
+
+- Added _accept hook for XVThumbImagePlugin #1853
+ [radarhere]
+
+- Test TIFF with LZW compression #1855, TGA RLE file #1854
+ [hugovk]
+
+- Improved SpiderImagePlugin help text #1863
+ [radarhere]
+
+- Updated Sphinx project description #1870
+ [radarhere]
+
+- Remove support for Python 3.0 from _imaging.c #1851
+ [radarhere]
+
+- Jpeg qtables are unsigned chars #1814, #1921
+ [thebostik]
+
+- Added additional EXIF tags #1841, TIFF Tags #1821
+ [radarhere]
+
+- Changed documentation to refer to ImageSequence Iterator #1833
+ [radarhere]
+
+- Fix Fedora prerequisites in installation docs, depends script #1842
+ [living180]
+
+- Added _accept hook for PixarImagePlugin #1843
+ [radarhere]
+
+- Removed outdated scanner classifier #1823
+ [radarhere]
+
+- Combined identical error messages in _imaging #1825
+ [radarhere]
+
+- Added debug option for setup.py to trace header and library finding #1790
+ [wiredfool]
+
+- Fix doc building on travis #1820, #1844
+ [wiredfool]
+
+- Fix for DIB/BMP images #1813, #1847
+ [wiredfool]
+
+- Add PixarImagePlugin file extension #1809
+ [radarhere]
+
+- Catch struct.errors when verifying png files #1805
+ [wiredfool]
+
+- SpiderImagePlugin: raise an error when seeking in a non-stack file #1794
+ [radarhere, jmichalon]
+
+- Added support for 2/4 bpp Tiff grayscale images #1789
+ [zwhfly]
+
+- Removed unused variable from selftest #1788
+ [radarhere]
+
+- Added warning for as_dict method (deprecated in 3.0.0) #1799
+ [radarhere]
+
+- Removed powf support for older Python versions #1784
+ [radarhere]
+
+- Health fixes #1625 #1903
+ [radarhere]
+
+3.2.0 (2016-04-01)
+------------------
+
+- Added install docs for Fedora 23 and FreeBSD #1729, #1739, #1792
+ [koobs, zandermartin, wiredfool]
+
+- Fixed TIFF multiframe load when the frames have different compression types #1782
+ [radarhere, geka000]
+
+- Added __copy__ method to Image #1772
+ [radarhere]
+
+- Updated dates in PIL license in OleFileIO README #1787
+ [radarhere]
+
+- Corrected Tiff tag names #1786
+ [radarhere]
+
+- Fixed documented name of JPEG property #1783
+ [radarhere]
+
+- Fixed UnboundLocalError when loading a corrupt jpeg2k file #1780
+ [wiredfool]
+
+- Fixed integer overflow in path.c #1773
+ [wiredfool, nedwill]
+
+- Added debug to command line help text for pilprint #1766
+ [radarhere]
+
+- Expose many more fields in ICC Profiles #1756
+ [lambdafu]
+
+- Documentation changes, URL update, transpose, release checklist
+ [radarhere]
+
+- Fixed saving to nonexistant files specified by pathlib.Path objects #1748 (fixes #1747)
+ [radarhere]
+
+- Round Image.crop arguments to the nearest integer #1745 (fixes #1744)
+ [hugovk]
+
+- Fix uninitialized variable warning in _imaging.c:getink #1663 (fixes #486)
+ [wiredfool]
+
+- Disable multiprocessing install on cygwin #1700 (fixes #1690)
+ [wiredfool]
+
+- Fix the error reported when libz is not found #1764
+ [wiredfool]
+
+- More general error check to avoid Symbol not found: _PyUnicodeUCS2_AsLatin1String on OS X #1761
+ [wiredfool]
+
+- Added py35 to tox envlist #1724
+ [radarhere]
+
+- Fix EXIF tag name typos #1736
+ [zarlant, radarhere]
+
+- Updated freetype to 2.6.3, Tk/Tcl to 8.6.5 and 8.5.19 #1725, #1752
+ [radarhere]
+
+- Add a loader for the FTEX format from Independence War 2: Edge of Chaos #1688
+ [jleclanche]
+
+- Improved alpha_composite documentation #1698
+ [radarhere]
+
+- Extend ImageDraw.text method to pass on multiline_text method specific arguments #1647
+ [radarhere]
+
+- Allow ImageSequence to seek to zero #1686
+ [radarhere]
+
+- ImageSequence Iterator is now an iterator #1649
+ [radarhere]
+
+- Updated windows test builds to jpeg9b #1673
+ [radarhere]
+
+- Fixed support for .gbr version 1 images, added support for version 2 in GbrImagePlugin #1653
+ [wiredfool]
+
+- Clarified which YCbCr format is used #1677
+ [radarhere]
+
+- Added TiffTags documentation, Moved windows build documentation to winbuild/ #1667
+ [wiredfool]
+
+- Add tests for OLE file based formats #1678
+ [radarhere]
+
+- Add TIFF IFD test #1671
+ [radarhere]
+
+- Add a basic DDS image plugin with more tests #1654
+ [jleclanche, hugovk, wiredfool]
+
+- Fix incorrect conditional in encode.c #1638
+ [manisandro]
+
+
+3.1.2 (2016-04-01)
+------------------
+
+- Fixed an integer overflow in Jpeg2KEncode.c causing a buffer overflow. CVE-2016-3076
+ [wiredfool]
+
+3.1.1 (2016-02-04)
+------------------
+
+- Fixed an integer overflow in Resample.c causing writes in the Python heap.
+ [nedwill]
+
+- Fixed a buffer overflow in PcdDecode.c causing a segfault when opening PhotoCD files. CVE-2016-2533
+ [wiredfool]
+
+- Fixed a buffer overflow in FliDecode.c causing a segfault when opening FLI files. CVE-2016-0775
+ [wiredfool]
+
+- Fixed a buffer overflow in TiffDecode.c causing an arbitrary amount of memory to be overwritten when opening a specially crafted invalid TIFF file. CVE-2016-0740
+ [wiredfool]
+
+
+3.1.0 (2016-01-04)
+------------------
+
+- Fixing test failures on Python 2.6/Windows #1633
+ [wiredfool]
+
+- Limit metadata tags when writing using libtiff #1620
+ [wiredfool]
+
+- Rolling back exif support to pre-3.0 format #1627
+ [wiredfool]
+
+- Fix Divide by zero in Exif, add IFDRational class #1531
+ [wiredfool]
+
+- Catch the IFD error near the source #1622
+ [wiredfool]
+
+- Added release notes for 3.1.0 #1623
+ [radarhere]
+
+- Updated spacing to be consistent between multiline methods #1624
+ [radarhere]
+
+- Let EditorConfig take care of some basic formatting #1489
+ [hugovk]
+
+- Restore gpsexif data to the v1 form #1619
+ [wiredfool]
+
+- Add /usr/local include and library directories for freebsd #1613
+ [leforestier]
+
+- Updated installation docs for new versions of dependencies #1611
+ [radarhere]
+
+- Removed unrunnable test file #1610
+ [radarhere]
+
+- Changed register calls to use format property #1608
+ [radarhere]
+
+- Added field type constants to TiffTags #1596
+ [radarhere]
+
+- Allow saving RowsPerStrip with libtiff #1594
+ [wiredfool]
+
+- Enabled conversion to numpy array for HSV images #1578
+ [cartisan]
+
+- Changed some urls in the docs to use https #1580
+ [hugovk]
+
+- Removed logger.exception from ImageFile.py #1590
+ [radarhere]
+
+- Removed warnings module check #1587
+ [radarhere]
+
+- Changed arcs, chords and pie slices to use floats #1577
+ [radarhere]
+
+- Update unit test asserts #1584, #1598
+ [radarhere]
+
+- Fix command to invoke ghostscript for eps files #1478
+ [baumatron, radarhere]
+
+- Consistent multiline text spacing #1574
+ [wiredfool, hugovk]
+
+- Removed unused lines in BDFFontFile #1530
+ [radarhere]
+
+- Changed ImageQt import of Image #1560
+ [radarhere, ericfrederich]
+
+- Throw TypeError if no cursors were found in .cur file #1556
+ [radarhere]
+
+- Fix crash in ImageTk.PhotoImage on win-amd64 #1553
+ [cgohlke]
+
+- ExtraSamples tag should be a SHORT, not a BYTE #1555
+ [Nexuapex]
+
+- Docs and code health fixes #1565 #1566 #1581 #1586 #1591 #1621
+ [radarhere]
+
+- Updated freetype to 2.6.2 #1564
+ [radarhere]
+
+- Updated WebP to 0.5.0 for Travis #1515 #1609
+ [radarhere]
+
+- Fix missing 'version' key value in __array_interface__ #1519
+ [mattip]
+
+- Replaced os.popen with subprocess.Popen to pilprint script #1523
+ [radarhere]
+
+- Catch OverflowError in SpiderImagePlugin #1545
+ [radarhere, MrShark]
+
+- Fix the definition of icc_profile in TiffTags #1539
+ [wiredfool]
+
+- Remove old _imagingtiff.c and pilplus stuff #1499
+ [hugovk]
+
+- Fix Exception when requiring jpeg #1501
+ [hansmosh]
+
+- Dependency scripts for Debian and Ubuntu #1486
+ [wiredfool]
+
+- Added Usage message to painter script #1482
+ [radarhere]
+
+- Add tag info for iccprofile, fixes #1462. #1465
+ [wiredfool]
+
+- Added some requirements for make release-test #1451
+ [wiredfool]
+
+- Flatten tiff metadata value SAMPLEFORMAT to initial value #1467 (fixes #1466)
+ [wiredfool]
+
+- Fix handling of pathlib in Image.save #1464 (fixes #1460)
+ [wiredfool]
+
+- Make tests more robust #1469
+ [hugovk]
+
+- Use correctly sized pointers for windows handle types #1458
+ [nu744]
+
+3.0.0 (2015-10-01)
+------------------
+
+- Check flush method existence for file-like object #1398
+ [mrTable, radarhere]
+
+- Added PDF multipage saving #1445
+ [radarhere]
+
+- Removed deprecated code, Image.tostring, Image.fromstring, Image.offset, ImageDraw.setink, ImageDraw.setfill, ImageFileIO, ImageFont.FreeTypeFont and ImageFont.truetype ``file`` kwarg, ImagePalette private _make functions, ImageWin.fromstring and ImageWin.tostring #1343
+ [radarhere]
+
+- Load more broken images #1428
+ [homm]
+
+- Require zlib and libjpeg #1439
+ [wiredfool]
+
+- Preserve alpha when converting from a QImage to a Pillow Image by using png instead of ppm #1429
+ [ericfrederich]
+
+- Qt needs 32 bit aligned image data #1430
+ [ericfrederich]
+
+- Tiff ImageFileDirectory rewrite #1419
+ [anntzer, wiredfool, homm]
+
+- Removed spammy debug logging #1423
+ [wiredfool]
+
+- Save as GiF89a with support for animation parameters #1384
+ [radarhere]
+
+- Correct convert matrix docs #1426
+ [wiredfool]
+
+- Catch TypeError in _getexif #1414
+ [radarhere, wiredfool]
+
+- Fix for UnicodeDecodeError in TiffImagePlugin #1416
+ [bogdan199, wiredfool]
+
+- Dedup code in image.open #1415
+ [wiredfool]
+
+- Skip any number extraneous chars at the end of JPEG chunks #1337
+ [homm]
+
+- Single threaded build for pypy3, refactor #1413
+ [wiredfool]
+
+- Fix loading of truncated images with LOAD_TRUNCATED_IMAGES enabled #1366
+ [homm]
+
+- Documentation update for concepts: bands #1406
+ [merriam]
+
+- Add Solaris/SmartOS include and library directories #1356
+ [njones11]
+
+- Improved handling of getink color #1387
+ [radarhere]
+
+- Disable compiler optimizations for topalette and tobilevel functions for all msvc versions #1402 (fixes #1357)
+ [cgohlke]
+
+- Skip ImageFont_bitmap test if _imagingft C module is not installed #1409
+ [homm]
+
+- Add param documentation to ImagePalette #1381
+ [bwrsandman]
+
+- Corrected scripts path #1407
+ [radarhere]
+
+- Updated libtiff to 4.0.6 #1405, #1421
+ [radarhere]
+
+- Updated Platform Support for Yosemite #1403
+ [radarhere]
+
+- Fixed infinite loop on truncated file #1401
+ [radarhere]
+
+- Check that images are L mode in ImageMorph methods #1400
+ [radarhere]
+
+- In tutorial of pasting images, add to mask text #1389
+ [merriam]
+
+- Style/health fixes #1391, #1397, #1417, #1418
+ [radarhere]
+
+- Test on Python 3.5 dev and 3.6 nightly #1361
+ [hugovk]
+
+- Fix fast rotate operations #1373
+ [radarhere]
+
+- Added support for pathlib Path objects to open and save #1372
+ [radarhere]
+
+- Changed register calls to use format property #1333
+ [radarhere]
+
+- Added support for ImageGrab.grab to OS X #1367, #1443
+ [radarhere, hugovk]
+
+- Fixed PSDraw stdout Python 3 compatibility #1365
+ [radarhere]
+
+- Added Python 3.3 to AppVeyor #1363
+ [radarhere]
+
+- Treat MPO with unknown header as base JPEG file #1350
+ [hugovk, radarhere]
+
+- Added various tests #1330, #1344
+ [radarhere]
+
+- More ImageFont tests #1327
+ [hugovk]
+
+- Use logging instead of print #1207
+ [anntzer]
+
+2.9.0 (2015-07-01)
+------------------
+
+- Added test for GimpPaletteFile #1324
+ [radarhere]
+
+- Merged gifmaker script to allow saving of multi-frame GIF images #1320
+ [radarhere]
+
+- Added is_animated property to multi-frame formats #1319
+ [radarhere]
+
+- Fixed ValueError in Python 2.6 #1315 #1316
+ [cgohlke, radarhere]
+
+- Fixed tox test script path #1308
+ [radarhere]
+
+- Added width and height properties #1304
+ [radarhere]
+
+- Update tiff and tk tcl 8.5 versions #1303
+ [radarhere, wiredfool]
+
+- Add functions to convert: Image <-> QImage; Image <-> QPixmap #1217
+ [radarhere, rominf]
+
+- Remove duplicate code in gifmaker script #1294
+ [radarhere]
+
+- Multiline text in ImageDraw #1177
+ [allo-, radarhere]
+
+- Automated Windows CI/build support #1278
+ [wiredfool]
+
+- Removed support for Tk versions earlier than 8.4 #1288
+ [radarhere]
+
+- Fixed polygon edge drawing #1255 (fixes #1252)
+ [radarhere]
+
+- Check prefix length in _accept methods #1267
+ [radarhere]
+
+- Register MIME type for BMP #1277
+ [coldmind]
+
+- Adjusted ImageQt use of unicode() for 2/3 compatibility #1218
+ [radarhere]
+
+- Identify XBM file created with filename including underscore #1230 (fixes #1229)
+ [hugovk]
+
+- Copy image when saving in GifImagePlugin #1231 (fixes #718)
+ [radarhere]
+
+- Removed support for FreeType 2.0 #1247
+ [radarhere]
+
+- Added background saving to GifImagePlugin #1273
+ [radarhere]
+
+- Provide n_frames attribute to multi-frame formats #1261
+ [anntzer, radarhere]
+
+- Add duration and loop set to GifImagePlugin #1172, #1269
+ [radarhere]
+
+- Ico files are little endian #1232
+ [wiredfool]
+
+- Upgrade olefile from 0.30 to 0.42b #1226
+ [radarhere, decalage2]
+
+- Setting transparency value to 0 when the tRNS contains only null byte(s) #1239
+ [juztin]
+
+- Separated out feature checking from selftest #1233
+ [radarhere]
+
+- Style/health fixes
+ [radarhere]
+
+- Update WebP from 0.4.1 to 0.4.3 #1235
+ [radarhere]
+
+- Release GIL during image load (decode) #1224
+ [lkesteloot]
+
+- Added icns save #1185
+ [radarhere]
+
+- Fix putdata memory leak #1196
+ [benoit-pierre]
+
+- Keep user-specified ordering of icon sizes #1193
+ [karimbahgat]
+
+- Tiff: allow writing floating point tag values #1113
+ [bpedersen2]
+
+2.8.2 (2015-06-06)
+------------------
+
+- Bug fix: Fixed Tiff handling of bad EXIF data
+ [radarhere]
+
+2.8.1 (2015-04-02)
+------------------
+
+- Bug fix: Catch struct.error on invalid JPEG, fixes #1163. #1165
+ [wiredfool, hugovk]
+
+2.8.0 (2015-04-01)
+------------------
+
+- Fix 32-bit BMP loading (RGBA or RGBX) #1125
+ [artscoop]
+
+- Fix UnboundLocalError in ImageFile #1131
+ [davarisg]
+
+- Re-enable test image caching #982
+ [hugovk, homm]
+
+- Fix: Cannot identify EPS images #1152 (fixes #1104)
+ [hugovk]
+
+- Configure setuptools to run nosetests, fixes #729
+ [aclark4life]
+
+- Style/health fixes
+ [radarhere, hugovk]
+
+- Add support for HTTP response objects to Image.open() #1151
+ [mfitzp]
+
+- Improve reference docs for PIL.ImageDraw.Draw.pieslice() #1145
+ [audreyr]
+
+- Added copy method font_variant() and accessible properties to truetype() #1123
+ [radarhere]
+
+- Fix ImagingEffectNoise #1128
+ [hugovk]
+
+- Remove unreachable code #1126
+ [hugovk]
+
+- Let Python do the endian stuff + tests #1121
+ [amoibos, radarhere]
+
+- Fix webp decode memory leak #1114
+ [benoit-pierre]
+
+- Fast path for opaque pixels in RGBa unpacker #1088
+ [bgilbert]
+
+- Enable basic support for 'RGBa' raw encoding/decoding #1096
+ [immerrr]
+
+- Fix pickling L mode images with no palette, #1095
+ [hugovk]
+
+- iPython display hook #1091
+ [wiredfool]
+
+- Adjust buffer size when quality=keep #1079 (fixes #148 again)
+ [wiredfool]
+
+- Fix for corrupted bitmaps embedded in truetype fonts #1072
+ [jackyyf, wiredfool]
+
+2.7.0 (2015-01-01)
+------------------
+
+- Split Sane into a separate repo: https://github.com/python-pillow/Sane
+ [hugovk]
+
+- Look for OS X and Linux fonts in common places #1054
+ [charleslaw]
+
+- Fix CVE-2014-9601, potential PNG decompression DOS #1060
+ [wiredfool]
+
+- Use underscores, not spaces, in TIFF tag kwargs #1044, #1058
+ [anntzer, hugovk]
+
+- Update PSDraw for Python3, add tests #1055
+ [hugovk]
+
+- Use Bicubic filtering by default for thumbnails. Don't use Jpeg Draft mode for thumbnails #1029
+ [homm]
+
+- Fix MSVC compiler error: Use Py_ssize_t instead of ssize_t #1051
+ [cgohlke]
+
+- Fix compiler error: MSVC needs variables defined at the start of the block #1048
+ [cgohlke]
+
+- The GIF Palette optimization algorithm is only applicable to mode='P' or 'L' #993
+ [moriyoshi]
+
+- Use PySide as an alternative to PyQt4/5 #1024
+ [holg]
+
+- Replace affine-based im.resize implementation with convolution-based im.stretch #997
+ [homm]
+
+- Replace Gaussian Blur implementation with iterated fast box blur. #961 Note: Radius parameter is interpreted differently than before.
+ [homm]
+
+- Better docs explaining import _imaging failure #1016, build #1017, mode #1018, PyAccess, PixelAccess objects #1019 Image.quantize #1020 and Image.save #1021
+ [wiredfool]
+
+- Fix for saving TIFF image into an io.BytesIO buffer #1011
+ [mfergie]
+
+- Fix antialias compilation on debug versions of Python #1010
+ [wiredfool]
+
+- Fix for Image.putdata segfault #1009
+ [wiredfool]
+
+- Ico save, additional tests #1007
+ [exherb]
+
+- Use PyQt4 if it has already been imported, otherwise prefer PyQt5 #1003
+ [AurelienBallier]
+
+- Speedup resample implementation up to 2.5 times #977
+ [homm]
+
+- Speed up rotation by using cache aware loops, added transpose to rotations #994
+ [homm]
+
+- Fix Bicubic interpolation #970
+ [homm]
+
+- Support for 4-bit greyscale TIFF images #980
+ [hugovk]
+
+- Updated manifest #957
+ [wiredfool]
+
+- Fix PyPy 2.4 regression #958
+ [wiredfool]
+
+- Webp Metadata Skip Test comments #954
+ [wiredfool]
+
+- Fixes for things rpmlint complains about #942
+ [manisandro]
+
+2.6.2 (2015-01-01)
+------------------
+
+- Fix CVE-2014-9601, potential PNG decompression DOS #1060
+ [wiredfool]
+
+- Fix Regression in PyPy 2.4 in streamio #958
+ [wiredfool]
+
+2.6.1 (2014-10-11)
+------------------
+
+- Fix SciPy regression in Image.resize #945
+ [wiredfool]
+
+- Fix manifest to include all test files.
+ [aclark4life]
+
+2.6.0 (2014-10-01)
+------------------
+
+- Relax precision of ImageDraw tests for x86, GimpGradient for PPC #930
+ [wiredfool]
+
+2.6.0-rc1 (2014-09-29)
+----------------------
+
+- Use redistributable image for testing #884
+ [hugovk]
+
+- Use redistributable ICC profiles for testing, skip if not available #923
+ [wiredfool]
+
+- Additional documentation for JPEG info and save options #922
+ [wiredfool]
+
+- Fix JPEG Encoding memory leak when exif or qtables were specified #921
+ [wiredfool]
+
+- Image.tobytes() and Image.tostring() documentation update #916 #917
+ [mgedmin]
+
+- On Windows, do not execute convert.exe without specifying path #912
+ [cgohlke]
+
+- Fix msvc build error #911
+ [cgohlke]
+
+- Fix for handling P + transparency -> RGBA conversions #904
+ [wiredfool]
+
+- Retain alpha in ImageEnhance operations #909
+ [wiredfool]
+
+- Jpeg2k Decode/encode memory leak fix #898
+ [joshware, wiredfool]
+
+- EpsFilePlugin Speed improvements #886
+ [wiredfool, karstenw]
+
+- Don't resize if already the right size #892
+ [radarhere]
+
+- Fix for reading multipage TIFFs #885
+ [kostrom, wiredfool]
+
+- Correctly handle saving gray and CMYK JPEGs with quality=keep #857
+ [etienned]
+
+- Correct duplicate Tiff Metadata and Exif tag values
+ [hugovk]
+
+- Windows fixes #871
+ [wiredfool]
+
+- Fix TGA files with image ID field #856
+ [megabuz]
+
+- Fixed wrong P-mode of small, unoptimized L-mode GIF #843
+ [uvNikita]
+
+- Fixed CVE-2014-3598, a DOS in the Jpeg2KImagePlugin
+ [Andrew Drake]
+
+- Fixed CVE-2014-3589, a DOS in the IcnsImagePlugin
+ [Andrew Drake]
+
+- setup.py: Close open file handle before deleting #844
+ [divergentdave]
+
+- Return Profile with Transformed Images #837
+ [wiredfool]
+
+- Changed docstring to refer to the correct function #836
+ [MatMoore]
+
+- Adding coverage support for C code tests #833
+ [wiredfool]
+
+- PyPy performance improvements #821
+ [wiredfool]
+
+- Added support for reading MPO files #822
+ [Feneric]
+
+- Added support for encoding and decoding iTXt chunks #818
+ [dolda2000]
+
+- HSV Support #816
+ [wiredfool]
+
+- Removed unusable ImagePalette.new()
+ [hugovk]
+
+- Fix Scrambled XPM #808
+ [wiredfool]
+
+- Doc cleanup
+ [wiredfool]
+
+- Fix ``ImageStat`` docs #796
+ [akx]
+
+- Added docs for ExifTags #794
+ [Wintermute3]
+
+- More tests for CurImagePlugin, DcxImagePlugin, Effects.c, GimpGradientFile, ImageFont, ImageMath, ImagePalette, IptcImagePlugin, SpiderImagePlugin, SgiImagePlugin, XpmImagePlugin and _util
+ [hugovk]
+
+- Fix return value of FreeTypeFont.textsize() does not include font offsets #784
+ [tk0miya]
+
+- Fix dispose calculations for animated GIFs #765
+ [larsjsol]
+
+- Added class checking to Image __eq__ function #775
+ [radarhere, hugovk]
+
+- Test PalmImagePlugin and method to skip known bad tests #776
+ [hugovk, wiredfool]
+
+2.5.3 (2014-08-18)
+------------------
+
+- Fixed CVE-2014-3598, a DOS in the Jpeg2KImagePlugin (backport)
+ [Andrew Drake]
+
+
+2.5.2 (2014-08-13)
+------------------
+
+- Fixed CVE-2014-3589, a DOS in the IcnsImagePlugin (backport)
+ [Andrew Drake]
+
+2.5.1 (2014-07-10)
+------------------
+
+- Fixed install issue if Multiprocessing.Pool is not available
+ [wiredfool]
+
+- 32bit mult overflow fix #782
+ [wiredfool]
+
+2.5.0 (2014-07-01)
+------------------
+
+- Imagedraw rewrite #737
+ [terseus, wiredfool]
+
+- Add support for multithreaded test execution #755
+ [wiredfool]
+
+- Prevent shell injection #748
+ [mbrown1413, wiredfool]
+
+- Support for Resolution in BMP files #734
+ [gcq]
+
+- Fix error in setup.py for Python 3 #744
+ [matthew-brett]
+
+- Pyroma fix and add Python 3.4 to setup metadata #742
+ [wirefool]
+
+- Top level flake8 fixes #741
+ [aclark4life]
+
+- Remove obsolete Animated Raster Graphics (ARG) support #736
+ [hugovk]
+
+- Fix test_imagedraw failures #727
+ [cgohlke]
+
+- Fix AttributeError: class Image has no attribute 'DEBUG' #726
+ [cgohlke]
+
+- Fix msvc warning: 'inline' : macro redefinition #725
+ [cgohlke]
+
+- Cleanup #654
+ [dvska, hugovk, wiredfool]
+
+- 16-bit monochrome support for JPEG2000 #730
+ [videan42]
+
+- Fixed ImagePalette.save
+ [brightpisces]
+
+- Support JPEG qtables #677
+ [csinchok]
+
+- Add binary morphology addon
+ [dov, wiredfool]
+
+- Decompression bomb protection #674
+ [hugovk]
+
+- Put images in a single directory #708
+ [hugovk]
+
+- Support OpenJpeg 2.1 #681
+ [al45tair, wiredfool]
+
+- Remove unistd.h #include for all platforms #704
+ [wiredfool]
+
+- Use unittest for tests
+ [hugovk]
+
+- ImageCms fixes
+ [hugovk]
+
+- Added more ImageDraw tests
+ [hugovk]
+
+- Added tests for Spider files
+ [hugovk]
+
+- Use libtiff to write any compressed tiff files #669
+ [wiredfool]
+
+- Support for pickling Image objects
+ [hugovk]
+
+- Fixed resolution handling for EPS thumbnails #619
+ [eliempje]
+
+- Fixed rendering of some binary EPS files (Issue #302)
+ [eliempje]
+
+- Rename variables not to use built-in function names #670
+ [hugovk]
+
+- Ignore junk JPEG markers
+ [hugovk]
+
+- Change default interpolation for Image.thumbnail to Image.ANTIALIAS
+ [hugovk]
+
+- Add tests and fixes for saving PDFs
+ [hugovk]
+
+- Remove transparency resource after P->RGBA conversion
+ [hugovk]
+
+- Clean up preprocessor cruft for Windows #652
+ [CounterPillow]
+
+- Adjust Homebrew freetype detection logic #656
+ [jacknagel]
+
+- Added Image.close, context manager support
+ [wiredfool]
+
+- Added support for 16 bit PGM files
+ [wiredfool]
+
+- Updated OleFileIO to version 0.30 from upstream #618
+ [hugovk]
+
+- Added support for additional TIFF floating point format
+ [Hijackal]
+
+- Have the tempfile use a suffix with a dot
+ [wiredfool]
+
+- Fix variable name used for transparency manipulations #604
+ [nijel]
+
+2.4.0 (2014-04-01)
+------------------
+
+- Indexed Transparency handled for conversions between L, RGB, and P modes #574 (fixes #510)
+ [wiredfool]
+
+- Conversions enabled from RGBA->P #574 (fixes #544)
+ [wiredfool]
+
+- Improved icns support #565
+ [al45tair]
+
+- Fix libtiff leaking open files #580 (fixes #526)
+ [wiredfool]
+
+- Fixes for Jpeg encoding in Python 3 #578 (fixes #577)
+ [wiredfool]
+
+- Added support for JPEG 2000 #547
+ [al45tair]
+
+- Add more detailed error messages to Image.py #566
+ [larsmans]
+
+- Avoid conflicting _expand functions in PIL & MINGW, fixes #538
+ [aclark4life]
+
+- Merge from Philippe Lagadec’s OleFileIO_PL fork #512
+ [vadmium]
+
+- Fix ImageColor.getcolor #534
+ [homm]
+
+- Make ICO files work with the ImageFile.Parser interface #525 (fixes #522)
+ [wiredfool]
+
+- Handle 32bit compiled python on 64bit architecture #521
+ [choppsv1]
+
+- Fix support for characters >128 using .pcf or .pil fonts in Py3k #517 (fixes #505)
+ [wiredfool]
+
+- Skip CFFI test earlier if it's not installed #516
+ [wiredfool]
+
+- Fixed opening and saving odd sized .pcx files #535 (fixes #523)
+ [wiredfool]
+
+- Fixed palette handling when converting from mode P->RGB->P
+ [d-schmidt]
+
+- Fixed saving mode P image as a PNG with transparency = palette color 0
+ [d-schmidt]
+
+- Improve heuristic used when saving progressive and optimized JPEGs with high quality values #504
+ [e98cuenc]
+
+- Fixed DOS with invalid palette size or invalid image size in BMP file
+ [wiredfool]
+
+- Added support for BMP version 4 and 5
+ [eddwardo, wiredfool]
+
+- Fix segfault in getfont when passed a memory resident font
+ [wiredfool]
+
+- Fix crash on Saving a PNG when icc-profile is None #496
+ [brutasse]
+
+- Cffi+Python implementation of the PixelAccess object
+ [wiredfool]
+
+- PixelAccess returns unsigned ints for I16 mode
+ [wiredfool]
+
+- Minor patch on booleans + Travis #474
+ [sciunto]
+
+- Look in multiarch paths in GNU platforms #511
+ [pinotree]
+
+- Add arch support for pcc64, s390, s390x, armv7l, aarch64 #475
+ [manisandro]
+
+- Add arch support for ppc
+ [wiredfool]
+
+- Correctly quote file names for WindowsViewer command
+ [cgohlke]
+
+- Prefer homebrew freetype over X11 freetype (but still allow both) #466
+ [dmckeone]
+
+2.3.2 (2014-08-13)
+------------------
+
+- Fixed CVE-2014-3589, a DOS in the IcnsImagePlugin (backport)
+ [Andrew Drake]
+
+2.3.1 (2014-03-14)
+------------------
+
+- Fix insecure use of tempfile.mktemp (CVE-2014-1932 CVE-2014-1933)
+ [wiredfool]
+
+2.3.0 (2014-01-01)
+------------------
+
+- Stop leaking filename parameter passed to getfont #459
+ [jpharvey]
+
+- Report availability of LIBTIFF during setup and selftest
+ [cgohlke]
+
+- Fix msvc build error C1189: "No Target Architecture" #460
+ [cgohlke]
+
+- Fix memory leak in font_getsize
+ [wiredfool]
+
+- Correctly prioritize include and library paths #442
+ [ohanar]
+
+- Image.point fixes for numpy.array and docs #441
+ [wiredfool]
+
+- Save the transparency header by default for PNGs #424
+ [wiredfool]
+
+- Support for PNG tRNS header when converting from RGB->RGBA #423
+ [wiredfool]
+
+- PyQT5 Support #418
+ [wiredfool]
+
+- Updates for saving color tiffs w/compression using libtiff #417
+ [wiredfool]
+
+- 2gigapix image fixes and redux
+ [wiredfool]
+
+- Save arbitrary tags in Tiff image files #369
+ [wiredfool]
+
+- Quote filenames and title before using on command line #398
+ [tmccombs]
+
+- Fixed Viewer.show to return properly #399
+ [tmccombs]
+
+- Documentation fixes
+ [wiredfool]
+
+- Fixed memory leak saving images as webp when webpmux is available #429
+ [cezarsa]
+
+- Fix compiling with FreeType 2.5.1 #427
+ [stromnov]
+
+- Adds directories for NetBSD #411
+ [deepy]
+
+- Support RGBA TIFF with missing ExtraSamples tag #393
+ [cgohlke]
+
+- Lossless WEBP Support #390
+ [wiredfool]
+
+- Take compression as an option in the save call for tiffs #389
+ [wiredfool]
+
+- Add support for saving lossless WebP. Just pass 'lossless=True' to save() #386
+ [liftoff]
+
+- LCMS support upgraded from version 1 to version 2 #380 (fixes #343)
+ [wiredfool]
+
+- Added more raw decoder 16 bit pixel formats #379
+ [svanheulen]
+
+- Document remaining Image* modules listed in PIL handbook
+ [irksep]
+
+- Document ImageEnhance, ImageFile, ImageFilter, ImageFont, ImageGrab, ImageMath, and ImageOps
+ [irksep]
+
+- Port and update docs for Image, ImageChops, ImageColor, and ImageDraw
+ [irksep]
+
+- Move or copy content from README.rst to docs/
+ [irksep]
+
+- Respect CFLAGS/LDFLAGS when searching for headers/libs
+ [iElectric]
+
+- Port PIL Handbook tutorial and appendices
+ [irksep]
+
+- Alpha Premultiplication support for transform and resize #364
+ [wiredfool]
+
+- Fixes to make Pypy 2.1.0 work on Ubuntu 12.04/64 #359
+ [wiredfool]
+
+2.2.2 (2013-12-11)
+------------------
+
+- Fix compiling with FreeType 2.5.1 #427
+ [stromnov]
+
+2.2.1 (2013-10-02)
+------------------
+
+- Error installing Pillow 2.2.0 on Mac OS X (due to hard dep on brew) #357 (fixes #356)
+ [wiredfool]
+
+2.2.0 (2013-10-02)
+------------------
+
+- Bug in image transformations resulting from uninitialized memory #348 (fixes #254)
+ [nikmolnar]
+
+- Fix for encoding of b_whitespace #346 (similar to closed issue #272)
+ [mhogg]
+
+- Add numpy array interface support for 16 and 32 bit integer modes #347 (fixes #273)
+ [cgohlke]
+
+- Partial fix for #290: Add preliminary support for TIFF tags.
+ [wiredfool]
+
+- Fix #251 and #326: circumvent classification of pngtest_bad.png as malware
+ [cgohlke]
+
+- Add typedef uint64_t for MSVC #339
+ [cgohlke]
+
+- setup.py: better support for C_INCLUDE_PATH, LD_RUN_PATH, etc. #336 (fixes #329)
+ [nu774]
+
+- _imagingcms.c: include windef.h to fix build issue on MSVC #335 (fixes #328)
+ [nu774]
+
+- Automatically discover homebrew include/ and lib/ paths on OS X #330
+ [donspaulding]
+
+- Fix bytes which should be bytearray #325
+ [manisandro]
+
+- Add respective paths for C_INCLUDE_PATH, LD_RUN_PATH (rpath) to build
+ if specified as environment variables #324
+ [seanupton]
+
+- Fix #312 + gif optimize improvement
+ [d-schmidt]
+
+- Be more tolerant of tag read failures #320
+ [ericbuehl]
+
+- Catch truncated zTXt errors #321 (fixes #318)
+ [vytisb]
+
+- Fix IOError when saving progressive JPEGs #313
+ [e98cuenc]
+
+- Add RGBA support to ImageColor #309
+ [yoavweiss]
+
+- Test for ``str``, not ``"utf-8"`` #306 (fixes #304)
+ [mjpieters]
+
+- Fix missing import os in _util.py #303
+ [mnowotka]
+
+- Added missing exif tags #300
+ [freyes]
+
+- Fail on all import errors #298, #299 (fixes #297)
+ [macfreek, wiredfool]
+
+- Fixed Windows fallback (wasn't using correct file in Windows fonts) #295
+ [lmollea]
+
+- Moved ImageFile and ImageFileIO comments to docstrings #293
+ [freyes]
+
+- Restore compatibility with ISO C #289
+ [cgohlke]
+
+- Use correct format character for C int type #288
+ [cgohlke]
+
+- Allocate enough memory to hold pointers in encode.c #287
+ [cgohlke]
+
+- Fillorder double shuffling bug when FillOrder ==2 and decoding using libtiff #284 (fixes #279)
+ [wiredfool]
+
+- Moved Image module comments to docstrings.
+ [freyes]
+
+- Add 16-bit TIFF support #277 (fixes #274)
+ [wiredfool]
+
+- Ignore high ascii characters in string.whitespace #276 (fixes #272)
+ [wiredfool]
+
+- Added clean/build to tox to make it behave like Travis #275
+ [freyes]
+
+- Adding support for metadata in webp images #271
+ [heynemann]
+
+2.1.0 (2013-07-02)
+------------------
+
+- Add /usr/bin/env python shebangs to all scripts in /Scripts #197
+ [mgorny]
+
+- Add several TIFF decoders and encoders #268
+ [megabuz]
+
+- Added support for alpha transparent webp images.
+
+- Adding Python 3 support for StringIO.
+
+- Adding Python3 basestring compatibility without changing basestring.
+
+- Fix webp encode errors on win-amd64 #259
+ [cgohlke]
+
+- Better fix for ZeroDivisionError in ImageOps.fit for image.size height is 1 #267
+ [chrispbailey]
+
+- Better support for ICO images.
+
+- Changed PY_VERSION_HEX #190 (fixes #166)
+
+- Changes to put everything under the PIL namespace #191
+ [wiredfool]
+
+- Changing StringIO to BytesIO.
+
+- Cleanup whitespace.
+ [Arfrever]
+
+- Don't skip 'import site' on initialization when running tests for inplace builds.
+ [cgohlke]
+
+- Enable warnings for test suite #227
+ [wiredfool]
+
+- Fix for ZeroDivisionError in ImageOps.fit for image.size == (1,1) #255
+ [pterk]
+
+- Fix for if isinstance(filter, collections.Callable) crash. Python bug #7624 on <2.6.6
+
+- Remove double typedef declaration #194 (fixes #193)
+ [evertrol]
+
+- Fix msvc compile errors (#230).
+
+- Fix rendered characters have been chipped for some TrueType fonts
+ [tk0miya]
+
+- Fix usage of pilfont.py script #184
+ [fabiomcosta]
+
+- Fresh start for docs, generated by sphinx-apidoc.
+
+- Introduce --enable-x and fail if it is given and x is not available.
+
+- Partial work to add a wrapper for WebPGetFeatures to correctly support #220 (fixes #204)
+
+- Significant performance improvement of ``alpha_composite`` function #156
+ [homm]
+
+- Support explicitly disabling features via --disable-* options #240
+ [mgorny]
+
+- Support selftest.py --installed, fixes #263
+
+- Transparent WebP Support #220 (fixes #204)
+ [euangoddard, wiredfool]
+
+- Use PyCapsule for py3.1 #238 (fixes #237)
+ [wiredfool]
+
+- Workaround for: https://bugs.python.org/issue16754 in 3.2.x < 3.2.4 and 3.3.0.
+
+2.0.0 (2013-03-15)
+------------------
+
+.. Note:: Special thanks to Christoph Gohlke and Eric Soroos for assisting with a pre-PyCon 2013 release!
+
+- Many other bug fixes and enhancements by many other people.
+
+- Add Python 3 support. (Pillow >= 2.0.0 supports Python 2.6, 2.7, 3.2, 3.3. Pillow < 2.0.0 supports Python 2.4, 2.5, 2.6, 2.7.)
+ [fluggo]
+
+- Add PyPy support (experimental, please see #67)
+
+- Add WebP support #96
+ [lqs]
+
+- Add Tiff G3/G4 support (experimental)
+ [wiredfool]
+
+- Backport PIL's PNG/Zip improvements #95, #97
+ [olt]
+
+- Various 64-bit and Windows fixes.
+ [cgohlke]
+
+- Add testing suite.
+ [cgohlke, fluggo]
+
+- Added support for PNG images with transparency palette.
+ [d-schmidt]
+
+1.7.8 (2012-11-01)
+------------------
+
+- Removed doctests.py that made tests of other packages fail.
+ [thomasdesvenain]
+
+- Fix opening psd files with RGBA layers when A mode is not of type 65535 but 3.
+ Fixes #3
+ [thomasdesvenain]
+
+
+1.7.7 (2012-04-04)
+------------------
+
+- UNDEF more types before including windows headers
+ [mattip]
+
+1.7.6 (2012-01-20)
+------------------
+
+- Bug fix: freetype not found on Mac OS X with case-sensitive filesystem
+ [gjo]
+
+- Bug fix: Backport fix to split() after open() (regression introduced in PIL 1.1.7).
+ [sfllaw]
+
+1.7.5 (2011-09-07)
+------------------
+
+- Fix for sys.platform = "linux3"
+ [blueyed]
+
+- Package cleanup and additional documentation
+ [aclark4life]
+
+1.7.4 (2011-07-21)
+------------------
+
+- Fix brown bag release
+ [aclark4life]
+
+1.7.3 (2011-07-20)
+------------------
+
+- Fix : resize need int values, append int conversion in thumbnail method
+ [harobed]
+
+1.7.2 (2011-06-02)
+------------------
+
+- Bug fix: Python 2.4 compat
+ [aclark4life]
+
+1.7.1 (2011-05-31)
+------------------
+
+- More multi-arch support
+ [SteveM, regebro, barry, aclark4life]
+
+1.7.0 (2011-05-27)
+------------------
+
+- Add support for multi-arch library directory /usr/lib/x86_64-linux-gnu
+ [aclark4life]
+
+1.6 (12/01/2010)
+----------------
+
+- Bug fix: /usr/x11/include should be added to include_dirs not library_dirs
+ [elro]
+
+- Doc fixes
+ [aclark4life]
+
+1.5 (11/28/2010)
+----------------
+
+- Module and package fixes
+ [aclark4life]
+
+1.4 (11/28/2010)
+----------------
+
+- Doc fixes
+ [aclark4life]
+
+1.3 (11/28/2010)
+----------------
+
+- Add support for /lib64 and /usr/lib64 library directories on Linux
+ [aclark4life]
+
+- Doc fixes
+ [aclark4life]
+
+1.2 (08/02/2010)
+----------------
+
+- On OS X also check for freetype2 in the X11 path
+ [jezdez]
+
+- Doc fixes
+ [aclark4life]
+
+1.1 (07/31/2010)
+----------------
+
+- Removed setuptools_hg requirement
+ [aclark4life]
+
+- Doc fixes
+ [aclark4life]
+
+1.0 (07/30/2010)
+----------------
+
+- Remove support for ``import Image``. ``from PIL import Image`` now required.
+- Forked PIL based on `Chris McDonough and Hanno Schlichting's setuptools compatible re-packaging <https://dist.plone.org/thirdparty/PIL-1.1.7.tar.gz>`_
+ [aclark4life]
+
+Pre-fork
+========
+
+0.2b5-1.1.7
+-----------
+
+::
+
+ -*- coding: utf-8 -*-
+
+ The Python Imaging Library
+ $Id$
+
+ ACKNOWLEDGEMENTS: PIL wouldn't be what it is without the help of:
+ David Ascher, Phil Austin, Douglas Bagnall, Larry Bates, Anthony
+ Baxter, William Baxter, Denis Benoit, Jan Blom, Duncan Booth, Alexey
+ Borzenkov, Jeff Breidenbach, Roger Burnham, Zac Burns, Gene Cash,
+ Kevin Cazabon, Fred Clare, Greg Coats, Chris Cogdon, Greg Couch, Bill
+ Crutchfield, Abel Deuring, Tim Docker, Fred Drake, Graham Dumpleton,
+ Matthew Ellis, Eric Etheridge, Daniel Fetchinson, Robin Friedrich,
+ Pier Paolo Glave, Federico Di Gregorio, Markus Gritsch, Daniel
+ Haertle, Greg Hamilton, Mark Hammond, Bernhard Herzog, Rob Hooft, Bob
+ Ippolito, Jack Jansen, Bill Janssen, Edward Jones, Richard Jones,
+ HÃ¥kan Karlsson, Robert Kern, David Kirtley, Bob Klimek, Matthias
+ Klose, Andrew Kuchling, Magnus Källström, Victor Lacina, Ben Last,
+ Hamish Lawson, Cesare Leonardi, Andrew MacIntyre, Jan Matejek, Naveen
+ Michaud-Agrawal, Gordon McMillan, Skip Montanaro, Fredrik Nehr,
+ Russell Nelson, Luciano Nocera, Travis Oliphant, Piet van Oostrum,
+ Richard Oudkerk, Paul Pharr, Andres Polit, Conrado Porto Lopes Gouvêa,
+ Eric Raymond, Victor Reijs, Bertil Reinhammar, Nicholas Riley, Don
+ Rozenberg, Toby Sargeant, Barry Scott, Les Schaffer, Joel Shprentz,
+ Klamer Shutte, Gene Skonicki, Niki Spahiev, D. Alan Stewart, Perry
+ Stoll, Paul Svensson, Ulrik Svensson, Miki Tebeka, Michael van
+ Tellingen, Ivan Tkatchev, Dan Torop, Adam Twardoch, Rune Uhlin, Dmitry
+ Vasiliev, Sasha Voynow, Charles Waldman, Collin Winter, Dan Wolfe,
+ Ka-Ping Yee, and many others (if your name should be on this list, let
+ me know.)
+
+1.1.6 to 1.1.7
+--------------
+
+This section may not be fully complete. For changes since this file
+was last updated, see the repository revision history:
+http://svn.effbot.org/public/pil/
+
+1.1.7 final
+-----------
+
+- Set GIF loop info property to the number of iterations if a NETSCAPE
+ loop extension is present, instead of always setting it to 1 (from
+ Valentino Volonghi).
+
+1.1.7c1
+-------
+
+- Improved PNG compression (from Alexey Borzenkov).
+
+- Read interlaced PNG files (from Conrado Porto Lopes Gouvêa)
+
+- Added various TGA improvements from Alexey Borzenkov, including
+ support for specifying image orientation.
+
+- Bumped block threshold to 16 megabytes, made size estimation a bit
+ more accurate. This speeds up allocation of large images.
+
+- Fixed rounding error in ImagingDrawWideLine.
+
+ "gormish" writes: ImagingDrawWideLine() in Draw.c has a bug in every
+ version I've seen, which leads to different width lines depending on
+ the order of the points in the line. This is especially bad at some
+ angles where a 'width=2' line can completely disappear.
+
+- Added support for RGBA mode to the SGI module (based on code by
+ Karsten Hiddemann).
+
+- Handle repeated IPTC tags (adapted from a patch by Eric Bruning).
+
+ Eric writes: According to the specification, some IPTC tags can be
+ repeated, e.g., tag 2:25 (keywords). PIL 1.1.6 only retained the last
+ instance of that tag. Below is a patch to store all tags. If there are
+ multiple tag instances, they are stored in a (python) list. Single tag
+ instances remain as strings.
+
+- Fixed potential crash in ImageFilter for small target images
+ (reported by Zac Burns and Daniel Fetchinson).
+
+- Use BMP instead of JPEG as temporary show format on Mac OS X.
+
+- Fixed putpixel/new for I;16 with colors > 255.
+
+- Added integer power support to ImagingMath.
+
+- Added limited support for I;16L mode (explicit little endian).
+
+- Moved WMF support into Image.core; enable WMF rendering by default
+ if renderer is available.
+
+- Mark the ARG plugin as obsolete.
+
+- Added version query mechanism to ImageCms and ImageFont, for
+ debugging.
+
+- Added (experimental) ImageCms function for fetching the ICC profile
+ for the current display (currently Windows only).
+
+ Added HWND/HDC support to ImageCms.get_display_profile().
+
+- Added WMF renderer (Windows only).
+
+- Added ImagePointHandler and ImageTransformHandler mixins; made
+ ImageCmsTransform work with im.point.
+
+- Fixed potential endless loop in the XVThumbnail reader (from Nikolai
+ Ugelvik).
+
+- Added Kevin Cazabon's pyCMS package.
+
+ The C code has been moved to _imagingcms.c, the Python interface
+ module is installed as PIL.ImageCMS.
+
+ Added support for in-memory ICC profiles.
+
+ Unified buildTransform and buildTransformFromOpenProfiles.
+
+ The profile can now be either a filename, a profile object, or a
+ file-like object containing an in-memory profile.
+
+ Additional fixes from Florian Böch:
+
+ Very nice - it just needs LCMS flags support so we can use black
+ point compensation and softproofing :) See attached patches. They
+ also fix a naming issue which could cause confusion - display
+ profile (ImageCms wording) actually means proof profile (lcms
+ wording), so I changed variable names and docstrings where
+ applicable. Patches are tested under Python 2.6.
+
+- Improved support for layer names in PSD files (from Sylvain Baubeau)
+
+ Sylvain writes: I needed to be able to retrieve the names of the
+ layers in a PSD files. But PsdImagePlugin.py didn't do the job so I
+ wrote this very small patch.
+
+- Improved RGBA support for ImageTk for 8.4 and newer (from Con
+ Radchenko).
+
+ This replaces the slow run-length based encoding model with true
+ compositing at the Tk level.
+
+- Added support for 16- and 32-bit images to McIdas loader.
+
+ Based on file samples and stand-alone reader code provided by Craig
+ Swank.
+
+- Added ImagePalette support to putpalette.
+
+- Fixed problem with incremental parsing of PNG files.
+
+- Make selftest.py report non-zero status on failure (from Mark
+ Sienkiewicz)
+
+- Add big endian save support and multipage infrastructure to the TIFF
+ writer (from Sebastian Haase).
+
+- Handle files with GPS IFD but no basic EXIF IFD (reported by Kurt
+ Schwehr).
+
+- Added zTXT support (from Andrew Kuchling via Lowell Alleman).
+
+- Fixed potential infinite loop bug in ImageFont (from Guilherme Polo).
+
+- Added sample ICC profiles (from Kevin Cazabon)
+
+- Fixed array interface for I, F, and RGBA/RGBX images.
+
+- Added Chroma subsampling support for JPEG (from Justin Huff).
+
+ Justin writes: Attached is a patch (against PIL 1.1.6) to provide
+ control over the chroma subsampling done by the JPEG encoder. This
+ is often useful for reducing compression artifacts around edges of
+ clipart and text.
+
+- Added USM/Gaussian Blur code from Kevin Cazabon.
+
+- Fixed bug w. uninitialized image data when cropping outside the
+ source image.
+
+- Use ImageShow to implement the Image.show method.
+
+ Most notably, this picks the 'display' utility when available. It
+ also allows application code to register new display utilities via
+ the ImageShow registry.
+
+- Release the GIL in the PNG compressor (from Michael van Tellingen).
+
+- Revised JPEG CMYK handling.
+
+ Always assume Adobe behaviour, both when reading and writing (based on
+ a patch by Kevin Cazabon, and test data by Tim V. and Charlie Clark, and
+ additional debugging by Michael van Tellingen).
+
+- Support for preserving ICC profiles (by Florian Böch via Tim Hatch).
+
+ Florian writes:
+
+ It's a beta, so still needs some testing, but should allow you to:
+
+ - retain embedded ICC profiles when saving from/to JPEG, PNG, TIFF.
+ Existing code doesn't need to be changed.
+ - access embedded profiles in JPEG, PNG, PSD, TIFF.
+
+ It also includes patches for TIFF to retain IPTC, Photoshop and XMP
+ metadata when saving as TIFF again, read/write TIFF resolution
+ information correctly, and to correct inverted CMYK JPEG files.
+
+- Fixed potential memory leak in median cut quantizer (from Evgeny Salmin).
+
+- Fixed OverflowError when reading upside-down BMP images.
+
+- Added resolution save option for PDF files.
+
+ Andreas Kostyrka writes: I've included a patched PdfImagePlugin.py
+ based on 1.1.6 as included in Ubuntu, that supports a "resolution"
+ save option. Not great, but it makes the PDF saving more useful by
+ allowing PDFs that are not exactly 72dpi.
+
+- Look for Tcl/Tk include files in version-specific include directory
+ (from Encolpe Degoute).
+
+- Fixed grayscale rounding error in ImageColor.getcolor (from Tim
+ Hatch).
+
+- Fixed calculation of mean value in ImageEnhance.Contrast (reported
+ by "roop" and Scott David Daniels).
+
+- Fixed truetype positioning when first character has a negative left
+ bearing (from Ned Batchelder):
+
+ Ned writes: In PIL 1.1.6, ImageDraw.text will position the string
+ incorrectly if the first character has a negative left bearing. To
+ see the problem, show a string like "///" in an italic font. The
+ first slash will be clipped at the left, and the string will be
+ mis-positioned.
+
+- Fixed resolution unit bug in tiff reader/writer (based on code by
+ Florian Höch, Gary Bloom, and others).
+
+- Added simple transparency support for RGB images (reported by
+ Sebastian Spaeth).
+
+- Added support for Unicode filenames in ImageFont.truetype (from Donn
+ Ingle).
+
+- Fixed potential crash in ImageFont.getname method (from Donn Ingle).
+
+- Fixed encoding issue in PIL/WalImageFile (from Santiago M. Mola).
+
+1.1.6
+-----
+
+- Fixed some 64-bit compatibility warnings for Python 2.5.
+
+- Added threading support for the Sane driver (from Abel Deuring).
+
+1.1.6b2
+-------
+
+- Added experimental "floodfill" function to the ImageDraw module
+ (based on code by Eric Raymond).
+
+- The default arguments for "frombuffer" doesn't match "fromstring"
+ and the documentation; this is a bug, and will most likely be fixed
+ in a future version. In this release, PIL prints a warning message
+ instead. To silence the warning, change any calls of the form
+ "frombuffer(mode, size, data)" to::
+
+ frombuffer(mode, size, data, "raw", mode, 0, 1)
+
+- Added "fromarray" function, which takes an object implementing the
+ NumPy array interface and creates a PIL Image from it. (from Travis
+ Oliphant).
+
+- Added NumPy array interface support (__array_interface__) to the
+ Image class (based on code by Travis Oliphant).
+
+ This allows you to easily convert between PIL image memories and
+ NumPy arrays::
+
+ import numpy, Image
+ im = Image.open('hopper.jpg')
+ a = numpy.asarray(im) # a is readonly
+ im = Image.fromarray(a)
+
+- Fixed CMYK polarity for JPEG images, by treating all images as
+ "Adobe CMYK" images. (thanks to Cesare Leonardi and Kevin Cazabon
+ for samples, debugging, and patches).
+
+1.1.6b1
+-------
+
+- Added 'expand' option to the Image 'rotate' method. If true, the
+ output image is made large enough to hold the entire rotated image.
+
+- Changed the ImageDraw 'line' method to always draw the last pixel in
+ a polyline, independent of line angle.
+
+- Fixed bearing calculation and clipping in the ImageFont truetype
+ renderer; this could lead to clipped text, or crashes in the low-level
+ _imagingft module. (based on input from Adam Twardoch and
+ others).
+
+- Added ImageQt wrapper module, for converting PIL Image objects to
+ QImage objects in an efficient way.
+
+- Fixed 'getmodebands' to return the number of bands also for "PA"
+ and "LA" modes. Added 'getmodebandnames' helper that return the
+ band names.
+
+1.1.6a2
+-------
+
+- Added float/double support to the TIFF loader (from Russell
+ Nelson).
+
+- Fixed broken use of realloc() in path.c (from Jan Matejek)
+
+- Added save support for Spider images (from William Baxter).
+
+- Fixed broken 'paste' and 'resize' operations in pildriver
+ (from Bill Janssen).
+
+- Added support for duplex scanning to the Sane interface (Abel
+ Deuring).
+
+1.1.6a1
+-------
+
+- Fixed a memory leak in "convert(mode)", when converting from
+ L to P.
+
+- Added pixel access object. The "load" method now returns a
+ access object that can be used to directly get and set pixel
+ values, using ordinary [x, y] notation::
+
+ pixel = im.load()
+ v = pixel[x, y]
+ pixel[x, y] = v
+
+ If you're accessing more than a few pixels, this is a lot
+ faster than using getpixel/putpixel.
+
+- Fixed building on Cygwin (from Miki Tebeka).
+
+- Fixed "point(callable)" on unloaded images (reported by HÃ¥kan
+ Karlsson).
+
+- Fixed size bug in ImageWin.ImageWindow constructor (from Victor
+ Reijs)
+
+- Fixed ImageMath float() and int() operations for Python 2.4
+ (reported by Don Rozenberg).
+
+- Fixed "RuntimeError: encoder error -8 in tostring" problem for
+ wide "RGB", "I", and "F" images.
+
+- Fixed line width calculation.
+
+1.1.6a0
+-------
+
+- Fixed byte order issue in Image.paste(ink) (from Ka-Ping Yee).
+
+- Fixed off-by-0.5 errors in the ANTIALIAS code (based on input
+ from Douglas Bagnall).
+
+- Added buffer interface support to the Path constructor. If
+ a buffer is provided, it is assumed to contain a flat array
+ of float coordinates (e.g. array.array('f', seq)).
+
+- Added new ImageMath module.
+
+- Fixed ImageOps.equalize when used with a small number of distinct
+ values (reported by David Kirtley).
+
+- Fixed potential integer division in PSDraw.image (from Eric Etheridge).
+
+1.1.5c2 and 1.1.5 final
+-----------------------
+
+- Added experimental PERSPECTIVE transform method (from Jeff Breidenbach).
+
+1.1.5c1
+-------
+
+- Make sure "thumbnail" never generates zero-wide or zero-high images
+ (reported by Gene Skonicki)
+
+- Fixed a "getcolors" bug that could result in a zero count for some
+ colors (reported by Richard Oudkerk).
+
+- Changed default "convert" palette to avoid "rounding errors" when
+ round-tripping white source pixels (reported by Henryk Gerlach and
+ Jeff Epler).
+
+1.1.5b3
+-------
+
+- Don't crash in "quantize" method if the number of colors requested
+ is larger than 256. This release raises a ValueError exception;
+ future versions may return a mode "RGB" image instead (reported
+ by Richard Oudkerk).
+
+- Added WBMP read/write support (based on code by Duncan Booth).
+
+1.1.5b2
+-------
+
+- Added DPI read/write support to the PNG codec. The decoder sets
+ the info["dpi"] attribute for PNG files with appropriate resolution
+ settings. The encoder uses the "dpi" option (based on code by Niki
+ Spahiev).
+
+- Added limited support for "point" mappings from mode "I" to mode "L".
+ Only 16-bit values are supported (other values are clipped), the lookup
+ table must contain exactly 65536 entries, and the mode argument must be
+ set to "L".
+
+- Added support for Mac OS X icns files (based on code by Bob Ippolito).
+
+- Added "ModeFilter" support to the ImageFilter module.
+
+- Added support for Spider images (from William Baxter). See the
+ comments in PIL/SpiderImagePlugin.py for more information on this
+ format.
+
+1.1.5b1
+-------
+
+- Added new Sane release (from Ralph Heinkel). See the Sane/README
+ and Sane/CHANGES files for more information.
+
+- Added experimental PngInfo chunk container to the PngImageFile
+ module. This can be used to add arbitrary chunks to a PNG file.
+ Create a PngInfo instance, use "add" or "add_text" to add chunks,
+ and pass the instance as the "pnginfo" option when saving the
+ file.
+
+- Added "getpalette" method. This returns the palette as a list,
+ or None if the image has no palette. To modify the palette, use
+ "getpalette" to fetch the current palette, modify the list, and
+ put it back using "putpalette".
+
+- Added optional flattening to the ImagePath "tolist" method.
+ tolist() or tolist(0) returns a list of 2-tuples, as before.
+ tolist(1) returns a flattened list instead.
+
+1.1.5a5
+-------
+
+- Fixed BILINEAR/BICUBIC/ANTIALIAS filtering for mode "LA".
+
+- Added "getcolors()" method. This is similar to the existing histogram
+ method, but looks at color values instead of individual layers,
+ and returns an unsorted list of (count, color) tuples.
+
+ By default, the method returns None if finds more than 256 colors.
+ If you need to look for more colors, you can pass in a limit (this
+ is used to allocate internal tables, so you probably don't want to
+ pass in too large values).
+
+- Build improvements: Fixed building under AIX, improved detection of
+ FreeType2 and Mac OS X framework libraries, and more. Many thanks
+ to everyone who helped test the new "setup.py" script!
+
+1.1.5a4
+-------
+
+- The "save" method now looks for a file format driver before
+ creating the file.
+
+- Don't use antialiased truetype fonts when drawing in mode "P", "I",
+ and "F" images.
+
+- Rewrote the "setup.py" file. The new version scans for available
+ support libraries, and configures both the libImaging core library
+ and the bindings in one step.
+
+ To use specific versions of the libraries, edit the ROOT variables
+ in the setup.py file.
+
+- Removed threaded "show" viewer; use the old "show" implementation
+ instead (Windows).
+
+- Added deprecation warnings to Image.offset, ImageDraw.setink, and
+ ImageDraw.setfill.
+
+- Added width option to ImageDraw.line(). The current implementation
+ works best for straight lines; it does not support line joins, so
+ polylines won't look good.
+
+- ImageDraw.Draw is now a factory function instead of a class. If
+ you need to create custom draw classes, inherit from the ImageDraw
+ class. All other code should use the factory function.
+
+- Fixed loading of certain PCX files (problem reported by Greg
+ Hamilton, who also provided samples).
+
+- Changed _imagingft.c to require FreeType 2.1 or newer. The
+ module can still be built with earlier versions; see comments
+ in _imagingft.c for details.
+
+1.1.5a3
+-------
+
+- Added 'getim' method, which returns a PyCObject wrapping an
+ Imaging pointer. The description string is set to IMAGING_MAGIC.
+ See Imaging.h for pointer and string declarations.
+
+- Fixed reading of TIFF JPEG images (problem reported by Ulrik
+ Svensson).
+
+- Made ImageColor work under Python 1.5.2
+
+- Fixed division by zero "equalize" on very small images (from
+ Douglas Bagnall).
+
+1.1.5a2
+-------
+
+- The "paste" method now supports the alternative "paste(im, mask)"
+ syntax (in this case, the box defaults to im's bounding box).
+
+- The "ImageFile.Parser" class now works also for PNG files with
+ more than one IDAT block.
+
+- Added DPI read/write to the TIFF codec, and fixed writing of
+ rational values. The decoder sets the info["dpi"] attribute
+ for TIFF files with appropriate resolution settings. The
+ encoder uses the "dpi" option.
+
+- Disable interlacing for small (or narrow) GIF images, to
+ work around what appears to be a hard-to-find bug in PIL's
+ GIF encoder.
+
+- Fixed writing of mode "P" PDF images. Made mode "1" PDF
+ images smaller.
+
+- Made the XBM reader a bit more robust; the file may now start
+ with a few whitespace characters.
+
+- Added support for enhanced metafiles to the WMF driver. The
+ separate PILWMF kit lets you render both placeable WMF files
+ and EMF files as raster images. See
+ http://effbot.org/downloads#pilwmf
+
+1.1.5a1
+-------
+
+- Replaced broken WMF driver with a WMF stub plugin (see below).
+
+- Fixed writing of mode "1", "L", and "CMYK" PDF images (based on
+ input from Nicholas Riley and others).
+
+- Fixed adaptive palette conversion for zero-width or zero-height
+ images (from Chris Cogdon)
+
+- Fixed reading of PNG images from QuickTime 6 (from Paul Pharr)
+
+- Added support for StubImageFile plugins, including stub plugins
+ for BUFR, FITS, GRIB, and HDF5 files. A stub plugin can identify
+ a given file format, but relies on application code to open and
+ save files in that format.
+
+- Added optional "encoding" argument to the ImageFont.truetype
+ factory. This argument can be used to specify non-Unicode character
+ maps for fonts that support that. For example, to draw text using
+ the Microsoft Symbol font, use::
+
+ font = ImageFont.truetype("symbol.ttf", 16, encoding="symb")
+ draw.text((0, 0), unichr(0xF000 + 0xAA))
+
+ (note that the symbol font uses characters in the 0xF000-0xF0FF
+ range)
+
+ Common encodings are "unic" (Unicode), "symb" (Microsoft Symbol),
+ "ADOB" (Adobe Standard), "ADBE" (Adobe Expert), and "armn" (Apple
+ Roman). See the FreeType documentation for more information.
+
+- Made "putalpha" a bit more robust; you can now attach an alpha
+ layer to a plain "L" or "RGB" image, and you can also specify
+ constant alphas instead of alpha layers (using integers or colour
+ names).
+
+- Added experimental "LA" mode support.
+
+ An "LA" image is an "L" image with an attached transparency layer.
+ Note that support for "LA" is not complete; some operations may
+ fail or produce unexpected results.
+
+- Added "RankFilter", "MinFilter", "MedianFilter", and "MaxFilter"
+ classes to the ImageFilter module.
+
+- Improved support for applications using multiple threads; PIL
+ now releases the global interpreter lock for many CPU-intensive
+ operations (based on work by Kevin Cazabon).
+
+- Ignore Unicode characters in the PCF loader (from Andres Polit)
+
+- Fixed typo in OleFileIO.loadfat, which could affect loading of
+ FlashPix and Image Composer images (Daniel Haertle)
+
+- Fixed building on platforms that have Freetype but don't have
+ Tcl/Tk (Jack Jansen, Luciano Nocera, Piet van Oostrum and others)
+
+- Added EXIF GPSInfo read support for JPEG files. To extract
+ GPSInfo information, open the file, extract the exif dictionary,
+ and check for the key 0x8825 (GPSInfo). If present, it contains
+ a dictionary mapping GPS keys to GPS values. For a list of keys,
+ see the EXIF specification.
+
+ The "ExifTags" module contains a GPSTAGS dictionary mapping GPS
+ tags to tag names.
+
+- Added DPI read support to the PCX and DCX codecs (info["dpi"]).
+
+- The "show" methods now uses a built-in image viewer on Windows.
+ This viewer creates an instance of the ImageWindow class (see
+ below) and keeps it running in a separate thread. NOTE: This
+ was disabled in 1.1.5a4.
+
+- Added experimental "Window" and "ImageWindow" classes to the
+ ImageWin module. These classes allow you to create a WCK-style
+ toplevel window, and use it to display raster data.
+
+- Fixed some Python 1.5.2 issues (to build under 1.5.2, use the
+ Makefile.pre.in/Setup.in approach)
+
+- Added support for the TIFF FillOrder tag. PIL can read mode "1",
+ "L", "P" and "RGB" images with non-standard FillOrder (based on
+ input from Jeff Breidenbach).
+
+1.1.4 final
+-----------
+
+- Fixed ImageTk build problem on Unix.
+
+1.1.4b2
+-------
+
+- Improved building on Mac OS X (from Jack Jansen).
+
+- Improved building on Windows with MinGW (from Klamer Shutte).
+
+- If no font is specified, ImageDraw now uses the embedded default
+ font. Use the "load" or "truetype" methods to load a real font.
+
+- Added embedded default font to the ImageFont module (currently
+ an 8-pixel Courier font, taken from the X window distribution).
+
+1.1.4b1
+-------
+
+- Added experimental EXIF support for JPEG files. To extract EXIF
+ information from a JPEG file, open the file as usual, and call the
+ "_getexif" method. If successful, this method returns a dictionary
+ mapping EXIF TIFF tags to values. If the file does not contain EXIF
+ data, the "_getexif" method returns None.
+
+ The "ExifTags" module contains a dictionary mapping tags to tag
+ names.
+
+ This interface will most likely change in future versions.
+
+- Fixed a bug when using the "transparency" option with the GIF
+ writer.
+
+- Added limited support for "bitfield compression" in BMP files
+ and DIB buffers, for 15-bit, 16-bit, and 32-bit images. This
+ also fixes a problem with ImageGrab module when copying screendumps
+ from the clipboard on 15/16/32-bit displays.
+
+- Added experimental WAL (Quake 2 textures) loader. To use this
+ loader, import WalImageFile and call the "open" method in that
+ module.
+
+1.1.4a4
+-------
+
+- Added updated SANE driver (Andrew Kuchling, Abel Deuring)
+
+- Use Python's "mmap" module on non-Windows platforms to read some
+ uncompressed formats using memory mapping. Also added a "frombuffer"
+ function that allows you to access the contents of an existing string
+ or buffer object as if it were an image object.
+
+- Fixed a memory leak that could appear when processing mode "P"
+ images (from Pier Paolo Glave)
+
+- Ignore Unicode characters in the BDF loader (from Graham Dumpleton)
+
+1.1.4a3 released; Windows only
+------------------------------
+
+- Added experimental RGBA-on-RGB drawing support. To use RGBA
+ colours on an RGB image, pass "RGBA" as the second string to
+ the ImageDraw.Draw constructor.
+
+- Added support for non-ASCII strings (Latin-1) and Unicode
+ to the truetype font renderer.
+
+- The ImageWin "Dib" object can now be constructed directly from
+ an image object.
+
+- The ImageWin module now allows you use window handles as well
+ as device contexts. To use a window handle, wrap the handle in
+ an ImageWin.HWND object, and pass in this object instead of the
+ device context.
+
+1.1.4a2
+-------
+
+- Improved support for 16-bit unsigned integer images (mode "I;16").
+ This includes TIFF reader support, and support for "getextrema"
+ and "point" (from Klamer Shutte).
+
+- Made the BdfFontFile reader a bit more robust (from Kevin Cazabon
+ and Dmitry Vasiliev)
+
+- Changed TIFF writer to always write Compression tag, even when
+ using the default compression (from Greg Couch).
+
+- Added "show" support for Mac OS X (from Dan Wolfe).
+
+- Added clipboard support to the "ImageGrab" module (Windows only).
+ The "grabclipboard" function returns an Image object, a list of
+ filenames (not in 1.1.4), or None if neither was found.
+
+1.1.4a1
+-------
+
+- Improved support for drawing RGB data in palette images. You can
+ now use RGB tuples or colour names (see below) when drawing in a
+ mode "P" image. The drawing layer automatically assigns color
+ indexes, as long as you don't use more than 256 unique colours.
+
+- Moved self test from MiniTest/test.py to ./selftest.py.
+
+- Added support for CSS3-style color strings to most places that
+ accept colour codes/tuples. This includes the "ImageDraw" module,
+ the Image "new" function, and the Image "paste" method.
+
+ Colour strings can use one of the following formats: "#f00",
+ "#ff0000", "rgb(255,0,0)", "rgb(100%,0%,0%)", "hsl(0, 100%, 50%)",
+ or "red" (most X11-style colour names are supported). See the
+ documentation for the "ImageColor" module for more information.
+
+- Fixed DCX decoder (based on input from Larry Bates)
+
+- Added "IptcImagePlugin.getiptcinfo" helper to extract IPTC/NAA
+ newsphoto properties from JPEG, TIFF, or IPTC files.
+
+- Support for TrueType/OpenType fonts has been added to
+ the standard distribution. You need the freetype 2.0
+ library.
+
+- Made the PCX reader a bit more robust when reading 2-bit
+ and 4-bit PCX images with odd image sizes.
+
+- Added "Kernel" class to the ImageFilter module. This class
+ allows you to filter images with user-defined 3x3 and 5x5
+ convolution kernels.
+
+- Added "putdata" support for mode "I", "F" and "RGB".
+
+- The GIF writer now supports the transparency option (from
+ Denis Benoit).
+
+- A HTML version of the module documentation is now shipped
+ with the source code distribution. You'll find the files in
+ the Doc subdirectory.
+
+- Added support for Palm pixmaps (from Bill Janssen). This
+ change was listed for 1.1.3, but the "PalmImagePlugin" driver
+ didn't make it into the distribution.
+
+- Improved decoder error messages.
+
+1.1.3 final
+-----------
+
+- Made setup.py look for old versions of zlib. For some background,
+ see: https://zlib.net/advisory-2002-03-11.txt
+
+1.1.3c2
+-------
+
+- Added setup.py file (tested on Unix and Windows). You still
+ need to build libImaging/imaging.lib in the traditional way,
+ but the setup.py script takes care of the rest.
+
+ The old Setup.in/Makefile.pre.in build method is still
+ supported.
+
+- Fixed segmentation violation in ANTIALIAS filter (an internal
+ buffer wasn't properly allocated).
+
+1.1.3c1
+-------
+
+- Added ANTIALIAS downsampling filter for high-quality "resize"
+ and "thumbnail" operations. Also added filter option to the
+ "thumbnail" operation; the default value is NEAREST, but this
+ will most likely change in future versions.
+
+- Fixed plugin loader to be more robust if the __file__
+ variable isn't set.
+
+- Added seek/tell support (for layers) to the PhotoShop
+ loader. Layer 0 is the main image.
+
+- Added new (but experimental) "ImageOps" module, which provides
+ shortcuts for commonly used operations on entire images.
+
+- Don't mess up when loading PNG images if the decoder leaves
+ data in the output buffer. This could cause internal errors
+ on some PNG images, with some versions of ZLIB. (Bug report
+ and patch provided by Bernhard Herzog.)
+
+- Don't mess up on Unicode filenames.
+
+- Don't mess up when drawing on big endian platforms.
+
+- Made the TIFF loader a bit more robust; it can now read some
+ more slightly broken TIFF files (based on input from Ted Wright,
+ Bob Klimek, and D. Alan Stewart)
+
+- Added OS/2 EMX build files (from Andrew MacIntyre)
+
+- Change "ImageFont" to reject image files if they don't have the
+ right mode. Older versions could leak memory for "P" images.
+ (Bug reported by Markus Gritsch).
+
+- Renamed some internal functions to avoid potential build
+ problem on Mac OS X.
+
+- Added DL_EXPORT where relevant (for Cygwin, based on input
+ from Robert Yodlowski)
+
+- (re)moved bogus __init__ call in BdfFontFile (bug spotted
+ by Fred Clare)
+
+- Added "ImageGrab" support (Windows only)
+
+- Added support for XBM hotspots (based on code contributed by
+ Bernhard Herzog).
+
+- Added write support for more TIFF tags, namely the Artist,
+ Copyright, DateTime, ResolutionUnit, Software, XResolution and
+ YResolution tags (from Greg Couch)
+
+- Added TransposedFont wrapper to ImageFont module
+
+- Added "optimize" flag to GIF encoder. If optimize is present
+ and non-zero, PIL will work harder to create a small file.
+
+- Raise "EOFError" (not IndexError) when reading beyond the
+ end of a TIFF sequence.
+
+- Support rewind ("seek(0)") for GIF and TIFF sequences.
+
+- Load grayscale GIF images as mode "L"
+
+- Added DPI read/write support to the JPEG codec. The decoder
+ sets the info["dpi"] attribute for JPEG files with JFIF dpi
+ settings. The encoder uses the "dpi" option::
+
+ im = Image.open("file.jpg")
+ dpi = im.info["dpi"] # raises KeyError if DPI not known
+ im.save("out.jpg", dpi=dpi)
+
+ Note that PIL doesn't always preserve the "info" attribute
+ for normal image operations.
+
+1.1.2c1 and 1.1.2 final
+-----------------------
+
+- Adapted to Python 2.1. Among other things, all uses of the
+ "regex" module have been replaced with "re".
+
+- Fixed attribute error when reading large PNG files (this bug
+ was introduced in maintenance code released after the 1.1.1
+ release)
+
+- Ignore non-string objects in sys.path
+
+- Fixed Image.transform(EXTENT) for negative xoffsets
+
+- Fixed loading of image plugins if PIL is installed as a package.
+ (The plugin loader now always looks in the directory where the
+ Image.py module itself is found, even if that directory isn't on
+ the standard search path)
+
+- The Png plugin has been added to the list of preloaded standard
+ formats
+
+- Fixed bitmap/text drawing in fill mode.
+
+- Fixed "getextrema" to work also for multiband images.
+
+- Added transparency support for L and P images to the PNG codec.
+
+- Improved support for read-only images. The "load" method now
+ sets the "readonly" attribute for memory-mapped images. Operations
+ that modifies an image in place (such as "paste" and drawing operations)
+ creates an in-memory copy of the image, if necessary. (before this
+ change, any attempt to modify a memory-mapped image resulted in a
+ core dump...)
+
+- Added special cases for lists everywhere PIL expects a sequence.
+ This should speed up things like "putdata" and drawing operations.
+
+- The Image.offset method is deprecated. Use the ImageChops.offset
+ function instead.
+
+- Changed ImageChops operators to copy palette and info dictionary
+ from the first image argument.
+
+1.1.1
+-----
+
+- Additional fixes for Python 1.6/2.0, including TIFF "save" bug.
+
+- Changed "init" to properly load plugins when PIL is used as a
+ package.
+
+- Fixed broken "show" method (on Unix)
+
+1.0 to 1.1
+----------
+
+- Adapted to Python 1.6 ("append" and other method changes)
+
+- Fixed Image.paste when pasting with solid colour and matte
+ layers ("L" or "RGBA" masks) (bug reported by Robert Kern)
+
+- To make it easier to distribute prebuilt versions of PIL,
+ the tkinit binding stuff has been moved to a separate
+ extension module, named "_imagingtk".
+
+0.3b2 to 1.0 final
+------------------
+
+- If there's no 16-bit integer (like on a Cray T3E), set
+ INT16 to the smallest integer available. Most of the
+ library works just fine anyway (from Bill Crutchfield)
+
+- Tweaks to make drawing work on big-endian platforms.
+
+1.0c2
+-----
+
+- If PIL is built with the WITH_TKINTER flag, ImageTk can
+ automatically hook into a standard Tkinter build. You
+ no longer need to build your own Tkinter to use the
+ ImageTk module.
+
+ The old way still works, though. For more information,
+ see Tk/install.txt.
+
+- Some tweaks to ImageTk to support multiple Tk interpreters
+ (from Greg Couch).
+
+- ImageFont "load_path" now scans directory mentioned in .pth
+ files (from Richard Jones).
+
+1.0c1
+-----
+
+- The TIFF plugin has been rewritten. The new plugin fully
+ supports all major PIL image modes (including F and I).
+
+- The ImageFile module now includes a Parser class, which can
+ be used to incrementally decode an image file (while downloading
+ it from the net, for example). See the handbook for
+ details.
+
+- "show" now converts non-standard modes to "L" or "RGB" (as
+ appropriate), rather than writing weird things to disk for
+ "xv" to choke upon. (bug reported by Les Schaffer).
+
+1.0b2
+-----
+
+- Major speedups for rotate, transform(EXTENT), and transform(AFFINE)
+ when using nearest neighbour resampling.
+
+- Modified ImageDraw to be compatible with the Arrow graphics
+ interface. See the handbook for details.
+
+- PIL now automatically loads file codecs when used as a package
+ (from The Dragon De Monsyne). Also included an __init__.py file
+ in the standard distribution.
+
+- The GIF encoder has been modified to produce much smaller files.
+
+ PIL now uses a run-length encoding method to encode GIF files.
+ On a random selection of GIF images grabbed from the web, this
+ version makes the images about twice as large as the original
+ LZW files, where the earlier version made them over 5 times
+ larger. YMMV, of course.
+
+- Added PCX write support (works with "1", "P", "L", and "RGB")
+
+- Added "bitmap" and "textsize" methods to ImageDraw.
+
+- Improved font rendering code. Fixed a bug or two, and moved
+ most of the time critical stuff to C.
+
+- Removed "bdf2pil.py". Use "pilfont.py" instead!
+
+- Improved 16-bit support (still experimental, though).
+
+ The following methods now support "I;16" and "I;16B" images:
+ "getpixel", "copy", "convert" (to and from mode "I"), "resize",
+ "rotate", and "transform" with nearest neighbour filters, and
+ "save" using the IM format. The "new" and "open" functions
+ also work as expected. On Windows, 16-bit files are memory
+ mapped.
+
+ NOTE: ALL other operations are still UNDEFINED on 16-bit images.
+
+- The "paste" method now supports constant sources.
+
+ Just pass a colour value (a number or a tuple, depending on
+ the target image mode) instead of the source image.
+
+ This was in fact implemented in an inefficient way in
+ earlier versions (the "paste" method generated a temporary
+ source image if you passed it a colour instead of an image).
+ In this version, this is handled on the C level instead.
+
+- Added experimental "RGBa" mode support.
+
+ An "RGBa" image is an RGBA image where the colour components
+ have have been premultiplied with the alpha value. PIL allows
+ you to convert an RGBA image to an RGBa image, and to paste
+ RGBa images on top of RGB images. Since this saves a bunch
+ of multiplications and shifts, it is typically about twice
+ as fast an ordinary RGBA paste.
+
+- Eliminated extra conversion step when pasting "RGBA" or "RGBa"
+ images on top of "RGB" images.
+
+- Fixed Image.BICUBIC resampling for "RGB" images.
+
+- Fixed PCX image file handler to properly read 8-bit PCX
+ files (bug introduced in 1.0b1, reported by Bernhard
+ Herzog)
+
+- Fixed PSDraw "image" method to restore the coordinate
+ system.
+
+- Fixed "blend" problem when applied to images that was
+ not already loaded (reported by Edward C. Jones)
+
+- Fixed -f option to "pilconvert.py" (from Anthony Baxter)
+
+1.0b1
+-----
+
+- Added Toby J. Sargeant's quantization package. To enable
+ quantization, use the "palette" option to "convert"::
+
+ imOut = im.convert("P", palette=Image.ADAPTIVE)
+
+ This can be used with "L", "P", and "RGB" images. In this
+ version, dithering cannot be used with adaptive palettes.
+
+ Note: ADAPTIVE currently maps to median cut quantization
+ with 256 colours. The quantization package also contains
+ a maximum coverage quantizer, which will be supported by
+ future versions of PIL.
+
+- Added Eric S. Raymond's "pildriver" image calculator to the
+ distribution. See the docstring for more information.
+
+- The "offset" method no longer dumps core if given positive
+ offsets (from Charles Waldman).
+
+- Fixed a resource leak that could cause ImageWin to run out of
+ GDI resources (from Roger Burnham).
+
+- Added "arc", "chord", and "pieslice" methods to ImageDraw (inspired
+ by code contributed by Richard Jones).
+
+- Added experimental 16-bit support, via modes "I;16" (little endian
+ data) and "I;16B" (big endian). Only a few methods properly support
+ such images (see above).
+
+- Added XV thumbnail file handler (from Gene Cash).
+
+- Fixed BMP image file handler to handle palette images with small
+ palettes (from Rob Hooft).
+
+- Fixed Sun raster file handler for palette images (from Charles
+ Waldman).
+
+- Improved various internal error messages.
+
+- Fixed Path constructor to handle arbitrary sequence objects. This
+ also affects the ImageDraw class (from Richard Jones).
+
+- Fixed a bug in JpegDecode that caused PIL to report "decoder error
+ -2" for some progressive JPEG files (reported by Magnus Källström,
+ who also provided samples).
+
+- Fixed a bug in JpegImagePlugin that caused PIL to hang when loading
+ JPEG files using 16-bit quantization tables.
+
+- The Image "transform" method now supports Image.QUAD transforms.
+ The data argument is an 8-tuple giving the upper left, lower
+ left, lower right, and upper right corner of the source quadrilateral.
+ Also added Image.MESH transform which takes a list
+ of quadrilaterals.
+
+- The Image "resize", "rotate", and "transform" methods now support
+ Image.BILINEAR (2x2) and Image.BICUBIC (4x4) resampling filters.
+ Filters can be used with all transform methods.
+
+- The ImageDraw "rectangle" method now includes both the right
+ and the bottom edges when drawing filled rectangles.
+
+- The TGA decoder now works properly for runlength encoded images
+ which have more than one byte per pixel.
+
+- "getbands" on an YCbCr image now returns ("Y", "Cb", "Cr")
+
+- Some file drivers didn't handle the optional "modify" argument
+ to the load method. This resulted in exceptions when you used
+ "paste" (and other methods that modify an image in place) on a
+ newly opened file.
+
+0.3b2
+-----
+
+The test suite includes 825 individual tests.
+
+- An Image "getbands" method has been added. It returns a tuple
+ containing the individual band names for this image. To figure
+ out how many bands an image has, use "len(im.getbands())".
+
+- An Image "putpixel" method has been added.
+
+- The Image "point" method can now be used to convert "L" images
+ to any other format, via a lookup table. That table should
+ contain 256 values for each band in the output image.
+
+- Some file drivers (including FLI/FLC, GIF, and IM) accidentally
+ overwrote the offset method with an internal attribute. All
+ drivers have been updated to use private attributes where
+ possible.
+
+- The Image "histogram" method now works for "I" and "F" images.
+ For these modes, PIL divides the range between the min and
+ max values used in the image into 256 bins. You can also
+ pass in your own min and max values via the "extrema" option::
+
+ h = im.histogram(extrema=(0, 255))
+
+- An Image "getextrema" method has been added. It returns the
+ min and max values used in the image. In this release, this
+ works for single band images only.
+
+- Changed the PNG driver to load and save mode "I" images as
+ 16-bit images. When saving, values outside the range 0..65535
+ are clipped.
+
+- Fixed ImageFont.py to work with the new "pilfont" compiler.
+
+- Added JPEG "save" and "draft" support for mode "YCbCr" images.
+ Note that if you save an "YCbCr" image as a JPEG file and read
+ it back, it is read as an RGB file. To get around this, you
+ can use the "draft" method::
+
+ im = Image.open("color.jpg")
+ im.draft("YCbCr", im.size)
+
+- Read "RGBA" TGA images. Also fixed the orientation bug; all
+ images should now come out the right way.
+
+- Changed mode name (and internal representation) from "YCrCb"
+ to "YCbCr" (!)
+ **WARNING: MAY BREAK EXISTING CODE**
+
+0.3b1
+-----
+
+The test suite includes 750 individual tests.
+
+- The "pilfont" package is now included in the standard PIL
+ distribution. The pilfont utility can be used to convert
+ X BDF and PCF raster font files to a format understood by
+ the ImageFont module.
+
+- GIF files are now interlaced by default. To write a
+ non-interlaced file, pass interlace=0 to the "save"
+ method.
+
+- The default string format has changed for the "fromstring"
+ and "tostring" methods.
+ **WARNING: MAY BREAK EXISTING CODE**
+
+ NOTE: If no extra arguments are given, the first line in
+ the string buffer is the top line of the image, instead of
+ the bottom line. For RGB images, the string now contains
+ 3 bytes per pixel instead of 4. These changes were made
+ to make the methods compatible with the "fromstring"
+ factory function.
+
+ To get the old behaviour, use the following syntax::
+
+ data = im.tostring("raw", "RGBX", 0, -1)
+ im.fromstring(data, "raw", "RGBX", 0, -1)
+
+- "new" no longer gives a MemoryError if the width or height
+ is zero (this only happened on platforms where malloc(0)
+ or calloc(0) returns NULL).
+
+- "new" now adds a default palette object to "P" images.
+
+- You can now convert directly between all modes supported by
+ PIL. When converting colour images to "P", PIL defaults to
+ a "web" palette and dithering. When converting greyscale
+ images to "1", PIL uses a thresholding and dithering.
+
+- Added a "dither" option to "convert". By default, "convert"
+ uses floyd-steinberg error diffusion for "P" and "1" targets,
+ so this option is only used to *disable* dithering. Allowed
+ values are NONE (no dithering) or FLOYDSTEINBERG (default).
+ ::
+
+ imOut = im.convert("P", dither=Image.NONE)
+
+- Added a full set of "I" decoders. You can use "fromstring"
+ (and file decoders) to read any standard integer type as an
+ "I" image.
+
+- Added some support for "YCbCr" images (creation, conversion
+ from/to "L" and "RGB", IM YCC load/save)
+
+- "getpixel" now works properly with fractional coordinates.
+
+- ImageDraw "setink" now works with "I", "F", "RGB", "RGBA",
+ "RGBX", "CMYK", and "YCbCr" images.
+
+- ImImagePlugin no longer attaches palettes to "RGB" images.
+
+- Various minor fixes.
+
+0.3a4
+-----
+
+- Added experimental IPTC/NAA support.
+
+- Eliminated AttributeError exceptions after "crop" (from
+ Skip Montanaro)
+
+- Reads some uncompressed formats via memory mapping (this
+ is currently supported on Win32 only)
+
+- Fixed some last minute glitches in the last alpha release
+ (Types instead of types in Image.py, version numbers, etc.)
+
+- Eliminated some more bogus compiler warnings.
+
+- Various fixes to make PIL compile and run smoother on Macs
+ (from Jack Jansen).
+
+- Fixed "fromstring" and "tostring" for mode "I" images.
+
+0.3a3
+-----
+
+The test suite includes 530 individual tests.
+
+- Eliminated unexpected side-effect in "paste" with matte. "paste"
+ now works properly also if compiled with "gcc".
+
+- Adapted to Python 1.5 (build issues only)
+
+- Fixed the ImageDraw "point" method to draw also the last
+ point (!).
+
+- Added "I" and "RGBX" support to Image.new.
+
+- The plugin path is now properly prepended to the module search
+ path when a plugin module is imported.
+
+- Added "draw" method to the ImageWin.Dib class. This is used by
+ Topaz to print images on Windows printers.
+
+- "convert" now supports conversions from "P" to "1" and "F".
+
+- "paste" can now take a colour instead of an image as the first argument.
+ The colour must match the colour argument given to the new function, and
+ match the mode of the target image.
+
+- Fixed "paste" to allow a mask also for mode "F" images.
+
+- The BMP driver now saves mode "1" images. When loading images, the mode
+ is set to "L" for 8-bit files with greyscale palettes, and to "P" for
+ other 8-bit files.
+
+- The IM driver now reads and saves "1" images (file modes "0 1" or "L 1").
+
+- The JPEG and GIF drivers now saves "1" images. For JPEG, the image
+ is saved as 8-bit greyscale (it will load as mode "L"). For GIF, the
+ image will be loaded as a "P" image.
+
+- Fixed a potential buffer overrun in the GIF encoder.
+
+0.3a2
+-----
+
+The test suite includes 400 individual tests.
+
+- Improvements to the test suite revealed a number of minor bugs, which
+ are all fixed. Note that crop/paste, 32-bit ImageDraw, and ImageFont
+ are still weak spots in this release.
+
+- Added "putpalette" method to the Image class. You can use this
+ to add or modify the palette for "P" and "L" images. If a palette
+ is added to an "L" image, it is automatically converted to a "P"
+ image.
+
+- Fixed ImageDraw to properly handle 32-bit image memories
+ ("RGB", "RGBA", "CMYK", "F")
+
+- Fixed "fromstring" and "tostring" not to mess up the mode attribute
+ in default mode.
+
+- Changed ImPlatform.h to work on CRAY's (don't have one at home, so I
+ haven't tried it). The previous version assumed that either "short"
+ or "int" were 16-bit wide. PIL still won't compile on platforms where
+ neither "short", "int" nor "long" are 32-bit wide.
+
+- Added file= and data= keyword arguments to PhotoImage and BitmapImage.
+ This allows you to use them as drop-in replacements for the corresponding
+ Tkinter classes.
+
+- Removed bogus references to the crack coder (ImagingCrack).
+
+0.3a1
+-----
+
+- Make sure image is loaded in "tostring".
+
+- Added floating point packer (native 32-bit floats only).
+
+0.1b1 to 0.2 (b5)
+-----------------
+
+- Modified "fromstring" and "tostring" methods to use file codecs.
+ Also added "fromstring" factory method to create an image directly
+ from data in a string.
+
+- Added support for 32-bit floating point images (mode "F"). You
+ can convert between "L" and "F" images, and apply a subset of the
+ available image processing methods on the "F" image. You can also
+ read virtually any data format into a floating point image memory;
+ see the section on "Decoding Floating Point Data" in the handbook
+ for more information.
+
+0.2b5 released; on windows only
+-------------------------------
+
+- Fixed the tobitmap() method to work properly for small bitmaps.
+
+- Added RMS and standard deviation to the ImageStat.Stat class. Also
+ modified the constructor to take an optional feature mask, and also
+ to accept either an image or a list containing the histogram data.
+
+- The BitmapImage code in ImageTk can now use a special bitmap
+ decoder, which has to be patched into Tk. See the "Tk/pilbitmap.txt"
+ file for details. If not installed, bitmaps are transferred to Tk as
+ XBM strings.
+
+- The PhotoImage code in ImageTk now uses a Tcl command ("PyImagingPaste")
+ instead of a special image type. This gives somewhat better performance,
+ and also allows PIL to support transparency.
+ **WARNING: TKAPPINIT MUST BE MODIFIED**
+
+- ImageTk now honours the alpha layer in RGBA images. Only fully
+ transparent pixels are made transparent (that is, the alpha layer
+ is treated as a mask). To treat the alpha laters as a matte, you
+ must paste the image on the background before handing it over to
+ ImageTk.
+
+- Added McIdas reader (supports 8-bit images only).
+
+- PIL now preloads drivers for BMP, GIF, JPEG, PPM, and TIFF. As
+ long as you only load and save these formats, you don't have to
+ wait for a full scan for drivers. To force scanning, call the
+ Image.init() function.
+
+- The "seek" and "tell" methods are now always available, also for
+ single-frame images.
+
+- Added optional mask argument to histogram method. The mask may
+ be an "1" or "L" image with the same size as the original image.
+ Only pixels where the mask is non-zero are included in the
+ histogram.
+
+- The "paste" method now allows you to specify only the lower left
+ corner (a 2-tuple), instead of the full region (a 4-tuple).
+
+- Reverted to old plugin scanning model; now scans all directory
+ names in the path when looking for plugins.
+
+- Added PIXAR raster support. Only uncompressed ("dumped") RGB
+ images can currently be read (based on information provided
+ by Greg Coats).
+
+- Added FlashPix (FPX) read support. Reads all pixel formats, but
+ only the highest resolution is read, and the viewing transform is
+ currently ignored.
+
+- Made PNG encoding somewhat more efficient in "optimize" mode; a
+ bug in 0.2b4 didn't enable all predictor filters when optimized
+ storage were requested.
+
+- Added Microsoft Image Composer (MIC) read support. When opened,
+ the first sprite in the file is loaded. You can use the seek method
+ to load additional sprites from the file.
+
+- Properly reads "P" and "CMYK" PSD images.
+
+- "pilconvert" no longer optimizes by default; use the -o option to
+ make the file as small as possible (at the expense of speed); use
+ the -q option to set the quality when compressing to JPEG.
+
+- Fixed "crop" not to drop the palette for "P" images.
+
+- Added and verified FLC support.
+
+- Paste with "L" or "RGBA" alpha is now several times faster on most
+ platforms.
+
+- Changed Image.new() to initialize the image to black, as described
+ in the handbook. To get an uninitialized image, use None as the
+ colour.
+
+- Fixed the PDF encoder to produce a valid header; Acrobat no longer
+ complains when you load PDF images created by PIL.
+
+- PIL only scans fully-qualified directory names in the path when
+ looking for plugins.
+ **WARNING: MAY BREAK EXISTING CODE**
+
+- Faster implementation of "save" used when filename is given,
+ or when file object has "fileno" and "flush" methods.
+
+- Don't crash in "crop" if region extends outside the source image.
+
+- Eliminated a massive memory leak in the "save" function.
+
+- The GIF decoder doesn't crash if the code size is set to an illegal
+ value. This could happen since another bug didn't handle local
+ palettes properly if they didn't have the same size as the
+ global palette (not very common).
+
+- Added predictor support (TIFF 6.0 section 14) to the TIFF decoder.
+
+- Fixed palette and padding problems in BMP driver. Now properly
+ writes "1", "L", "P" and "RGB" images.
+
+- Fixed getpixel()/getdata() to return correct pixel values.
+
+- Added PSD (PhotoShop) read support. Reads both uncompressed
+ and compressed images of most types.
+
+- Added GIF write support (writes "uncompressed" GIF files only,
+ due to unresolvable licensing issues). The "gifmaker.py" script
+ can be used to create GIF animations.
+
+- Reads 8-bit "L" and "P" TGA images. Also reads 16-bit "RGB"
+ images.
+
+- Added FLI read support. This driver has only been tested
+ on a few FLI samples.
+
+- Reads 2-bit and 4-bit PCX images.
+
+- Added MSP read and write support. Both version 1 and 2 can be
+ read, but only version 1 (uncompressed) files are written.
+
+- Fixed a bug in the FLI/FLC identification code that caused the
+ driver to raise an exception when parsing valid FLI/FLC files.
+
+- Improved performance when loading file format plugins, and when
+ opening files.
+
+- Added GIF animation support, via the "seek" and "tell" methods.
+ You can use "player.py" to play an animated GIF file.
+
+- Removed MNG support, since the spec is changing faster than I
+ can change the code. I've added support for the experimental
+ ARG format instead. Contact me for more information on this
+ format.
+
+- Added keyword options to the "save" method. The following options
+ are currently supported:
+
+ .. list-table::
+ :widths: 25 25 50
+ :header-rows: 1
+
+ * - Format
+ - Option
+ - Description
+ * - JPEG
+ - optimize
+ - Minimize output file at the expense of compression speed.
+ * - JPEG
+ - progressive
+ - Enable progressive output. The option value is ignored.
+ * - JPEG
+ - quality
+ - Set compression quality (1-100). The default value is 75.
+ * - JPEG
+ - smooth
+ - Smooth dithered images. Value is strength (1-100). Default is off (0).
+ * - PNG
+ - optimize
+ - Minimize output file at the expense of compression speed.
+
+ Expect more options in future releases. Also note that
+ file writers silently ignore unknown options.
+
+- Plugged memory leaks in the PNG and TIFF decoders.
+
+- Added PNG write support.
+
+- (internal) RGB unpackers and converters now set the pad byte
+ to 255 (full opacity).
+
+- Properly handles the "transparency" property for GIF, PNG
+ and XPM files.
+
+- Added a "putalpha" method, allowing you to attach a "1" or "L"
+ image as the alpha layer to an "RGBA" image.
+
+- Various improvements to the sample scripts:
+
+ .. list-table::
+ :widths: 25 75
+
+ * - pilconvert
+ - Carries out some extra tricks in order to make
+ the resulting file as small as possible.
+ * - explode
+ - (NEW) Split an image sequence into individual frames.
+ * - gifmaker
+ - (NEW) Convert a sequence file into a GIF animation.
+ Note that the GIF encoder create "uncompressed" GIF
+ files, so animations created by this script are
+ rather large (typically 2-5 times the compressed
+ sizes).
+ * - image2py
+ - (NEW) Convert a single image to a python module. See
+ comments in this script for details.
+ * - player
+ - If multiple images are given on the command line,
+ they are interpreted as frames in a sequence. The
+ script assumes that they all have the same size.
+ Also note that this script now can play FLI/FLC
+ and GIF animations.
+
+ This player can also execute embedded Python
+ animation applets (ARG format only).
+ * - viewer
+ - Transparent images ("P" with transparency property,
+ and "RGBA") are superimposed on the standard Tk background.
+
+- Fixed colour argument to "new". For multilayer images, pass a
+ tuple: (Red, Green, Blue), (Red, Green, Blue, Alpha), or (Cyan,
+ Magenta, Yellow, Black).
+
+- Added XPM (X pixmap) read support.
+
+0.2b3
+-----
+
+- Added MNG (multi-image network graphics) read support. "Ming"
+ is a proposed animation standard, based on the PNG file format.
+
+ You can use the "player" sample script to display some flavours
+ of this format. The MNG standard is still under development,
+ as is this driver. More information, including sample files,
+ can be found at <ftp://swrinde.nde.swri.edu/pub/mng>
+
+- Added a "verify" method to images loaded from file. This method
+ scans the file for errors, without actually decoding the image
+ data, and raises a suitable exception if it finds any problems.
+ Currently implemented for PNG and MNG files only.
+
+- Added support for interlaced GIF images.
+
+- Added PNG read support -- if linked with the ZLIB compression library,
+ PIL reads all kinds of PNG images, except interlaced files.
+
+- Improved PNG identification support -- doesn't mess up on unknown
+ chunks, identifies all possible PNG modes, and verifies checksum
+ on PNG header chunks.
+
+- Added an experimental reader for placable Windows Meta Files (WMF).
+ This reader is still very incomplete, but it illustrates how PIL's
+ drawing capabilities can be used to render vector and metafile
+ formats.
+
+- Added restricted drivers for images from Image Tools (greyscale
+ only) and LabEye/IFUNC (common interchange modes only).
+
+- Some minor improvements to the sample scripts provided in the
+ "Scripts" directory.
+
+- The test images have been moved to the "Images" directory.
+
+0.2b2 released. 0.2b1 released for Windows only
+-----------------------------------------------
+
+- Fixed filling of complex polygons. The ImageDraw "line" and
+ "polygon" methods also accept Path objects.
+
+- The ImageTk "PhotoImage" object can now be constructed directly
+ from an image. You can also pass the object itself to Tkinter,
+ instead of using the "image" attribute. Finally, using "paste"
+ on a displayed image automatically updates the display.
+
+- The ImageTk "BitmapImage" object allows you to create transparent
+ overlays from 1-bit images. You can pass the object itself to
+ Tkinter. The constructor takes the same arguments as the Tkinter
+ BitmapImage class; use the "foreground" option to set the colour
+ of the overlay.
+
+- Added a "putdata" method to the Image class. This can be used to
+ load a 1-layer image with data from a sequence object or a string.
+ An optional floating point scale and offset can be used to adjust
+ the data to fit into the 8-bit pixel range. Also see the "getdata"
+ method.
+
+- Added the EXTENT method to the Image "transform" method. This can
+ be used to quickly crop, stretch, shrink, or mirror a subregion
+ from another image.
+
+- Adapted to Python 1.4.
+
+- Added a project makefile for Visual C++ 4.x. This allows you to
+ easily build a dynamically linked version of PIL for Windows 95
+ and NT.
+
+- A Tk "booster" patch for Windows is available. It gives dramatic
+ performance improvements for some displays. Has been tested with
+ Tk 4.2 only, but is likely to work with Tk 4.1 as well. See the Tk
+ subdirectory for details.
+
+- You can now save 1-bit images in the XBM format. In addition, the
+ Image class now provides a "tobitmap" method which returns a string
+ containing an XBM representation of the image. Quite handy to use
+ with Tk.
+
+- More conversions, including "RGB" to "1" and more.
+
+0.2a1
+-----
+
+- Where earlier versions accepted lists, this version accepts arbitrary
+ Python sequences (including strings, in some cases). A few resource
+ leaks were plugged in the process.
+
+- The Image "paste" method now allows the box to extend outside
+ the target image. The size of the box, the image to be pasted,
+ and the optional mask must still match.
+
+- The ImageDraw module now supports filled polygons, outlined and
+ filled ellipses, and text. Font support is rudimentary, though.
+
+- The Image "point" method now takes an optional mode argument,
+ allowing you to convert the image while translating it. Currently,
+ this can only be used to convert "L" or "P" images to "1" images
+ (creating thresholded images or "matte" masks).
+
+- An Image "getpixel" method has been added. For single band images,
+ it returns the pixel value at a given position as an integer.
+ For n-band images, it returns an n-tuple of integers.
+
+- An Image "getdata" method has been added. It returns a sequence
+ object representing the image as a 1-dimensional array. Only len()
+ and [] can be used with this sequence. This method returns a
+ reference to the existing image data, so changes in the image
+ will be immediately reflected in the sequence object.
+
+- Fixed alignment problems in the Windows BMP writer.
+
+- If converting an "RGB" image to "RGB" or "L", you can give a second
+ argument containing a colour conversion matrix.
+
+- An Image "getbbox" method has been added. It returns the bounding
+ box of data in an image, considering the value 0 as background.
+
+- An Image "offset" method has been added. It returns a new image
+ where the contents of the image have been offset the given distance
+ in X and/or Y direction. Data wraps between edges.
+
+- Saves PDF images. The driver creates a binary PDF 1.1 files, using
+ JPEG compression for "L", "RGB", and "CMYK" images, and hex encoding
+ (same as for PostScript) for other formats.
+
+- The "paste" method now accepts "1" masks. Zero means transparent,
+ any other pixel value means opaque. This is faster than using an
+ "L" transparency mask.
+
+- Properly writes EPS files (and properly prints images to PostScript
+ printers as well).
+
+- Reads 4-bit BMP files, as well as 4 and 8-bit Windows ICO and CUR
+ files. Cursor animations are not supported.
+
+- Fixed alignment problems in the Sun raster loader.
+
+- Added "draft" and "thumbnail" methods. The draft method is used
+ to optimize loading of JPEG and PCD files, the thumbnail method is
+ used to create a thumbnail representation of an image.
+
+- Added Windows display support, via the ImageWin class (see the
+ handbook for details).
+
+- Added raster conversion for EPS files. This requires GNU or Aladdin
+ Ghostscript, and probably works on UNIX only.
+
+- Reads PhotoCD (PCD) images. The base resolution (768x512) can be
+ read from a PhotoCD file.
+
+- Eliminated some compiler warnings. Bindings now compile cleanly in C++
+ mode. Note that the Imaging library itself must be compiled in C mode.
+
+- Added "bdf2pil.py", which converts BDF fonts into images with associated
+ metrics. This is definitely work in progress. For info, see description
+ in script for details.
+
+- Fixed a bug in the "ImageEnhance.py" module.
+
+- Fixed a bug in the netpbm save hack in "GifImagePlugin.py"
+
+- Fixed 90 and 270 degree rotation of rectangular images.
+
+- Properly reads 8-bit TIFF palette-color images.
+
+- Reads plane separated RGB and CMYK TIFF images.
+
+- Added driver debug mode. This is enabled by setting Image.DEBUG
+ to a non-zero value. Try the -D option to "pilfile.py" and see what
+ happens.
+
+- Don't crash on "atend" constructs in PostScript files.
+
+- Only the Image module imports _imaging directly. Other modules
+ should refer to the binding module as "Image.core".
+
+0.0 to 0.1 (b1)
+---------------
+
+- A handbook is available (distributed separately).
+
+- The coordinate system is changed so that (0,0) is now located
+ in the upper left corner. This is in compliancy with ISO 12087
+ and 90% of all other image processing and graphics libraries.
+
+- Modes "1" (bilevel) and "P" (palette) have been introduced. Note
+ that bilevel images are stored with one byte per pixel.
+
+- The Image "crop" and "paste" methods now accepts None as the
+ box argument, to refer to the full image (self, that is).
+
+- The Image "crop" method now works properly.
+
+- The Image "point" method is now available. You can use either a
+ lookup table or a function taking one argument.
+
+- The Image join function has been renamed to "merge".
+
+- An Image "composite" function has been added. It is identical
+ to copy() followed by paste(mask).
+
+- An Image "eval" function has been added. It is currently identical
+ to point(function); that is, only a single image can be processed.
+
+- A set of channel operations has been added. See the "ImageChops"
+ module, test_chops.py, and the handbook for details.
+
+- Added the "pilconvert" utility, which converts image files. Note
+ that the number of output formats are still quite restricted.
+
+- Added the "pilfile" utility, which quickly identifies image files
+ (without loading them, in most cases).
+
+- Added the "pilprint" utility, which prints image files to PostScript
+ printers.
+
+- Added a rudimentary version of the "pilview" utility, which is
+ simple image viewer based on Tk. Only File/Exit and Image/Next
+ works properly.
+
+- An interface to Tk has been added. See "Lib/ImageTk.py" and README
+ for details.
+
+- An interface to Jack Jansen's Img library has been added (thanks to
+ Jack). This allows you to read images through the Img extensions file
+ format handlers. See the file "Lib/ImgExtImagePlugin.py" for details.
+
+- PostScript printing is provided through the PSDraw module. See the
+ handbook for details.
diff --git a/contrib/python/Pillow/py3/LICENSE b/contrib/python/Pillow/py3/LICENSE
new file mode 100644
index 00000000000..cf65e86d734
--- /dev/null
+++ b/contrib/python/Pillow/py3/LICENSE
@@ -0,0 +1,30 @@
+The Python Imaging Library (PIL) is
+
+ Copyright © 1997-2011 by Secret Labs AB
+ Copyright © 1995-2011 by Fredrik Lundh
+
+Pillow is the friendly PIL fork. It is
+
+ Copyright © 2010-2023 by Jeffrey A. Clark (Alex) and contributors.
+
+Like PIL, Pillow is licensed under the open source HPND License:
+
+By obtaining, using, and/or copying this software and/or its associated
+documentation, you agree that you have read, understood, and will comply
+with the following terms and conditions:
+
+Permission to use, copy, modify and distribute this software and its
+documentation for any purpose and without fee is hereby granted,
+provided that the above copyright notice appears in all copies, and that
+both that copyright notice and this permission notice appear in supporting
+documentation, and that the name of Secret Labs AB or the author not be
+used in advertising or publicity pertaining to distribution of the software
+without specific, written prior permission.
+
+SECRET LABS AB AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS
+SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS.
+IN NO EVENT SHALL SECRET LABS AB OR THE AUTHOR BE LIABLE FOR ANY SPECIAL,
+INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+PERFORMANCE OF THIS SOFTWARE.
diff --git a/contrib/python/Pillow/py3/PIL/BdfFontFile.py b/contrib/python/Pillow/py3/PIL/BdfFontFile.py
new file mode 100644
index 00000000000..161954831ae
--- /dev/null
+++ b/contrib/python/Pillow/py3/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 . import FontFile, Image
+
+bdf_slant = {
+ "R": "Roman",
+ "I": "Italic",
+ "O": "Oblique",
+ "RI": "Reverse Italic",
+ "RO": "Reverse Oblique",
+ "OT": "Other",
+}
+
+bdf_spacing = {"P": "Proportional", "M": "Monospaced", "C": "Cell"}
+
+
+def bdf_char(f):
+ # skip to STARTCHAR
+ while True:
+ s = f.readline()
+ if not s:
+ return None
+ if s[:9] == b"STARTCHAR":
+ break
+ id = s[9:].strip().decode("ascii")
+
+ # load symbol properties
+ props = {}
+ while True:
+ s = f.readline()
+ if not s or s[:6] == b"BITMAP":
+ break
+ i = s.find(b" ")
+ props[s[:i].decode("ascii")] = s[i + 1 : -1].decode("ascii")
+
+ # load bitmap
+ bitmap = []
+ while True:
+ s = f.readline()
+ if not s or s[:7] == b"ENDCHAR":
+ break
+ bitmap.append(s[:-1])
+ bitmap = b"".join(bitmap)
+
+ # 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):
+ super().__init__()
+
+ s = fp.readline()
+ if s[:13] != 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[:13] == 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/contrib/python/Pillow/py3/PIL/BlpImagePlugin.py b/contrib/python/Pillow/py3/PIL/BlpImagePlugin.py
new file mode 100644
index 00000000000..398696d5c77
--- /dev/null
+++ b/contrib/python/Pillow/py3/PIL/BlpImagePlugin.py
@@ -0,0 +1,474 @@
+"""
+Blizzard Mipmap Format (.blp)
+Jerome Leclanche <[email protected]>
+
+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.
+"""
+
+import os
+import struct
+from enum import IntEnum
+from io import BytesIO
+
+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):
+ return ((i >> 11) & 0x1F) << 3, ((i >> 5) & 0x3F) << 2, (i & 0x1F) << 3
+
+
+def decode_dxt1(data, alpha=False):
+ """
+ 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 in range(blocks):
+ # Decode next 8-byte block.
+ idx = block * 8
+ color0, color1, bits = struct.unpack_from("<HHI", data, idx)
+
+ r0, g0, b0 = unpack_565(color0)
+ r1, g1, b1 = unpack_565(color1)
+
+ # Decode this block into 4x4 pixels
+ # Accumulate the results onto our 4 row accumulators
+ for j in range(4):
+ for i in range(4):
+ # get next control op and generate a pixel
+
+ control = bits & 3
+ bits = bits >> 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):
+ """
+ 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 in range(blocks):
+ idx = block * 16
+ block = data[idx : idx + 16]
+ # Decode next 16-byte block.
+ bits = struct.unpack_from("<8B", block)
+ color0, color1 = struct.unpack_from("<HH", block, 8)
+
+ (code,) = struct.unpack_from("<I", block, 12)
+
+ r0, g0, b0 = unpack_565(color0)
+ r1, g1, b1 = unpack_565(color1)
+
+ for j in range(4):
+ high = False # Do we want the higher bits?
+ for i in range(4):
+ alphacode_index = (4 * j + i) // 2
+ a = bits[alphacode_index]
+ if high:
+ high = False
+ a >>= 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):
+ """
+ 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 in range(blocks):
+ idx = block * 16
+ block = data[idx : idx + 16]
+ # Decode next 16-byte block.
+ a0, a1 = struct.unpack_from("<BB", block)
+
+ bits = struct.unpack_from("<6B", block, 2)
+ alphacode1 = bits[2] | (bits[3] << 8) | (bits[4] << 16) | (bits[5] << 24)
+ alphacode2 = bits[0] | (bits[1] << 8)
+
+ color0, color1 = struct.unpack_from("<HH", block, 8)
+
+ (code,) = struct.unpack_from("<I", block, 12)
+
+ r0, g0, b0 = unpack_565(color0)
+ r1, g1, b1 = unpack_565(color1)
+
+ for j in range(4):
+ for i in range(4):
+ # get next control op and generate a pixel
+ alphacode_index = 3 * (4 * j + i)
+
+ if alphacode_index <= 12:
+ alphacode = (alphacode2 >> 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):
+ return prefix[:4] in (b"BLP1", b"BLP2")
+
+
+class BlpImageFile(ImageFile.ImageFile):
+ """
+ Blizzard Mipmap Format
+ """
+
+ format = "BLP"
+ format_description = "Blizzard Mipmap Format"
+
+ def _open(self):
+ self.magic = self.fp.read(4)
+
+ self.fp.seek(5, os.SEEK_CUR)
+ (self._blp_alpha_depth,) = struct.unpack("<b", self.fp.read(1))
+
+ self.fp.seek(2, os.SEEK_CUR)
+ self._size = struct.unpack("<II", self.fp.read(8))
+
+ if self.magic in (b"BLP1", b"BLP2"):
+ decoder = self.magic.decode()
+ else:
+ msg = f"Bad BLP magic {repr(self.magic)}"
+ raise BLPFormatError(msg)
+
+ self._mode = "RGBA" if self._blp_alpha_depth else "RGB"
+ self.tile = [(decoder, (0, 0) + self.size, 0, (self.mode, 0, 1))]
+
+
+class _BLPBaseDecoder(ImageFile.PyDecoder):
+ _pulls_fd = True
+
+ def decode(self, buffer):
+ try:
+ self._read_blp_header()
+ self._load()
+ except struct.error as e:
+ msg = "Truncated BLP file"
+ raise OSError(msg) from e
+ return -1, 0
+
+ def _read_blp_header(self):
+ self.fd.seek(4)
+ (self._blp_compression,) = struct.unpack("<i", self._safe_read(4))
+
+ (self._blp_encoding,) = struct.unpack("<b", self._safe_read(1))
+ (self._blp_alpha_depth,) = struct.unpack("<b", self._safe_read(1))
+ (self._blp_alpha_encoding,) = struct.unpack("<b", self._safe_read(1))
+ self.fd.seek(1, os.SEEK_CUR) # mips
+
+ self.size = struct.unpack("<II", self._safe_read(8))
+
+ if isinstance(self, BLP1Decoder):
+ # Only present for BLP1
+ (self._blp_encoding,) = struct.unpack("<i", self._safe_read(4))
+ self.fd.seek(4, os.SEEK_CUR) # subtype
+
+ self._blp_offsets = struct.unpack("<16I", self._safe_read(16 * 4))
+ self._blp_lengths = struct.unpack("<16I", self._safe_read(16 * 4))
+
+ def _safe_read(self, length):
+ return ImageFile._safe_read(self.fd, length)
+
+ def _read_palette(self):
+ 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):
+ data = bytearray()
+ _data = BytesIO(self._safe_read(self._blp_lengths[0]))
+ while True:
+ try:
+ (offset,) = struct.unpack("<B", _data.read(1))
+ except struct.error:
+ break
+ b, g, r, a = palette[offset]
+ d = (r, g, b)
+ if self._blp_alpha_depth:
+ d += (a,)
+ data.extend(d)
+ return data
+
+
+class BLP1Decoder(_BLPBaseDecoder):
+ def _load(self):
+ if self._blp_compression == Format.JPEG:
+ self._decode_jpeg_stream()
+
+ elif self._blp_compression == 1:
+ if self._blp_encoding in (4, 5):
+ palette = self._read_palette()
+ data = self._read_bgra(palette)
+ self.set_as_raw(bytes(data))
+ else:
+ msg = f"Unsupported BLP encoding {repr(self._blp_encoding)}"
+ raise BLPFormatError(msg)
+ else:
+ msg = f"Unsupported BLP compression {repr(self._blp_encoding)}"
+ raise BLPFormatError(msg)
+
+ def _decode_jpeg_stream(self):
+ from .JpegImagePlugin import JpegImageFile
+
+ (jpeg_header_size,) = struct.unpack("<I", self._safe_read(4))
+ jpeg_header = self._safe_read(jpeg_header_size)
+ self._safe_read(self._blp_offsets[0] - self.fd.tell()) # What IS this?
+ data = self._safe_read(self._blp_lengths[0])
+ data = jpeg_header + data
+ data = BytesIO(data)
+ image = JpegImageFile(data)
+ Image._decompression_bomb_check(image.size)
+ if image.mode == "CMYK":
+ decoder_name, extents, offset, args = image.tile[0]
+ image.tile = [(decoder_name, extents, offset, (args[0], "CMYK"))]
+ r, g, b = image.convert("RGB").split()
+ image = Image.merge("RGB", (b, g, r))
+ self.set_as_raw(image.tobytes())
+
+
+class BLP2Decoder(_BLPBaseDecoder):
+ def _load(self):
+ palette = self._read_palette()
+
+ self.fd.seek(self._blp_offsets[0])
+
+ if self._blp_compression == 1:
+ # Uncompressed or DirectX compression
+
+ if self._blp_encoding == Encoding.UNCOMPRESSED:
+ data = self._read_bgra(palette)
+
+ elif self._blp_encoding == Encoding.DXT:
+ data = bytearray()
+ if self._blp_alpha_encoding == AlphaEncoding.DXT1:
+ linesize = (self.size[0] + 3) // 4 * 8
+ for yb in range((self.size[1] + 3) // 4):
+ for d in decode_dxt1(
+ self._safe_read(linesize), alpha=bool(self._blp_alpha_depth)
+ ):
+ data += d
+
+ elif self._blp_alpha_encoding == AlphaEncoding.DXT3:
+ linesize = (self.size[0] + 3) // 4 * 16
+ for yb in range((self.size[1] + 3) // 4):
+ for d in decode_dxt3(self._safe_read(linesize)):
+ data += d
+
+ elif self._blp_alpha_encoding == AlphaEncoding.DXT5:
+ linesize = (self.size[0] + 3) // 4 * 16
+ for yb in range((self.size[1] + 3) // 4):
+ for d in decode_dxt5(self._safe_read(linesize)):
+ data += d
+ else:
+ msg = f"Unsupported alpha encoding {repr(self._blp_alpha_encoding)}"
+ raise BLPFormatError(msg)
+ else:
+ msg = f"Unknown BLP encoding {repr(self._blp_encoding)}"
+ raise BLPFormatError(msg)
+
+ else:
+ msg = f"Unknown BLP compression {repr(self._blp_compression)}"
+ raise BLPFormatError(msg)
+
+ self.set_as_raw(bytes(data))
+
+
+class BLPEncoder(ImageFile.PyEncoder):
+ _pushes_fd = True
+
+ def _write_palette(self):
+ data = b""
+ 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):
+ palette_data = self._write_palette()
+
+ offset = 20 + 16 * 4 * 2 + len(palette_data)
+ data = struct.pack("<16I", offset, *((0,) * 15))
+
+ 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("<B", self.im.getpixel((x, y)))
+
+ return len(data), 0, data
+
+
+def _save(im, fp, filename):
+ 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)
+
+ fp.write(struct.pack("<i", 1)) # Uncompressed or DirectX compression
+ fp.write(struct.pack("<b", Encoding.UNCOMPRESSED))
+ fp.write(struct.pack("<b", 1 if im.palette.mode == "RGBA" else 0))
+ fp.write(struct.pack("<b", 0)) # alpha encoding
+ fp.write(struct.pack("<b", 0)) # mips
+ fp.write(struct.pack("<II", *im.size))
+ if magic == b"BLP1":
+ fp.write(struct.pack("<i", 5))
+ fp.write(struct.pack("<i", 0))
+
+ ImageFile._save(im, fp, [("BLP", (0, 0) + im.size, 0, im.mode)])
+
+
+Image.register_open(BlpImageFile.format, BlpImageFile, _accept)
+Image.register_extension(BlpImageFile.format, ".blp")
+Image.register_decoder("BLP1", BLP1Decoder)
+Image.register_decoder("BLP2", BLP2Decoder)
+
+Image.register_save(BlpImageFile.format, _save)
+Image.register_encoder("BLP", BLPEncoder)
diff --git a/contrib/python/Pillow/py3/PIL/BmpImagePlugin.py b/contrib/python/Pillow/py3/PIL/BmpImagePlugin.py
new file mode 100644
index 00000000000..9abfd0b5b8d
--- /dev/null
+++ b/contrib/python/Pillow/py3/PIL/BmpImagePlugin.py
@@ -0,0 +1,471 @@
+#
+# The Python Imaging Library.
+# $Id$
+#
+# BMP file handler
+#
+# Windows (and OS/2) native bitmap storage format.
+#
+# history:
+# 1995-09-01 fl Created
+# 1996-04-30 fl Added save
+# 1997-08-27 fl Fixed save of 1-bit images
+# 1998-03-06 fl Load P images as L where possible
+# 1998-07-03 fl Load P images as 1 where possible
+# 1998-12-29 fl Handle small palettes
+# 2002-12-30 fl Fixed load of 1-bit palette images
+# 2003-04-21 fl Fixed load of 1-bit monochrome images
+# 2003-04-23 fl Added limited support for BI_BITFIELDS compression
+#
+# 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.
+#
+
+
+import os
+
+from . import Image, ImageFile, ImagePalette
+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
+
+#
+# --------------------------------------------------------------------
+# Read BMP file
+
+BIT2MODE = {
+ # bits => mode, rawmode
+ 1: ("P", "P;1"),
+ 4: ("P", "P;4"),
+ 8: ("P", "P"),
+ 16: ("RGB", "BGR;15"),
+ 24: ("RGB", "BGR"),
+ 32: ("RGB", "BGRX"),
+}
+
+
+def _accept(prefix):
+ return prefix[:2] == b"BM"
+
+
+def _dib_accept(prefix):
+ return i32(prefix) in [12, 40, 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=0, offset=0):
+ """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 = {"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
+ header_data = ImageFile._safe_read(self.fp, file_info["header_size"] - 4)
+
+ # -------------------------------------------------- IBM OS/2 Bitmap v1
+ # ----- This format has different offsets because of width/height types
+ 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.RAW
+ file_info["palette_padding"] = 3
+
+ # --------------------------------------------- Windows Bitmap v2 to v5
+ # v3, OS/2 v2, v4, v5
+ elif file_info["header_size"] in (40, 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
+ self.info["dpi"] = tuple(x / 39.3701 for x in file_info["pixels_per_meter"])
+ if file_info["compression"] == self.BITFIELDS:
+ if len(header_data) >= 52:
+ for idx, mask in enumerate(
+ ["r_mask", "g_mask", "b_mask", "a_mask"]
+ ):
+ 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 ["r_mask", "g_mask", "b_mask"]:
+ file_info[mask] = i32(read(4))
+ 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
+ self._size = file_info["width"], file_info["height"]
+
+ # ------- If color count was not found in the header, compute from bits
+ file_info["colors"] = (
+ file_info["colors"]
+ if file_info.get("colors", 0)
+ else (1 << file_info["bits"])
+ )
+ 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"], (None, None))
+ if self.mode is None:
+ 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.BITFIELDS:
+ SUPPORTED = {
+ 32: [
+ (0xFF0000, 0xFF00, 0xFF, 0x0),
+ (0xFF000000, 0xFF0000, 0xFF00, 0x0),
+ (0xFF000000, 0xFF0000, 0xFF00, 0xFF),
+ (0xFF, 0xFF00, 0xFF0000, 0xFF000000),
+ (0xFF0000, 0xFF00, 0xFF, 0xFF000000),
+ (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, 0xFF0000, 0xFF00, 0xFF)): "ABGR",
+ (32, (0xFF, 0xFF00, 0xFF0000, 0xFF000000)): "RGBA",
+ (32, (0xFF0000, 0xFF00, 0xFF, 0xFF000000)): "BGRA",
+ (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"]]
+ ):
+ 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"]]
+ ):
+ 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.RAW:
+ if file_info["bits"] == 32 and header == 22: # 32-bit .cur offset
+ raw_mode, self._mode = "BGRA", "RGBA"
+ elif file_info["compression"] in (self.RLE8, self.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:
+ padding = file_info["palette_padding"]
+ palette = read(padding * file_info["colors"])
+ greyscale = True
+ indices = (
+ (0, 255)
+ if file_info["colors"] == 2
+ else list(range(file_info["colors"]))
+ )
+
+ # ----------------- Check if greyscale and ignore palette if so
+ for ind, val in enumerate(indices):
+ rgb = palette[ind * padding : ind * padding + 3]
+ if rgb != o8(val) * 3:
+ greyscale = False
+
+ # ------- If all colors are grey, white or black, ditch palette
+ if greyscale:
+ 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 = [raw_mode]
+ if decoder_name == "bmp_rle":
+ args.append(file_info["compression"] == self.RLE4)
+ else:
+ args.append(((file_info["width"] * file_info["bits"] + 31) >> 3) & (~3))
+ args.append(file_info["direction"])
+ self.tile = [
+ (
+ decoder_name,
+ (0, 0, file_info["width"], file_info["height"]),
+ offset or self.fp.tell(),
+ tuple(args),
+ )
+ ]
+
+ def _open(self):
+ """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):
+ rle4 = self.args[1]
+ data = bytearray()
+ x = 0
+ while len(data) < self.state.xsize * self.state.ysize:
+ 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):
+ 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, fp, filename):
+ _save(im, fp, filename, False)
+
+
+def _save(im, fp, filename, bitmap_header=True):
+ 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(map(lambda x: int(x * 39.3701 + 0.5), 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) * 4 for i in (0, 255))
+ elif im.mode == "L":
+ palette = b"".join(o8(i) * 4 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, [("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/contrib/python/Pillow/py3/PIL/BufrStubImagePlugin.py b/contrib/python/Pillow/py3/PIL/BufrStubImagePlugin.py
new file mode 100644
index 00000000000..eef25aa14cf
--- /dev/null
+++ b/contrib/python/Pillow/py3/PIL/BufrStubImagePlugin.py
@@ -0,0 +1,73 @@
+#
+# 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 . import Image, ImageFile
+
+_handler = None
+
+
+def register_handler(handler):
+ """
+ Install application-specific BUFR image handler.
+
+ :param handler: Handler object.
+ """
+ global _handler
+ _handler = handler
+
+
+# --------------------------------------------------------------------
+# Image adapter
+
+
+def _accept(prefix):
+ return prefix[:4] == b"BUFR" or prefix[:4] == b"ZCZC"
+
+
+class BufrStubImageFile(ImageFile.StubImageFile):
+ format = "BUFR"
+ format_description = "BUFR"
+
+ def _open(self):
+ offset = self.fp.tell()
+
+ if not _accept(self.fp.read(4)):
+ msg = "Not a BUFR file"
+ raise SyntaxError(msg)
+
+ self.fp.seek(offset)
+
+ # make something up
+ self._mode = "F"
+ self._size = 1, 1
+
+ loader = self._load()
+ if loader:
+ loader.open(self)
+
+ def _load(self):
+ return _handler
+
+
+def _save(im, fp, filename):
+ 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/contrib/python/Pillow/py3/PIL/ContainerIO.py b/contrib/python/Pillow/py3/PIL/ContainerIO.py
new file mode 100644
index 00000000000..45e80b39af7
--- /dev/null
+++ b/contrib/python/Pillow/py3/PIL/ContainerIO.py
@@ -0,0 +1,120 @@
+#
+# 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.
+#
+
+
+import io
+
+
+class ContainerIO:
+ """
+ A file object that provides read access to a part of an existing
+ file (for example a TAR file).
+ """
+
+ def __init__(self, file, offset, length):
+ """
+ Create file object.
+
+ :param file: Existing file.
+ :param offset: Start of region, in bytes.
+ :param length: Size of region, in bytes.
+ """
+ self.fh = file
+ self.pos = 0
+ self.offset = offset
+ self.length = length
+ self.fh.seek(offset)
+
+ ##
+ # Always false.
+
+ def isatty(self):
+ return False
+
+ def seek(self, offset, mode=io.SEEK_SET):
+ """
+ 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.
+ """
+ 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)
+
+ def tell(self):
+ """
+ Get current file pointer.
+
+ :returns: Offset from start of region, in bytes.
+ """
+ return self.pos
+
+ def read(self, n=0):
+ """
+ Read data.
+
+ :param n: Number of bytes to read. If omitted or zero,
+ read until end of region.
+ :returns: An 8-bit string.
+ """
+ if n:
+ n = min(n, self.length - self.pos)
+ else:
+ n = self.length - self.pos
+ if not n: # EOF
+ return b"" if "b" in self.fh.mode else ""
+ self.pos = self.pos + n
+ return self.fh.read(n)
+
+ def readline(self):
+ """
+ Read a line of text.
+
+ :returns: An 8-bit string.
+ """
+ s = b"" if "b" in self.fh.mode else ""
+ 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:
+ break
+ return s
+
+ def readlines(self):
+ """
+ Read multiple lines of text.
+
+ :returns: A list of 8-bit strings.
+ """
+ lines = []
+ while True:
+ s = self.readline()
+ if not s:
+ break
+ lines.append(s)
+ return lines
diff --git a/contrib/python/Pillow/py3/PIL/CurImagePlugin.py b/contrib/python/Pillow/py3/PIL/CurImagePlugin.py
new file mode 100644
index 00000000000..94efff34156
--- /dev/null
+++ b/contrib/python/Pillow/py3/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 . import BmpImagePlugin, Image
+from ._binary import i16le as i16
+from ._binary import i32le as i32
+
+#
+# --------------------------------------------------------------------
+
+
+def _accept(prefix):
+ return prefix[:4] == b"\0\0\2\0"
+
+
+##
+# Image plugin for Windows Cursor files.
+
+
+class CurImageFile(BmpImagePlugin.BmpImageFile):
+ format = "CUR"
+ format_description = "Windows Cursor"
+
+ def _open(self):
+ 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] = d, (0, 0) + self.size, o, a
+
+ return
+
+
+#
+# --------------------------------------------------------------------
+
+Image.register_open(CurImageFile.format, CurImageFile, _accept)
+
+Image.register_extension(CurImageFile.format, ".cur")
diff --git a/contrib/python/Pillow/py3/PIL/DcxImagePlugin.py b/contrib/python/Pillow/py3/PIL/DcxImagePlugin.py
new file mode 100644
index 00000000000..cde9d42f09f
--- /dev/null
+++ b/contrib/python/Pillow/py3/PIL/DcxImagePlugin.py
@@ -0,0 +1,79 @@
+#
+# 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 . import Image
+from ._binary import i32le as i32
+from .PcxImagePlugin import PcxImageFile
+
+MAGIC = 0x3ADE68B1 # QUIZ: what's this value, then?
+
+
+def _accept(prefix):
+ 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):
+ # 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 = None
+ self.n_frames = len(self._offset)
+ self.is_animated = self.n_frames > 1
+ self.seek(0)
+
+ def seek(self, frame):
+ if not self._seek_check(frame):
+ return
+ self.frame = frame
+ self.fp = self._fp
+ self.fp.seek(self._offset[frame])
+ PcxImageFile._open(self)
+
+ def tell(self):
+ return self.frame
+
+
+Image.register_open(DcxImageFile.format, DcxImageFile, _accept)
+
+Image.register_extension(DcxImageFile.format, ".dcx")
diff --git a/contrib/python/Pillow/py3/PIL/DdsImagePlugin.py b/contrib/python/Pillow/py3/PIL/DdsImagePlugin.py
new file mode 100644
index 00000000000..54f358c7f9a
--- /dev/null
+++ b/contrib/python/Pillow/py3/PIL/DdsImagePlugin.py
@@ -0,0 +1,295 @@
+"""
+A Pillow loader for .dds files (S3TC-compressed aka DXTC)
+Jerome Leclanche <[email protected]>
+
+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/
+"""
+
+import struct
+from io import BytesIO
+
+from . import Image, ImageFile, ImagePalette
+from ._binary import o32le as o32
+
+# Magic ("DDS ")
+DDS_MAGIC = 0x20534444
+
+# DDS flags
+DDSD_CAPS = 0x1
+DDSD_HEIGHT = 0x2
+DDSD_WIDTH = 0x4
+DDSD_PITCH = 0x8
+DDSD_PIXELFORMAT = 0x1000
+DDSD_MIPMAPCOUNT = 0x20000
+DDSD_LINEARSIZE = 0x80000
+DDSD_DEPTH = 0x800000
+
+# DDS caps
+DDSCAPS_COMPLEX = 0x8
+DDSCAPS_TEXTURE = 0x1000
+DDSCAPS_MIPMAP = 0x400000
+
+DDSCAPS2_CUBEMAP = 0x200
+DDSCAPS2_CUBEMAP_POSITIVEX = 0x400
+DDSCAPS2_CUBEMAP_NEGATIVEX = 0x800
+DDSCAPS2_CUBEMAP_POSITIVEY = 0x1000
+DDSCAPS2_CUBEMAP_NEGATIVEY = 0x2000
+DDSCAPS2_CUBEMAP_POSITIVEZ = 0x4000
+DDSCAPS2_CUBEMAP_NEGATIVEZ = 0x8000
+DDSCAPS2_VOLUME = 0x200000
+
+# Pixel Format
+DDPF_ALPHAPIXELS = 0x1
+DDPF_ALPHA = 0x2
+DDPF_FOURCC = 0x4
+DDPF_PALETTEINDEXED8 = 0x20
+DDPF_RGB = 0x40
+DDPF_LUMINANCE = 0x20000
+
+
+# dds.h
+
+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
+DXT1_FOURCC = 0x31545844
+
+# DXT3
+DXT3_FOURCC = 0x33545844
+
+# DXT5
+DXT5_FOURCC = 0x35545844
+
+
+# dxgiformat.h
+
+DXGI_FORMAT_R8G8B8A8_TYPELESS = 27
+DXGI_FORMAT_R8G8B8A8_UNORM = 28
+DXGI_FORMAT_R8G8B8A8_UNORM_SRGB = 29
+DXGI_FORMAT_BC5_TYPELESS = 82
+DXGI_FORMAT_BC5_UNORM = 83
+DXGI_FORMAT_BC5_SNORM = 84
+DXGI_FORMAT_BC6H_UF16 = 95
+DXGI_FORMAT_BC6H_SF16 = 96
+DXGI_FORMAT_BC7_TYPELESS = 97
+DXGI_FORMAT_BC7_UNORM = 98
+DXGI_FORMAT_BC7_UNORM_SRGB = 99
+
+
+class DdsImageFile(ImageFile.ImageFile):
+ format = "DDS"
+ format_description = "DirectDraw Surface"
+
+ def _open(self):
+ if not _accept(self.fp.read(4)):
+ msg = "not a DDS file"
+ raise SyntaxError(msg)
+ (header_size,) = struct.unpack("<I", self.fp.read(4))
+ if header_size != 124:
+ msg = f"Unsupported header size {repr(header_size)}"
+ raise OSError(msg)
+ header_bytes = self.fp.read(header_size - 4)
+ if len(header_bytes) != 120:
+ msg = f"Incomplete header: {len(header_bytes)} bytes"
+ raise OSError(msg)
+ header = BytesIO(header_bytes)
+
+ flags, height, width = struct.unpack("<3I", header.read(12))
+ self._size = (width, height)
+ self._mode = "RGBA"
+
+ pitch, depth, mipmaps = struct.unpack("<3I", header.read(12))
+ struct.unpack("<11I", header.read(44)) # reserved
+
+ # pixel format
+ pfsize, pfflags = struct.unpack("<2I", header.read(8))
+ fourcc = header.read(4)
+ (bitcount,) = struct.unpack("<I", header.read(4))
+ masks = struct.unpack("<4I", header.read(16))
+ if pfflags & DDPF_LUMINANCE:
+ # Texture contains uncompressed L or LA data
+ if pfflags & DDPF_ALPHAPIXELS:
+ self._mode = "LA"
+ else:
+ self._mode = "L"
+
+ self.tile = [("raw", (0, 0) + self.size, 0, (self.mode, 0, 1))]
+ elif pfflags & DDPF_RGB:
+ # Texture contains uncompressed RGB data
+ masks = {mask: ["R", "G", "B", "A"][i] for i, mask in enumerate(masks)}
+ rawmode = ""
+ if pfflags & DDPF_ALPHAPIXELS:
+ rawmode += masks[0xFF000000]
+ else:
+ self._mode = "RGB"
+ rawmode += masks[0xFF0000] + masks[0xFF00] + masks[0xFF]
+
+ self.tile = [("raw", (0, 0) + self.size, 0, (rawmode[::-1], 0, 1))]
+ elif pfflags & DDPF_PALETTEINDEXED8:
+ self._mode = "P"
+ self.palette = ImagePalette.raw("RGBA", self.fp.read(1024))
+ self.tile = [("raw", (0, 0) + self.size, 0, "L")]
+ else:
+ data_start = header_size + 4
+ n = 0
+ if fourcc == b"DXT1":
+ self.pixel_format = "DXT1"
+ n = 1
+ elif fourcc == b"DXT3":
+ self.pixel_format = "DXT3"
+ n = 2
+ elif fourcc == b"DXT5":
+ self.pixel_format = "DXT5"
+ n = 3
+ elif fourcc == b"ATI1":
+ self.pixel_format = "BC4"
+ n = 4
+ self._mode = "L"
+ elif fourcc in (b"ATI2", b"BC5U"):
+ self.pixel_format = "BC5"
+ n = 5
+ self._mode = "RGB"
+ elif fourcc == b"BC5S":
+ self.pixel_format = "BC5S"
+ n = 5
+ self._mode = "RGB"
+ elif fourcc == b"DX10":
+ data_start += 20
+ # ignoring flags which pertain to volume textures and cubemaps
+ (dxgi_format,) = struct.unpack("<I", self.fp.read(4))
+ self.fp.read(16)
+ if dxgi_format in (DXGI_FORMAT_BC5_TYPELESS, DXGI_FORMAT_BC5_UNORM):
+ self.pixel_format = "BC5"
+ n = 5
+ self._mode = "RGB"
+ elif dxgi_format == DXGI_FORMAT_BC5_SNORM:
+ self.pixel_format = "BC5S"
+ n = 5
+ self._mode = "RGB"
+ elif dxgi_format == DXGI_FORMAT_BC6H_UF16:
+ self.pixel_format = "BC6H"
+ n = 6
+ self._mode = "RGB"
+ elif dxgi_format == DXGI_FORMAT_BC6H_SF16:
+ self.pixel_format = "BC6HS"
+ n = 6
+ self._mode = "RGB"
+ elif dxgi_format in (DXGI_FORMAT_BC7_TYPELESS, DXGI_FORMAT_BC7_UNORM):
+ self.pixel_format = "BC7"
+ n = 7
+ elif dxgi_format == DXGI_FORMAT_BC7_UNORM_SRGB:
+ self.pixel_format = "BC7"
+ self.info["gamma"] = 1 / 2.2
+ n = 7
+ elif dxgi_format in (
+ DXGI_FORMAT_R8G8B8A8_TYPELESS,
+ DXGI_FORMAT_R8G8B8A8_UNORM,
+ DXGI_FORMAT_R8G8B8A8_UNORM_SRGB,
+ ):
+ self.tile = [("raw", (0, 0) + self.size, 0, ("RGBA", 0, 1))]
+ if dxgi_format == DXGI_FORMAT_R8G8B8A8_UNORM_SRGB:
+ self.info["gamma"] = 1 / 2.2
+ return
+ else:
+ msg = f"Unimplemented DXGI format {dxgi_format}"
+ raise NotImplementedError(msg)
+ else:
+ msg = f"Unimplemented pixel format {repr(fourcc)}"
+ raise NotImplementedError(msg)
+
+ self.tile = [
+ ("bcn", (0, 0) + self.size, data_start, (n, self.pixel_format))
+ ]
+
+ def load_seek(self, pos):
+ pass
+
+
+def _save(im, fp, filename):
+ if im.mode not in ("RGB", "RGBA", "L", "LA"):
+ msg = f"cannot write mode {im.mode} as DDS"
+ raise OSError(msg)
+
+ rawmode = im.mode
+ masks = [0xFF0000, 0xFF00, 0xFF]
+ if im.mode in ("L", "LA"):
+ pixel_flags = DDPF_LUMINANCE
+ else:
+ pixel_flags = DDPF_RGB
+ rawmode = rawmode[::-1]
+ if im.mode in ("LA", "RGBA"):
+ pixel_flags |= DDPF_ALPHAPIXELS
+ masks.append(0xFF000000)
+
+ bitcount = len(masks) * 8
+ while len(masks) < 4:
+ masks.append(0)
+
+ fp.write(
+ o32(DDS_MAGIC)
+ + o32(124) # header size
+ + o32(
+ DDSD_CAPS | DDSD_HEIGHT | DDSD_WIDTH | DDSD_PITCH | DDSD_PIXELFORMAT
+ ) # flags
+ + o32(im.height)
+ + o32(im.width)
+ + o32((im.width * bitcount + 7) // 8) # pitch
+ + o32(0) # depth
+ + o32(0) # mipmaps
+ + o32(0) * 11 # reserved
+ + o32(32) # pfsize
+ + o32(pixel_flags) # pfflags
+ + o32(0) # fourcc
+ + o32(bitcount) # bitcount
+ + b"".join(o32(mask) for mask in masks) # rgbabitmask
+ + o32(DDSCAPS_TEXTURE) # dwCaps
+ + o32(0) # dwCaps2
+ + o32(0) # dwCaps3
+ + o32(0) # dwCaps4
+ + o32(0) # dwReserved2
+ )
+ if im.mode == "RGBA":
+ r, g, b, a = im.split()
+ im = Image.merge("RGBA", (a, r, g, b))
+ ImageFile._save(im, fp, [("raw", (0, 0) + im.size, 0, (rawmode, 0, 1))])
+
+
+def _accept(prefix):
+ return prefix[:4] == b"DDS "
+
+
+Image.register_open(DdsImageFile.format, DdsImageFile, _accept)
+Image.register_save(DdsImageFile.format, _save)
+Image.register_extension(DdsImageFile.format, ".dds")
diff --git a/contrib/python/Pillow/py3/PIL/EpsImagePlugin.py b/contrib/python/Pillow/py3/PIL/EpsImagePlugin.py
new file mode 100644
index 00000000000..9b2fce0ac01
--- /dev/null
+++ b/contrib/python/Pillow/py3/PIL/EpsImagePlugin.py
@@ -0,0 +1,480 @@
+#
+# 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.
+#
+
+import io
+import os
+import re
+import subprocess
+import sys
+import tempfile
+
+from . import Image, ImageFile
+from ._binary import i32le as i32
+from ._deprecate import deprecate
+
+# --------------------------------------------------------------------
+
+
+split = re.compile(r"^%%([^:]*):[ \t]*(.*)[ \t]*$")
+field = re.compile(r"^%[%!\w]([^:]*)[ \t]*$")
+
+gs_binary = None
+gs_windows_binary = None
+
+
+def has_ghostscript():
+ 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, size, fp, scale=1, transparency=False):
+ """Render an image using Ghostscript"""
+ global gs_binary
+ if not has_ghostscript():
+ msg = "Unable to locate Ghostscript on paths"
+ raise OSError(msg)
+
+ # Unpack decoder tile
+ decoder, tile, offset, data = tile[0]
+ length, bbox = data
+
+ # Hack to support hi-res rendering
+ scale = int(scale) or 1
+ # orig_size = size
+ # orig_bbox = bbox
+ size = (size[0] * scale, size[1] * scale)
+ # resolution is dependent on bbox and size
+ res = (
+ 72.0 * size[0] / (bbox[2] - bbox[0]),
+ 72.0 * size[1] / (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)
+
+ device = "pngalpha" if transparency else "ppmraw"
+
+ # Build Ghostscript command
+ command = [
+ gs_binary,
+ "-q", # quiet mode
+ "-g%dx%d" % size, # set output geometry (pixels)
+ "-r%fx%f" % res, # 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)
+ out_im = Image.open(outfile)
+ out_im.load()
+ finally:
+ try:
+ os.unlink(outfile)
+ if infile_temp:
+ os.unlink(infile_temp)
+ except OSError:
+ pass
+
+ im = out_im.im.copy()
+ out_im.close()
+ return im
+
+
+class PSFile:
+ """
+ Wrapper for bytesio object that treats either CR or LF as end of line.
+ This class is no longer used internally, but kept for backwards compatibility.
+ """
+
+ def __init__(self, fp):
+ deprecate(
+ "PSFile",
+ 11,
+ action="If you need the functionality of this class "
+ "you will need to implement it yourself.",
+ )
+ self.fp = fp
+ self.char = None
+
+ def seek(self, offset, whence=io.SEEK_SET):
+ self.char = None
+ self.fp.seek(offset, whence)
+
+ def readline(self):
+ s = [self.char or b""]
+ self.char = None
+
+ c = self.fp.read(1)
+ while (c not in b"\r\n") and len(c):
+ s.append(c)
+ c = self.fp.read(1)
+
+ self.char = self.fp.read(1)
+ # line endings can be 1 or 2 of \r \n, in either order
+ if self.char in b"\r\n":
+ self.char = None
+
+ return b"".join(s).decode("latin-1")
+
+
+def _accept(prefix):
+ return prefix[:4] == 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):
+ (length, offset) = self._find_offset(self.fp)
+
+ # go to offset - start of "%!PS"
+ self.fp.seek(offset)
+
+ self._mode = "RGB"
+ self._size = 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():
+ 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):
+ nonlocal reading_trailer_comments
+ try:
+ m = split.match(s)
+ except re.error as e:
+ msg = "not an EPS file"
+ raise SyntaxError(msg) from e
+
+ if m:
+ k, v = m.group(1, 2)
+ self.info[k] = v
+ if k == "BoundingBox":
+ if v == "(atend)":
+ reading_trailer_comments = True
+ elif not self._size 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.
+ box = [int(float(i)) for i in v.split()]
+ self._size = box[2] - box[0], box[3] - box[1]
+ self.tile = [
+ ("eps", (0, 0) + self.size, offset, (length, box))
+ ]
+ 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:
+ 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[:8] == "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
+
+ # 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
+
+ self._size = columns, rows
+ return
+ elif trailer_reached and reading_trailer_comments:
+ # Load EPS trailer
+
+ # if this line starts with "%%EOF",
+ # then we've reached the end of the file
+ if bytes_mv[:5] == b"%%EOF":
+ break
+
+ s = str(bytes_mv[:bytes_read], "latin-1")
+ _read_comment(s)
+ elif bytes_mv[:9] == b"%%Trailer":
+ trailer_reached = True
+ bytes_read = 0
+
+ check_required_header_comments()
+
+ if not self._size:
+ msg = "cannot determine EPS bounding box"
+ raise OSError(msg)
+
+ def _find_offset(self, fp):
+ 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=1, transparency=False):
+ # 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, *args, **kwargs):
+ # we can't incrementally load, so force ImageFile.parser to
+ # use our custom load method by defining this method.
+ pass
+
+
+# --------------------------------------------------------------------
+
+
+def _save(im, fp, filename, eps=1):
+ """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, [("eps", (0, 0) + im.size, 0, None)])
+
+ 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/contrib/python/Pillow/py3/PIL/ExifTags.py b/contrib/python/Pillow/py3/PIL/ExifTags.py
new file mode 100644
index 00000000000..2347c6d4c27
--- /dev/null
+++ b/contrib/python/Pillow/py3/PIL/ExifTags.py
@@ -0,0 +1,380 @@
+#
+# 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 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 = 0
+ GPSLatitudeRef = 1
+ GPSLatitude = 2
+ GPSLongitudeRef = 3
+ GPSLongitude = 4
+ GPSAltitudeRef = 5
+ GPSAltitude = 6
+ GPSTimeStamp = 7
+ GPSSatellites = 8
+ GPSStatus = 9
+ GPSMeasureMode = 10
+ GPSDOP = 11
+ GPSSpeedRef = 12
+ GPSSpeed = 13
+ GPSTrackRef = 14
+ GPSTrack = 15
+ GPSImgDirectionRef = 16
+ GPSImgDirection = 17
+ GPSMapDatum = 18
+ GPSDestLatitudeRef = 19
+ GPSDestLatitude = 20
+ GPSDestLongitudeRef = 21
+ GPSDestLongitude = 22
+ GPSDestBearingRef = 23
+ GPSDestBearing = 24
+ GPSDestDistanceRef = 25
+ GPSDestDistance = 26
+ GPSProcessingMethod = 27
+ GPSAreaInformation = 28
+ GPSDateStamp = 29
+ GPSDifferential = 30
+ GPSHPositioningError = 31
+
+
+"""Maps EXIF GPS tags to tag names."""
+GPSTAGS = {i.value: i.name for i in GPS}
+
+
+class Interop(IntEnum):
+ InteropIndex = 1
+ InteropVersion = 2
+ RelatedImageFileFormat = 4096
+ RelatedImageWidth = 4097
+ RleatedImageHeight = 4098
+
+
+class IFD(IntEnum):
+ Exif = 34665
+ GPSInfo = 34853
+ Makernote = 37500
+ Interop = 40965
+ IFD1 = -1
+
+
+class LightSource(IntEnum):
+ Unknown = 0
+ Daylight = 1
+ Fluorescent = 2
+ Tungsten = 3
+ Flash = 4
+ Fine = 9
+ Cloudy = 10
+ Shade = 11
+ DaylightFluorescent = 12
+ DayWhiteFluorescent = 13
+ CoolWhiteFluorescent = 14
+ WhiteFluorescent = 15
+ StandardLightA = 17
+ StandardLightB = 18
+ StandardLightC = 19
+ D55 = 20
+ D65 = 21
+ D75 = 22
+ D50 = 23
+ ISO = 24
+ Other = 255
diff --git a/contrib/python/Pillow/py3/PIL/FitsImagePlugin.py b/contrib/python/Pillow/py3/PIL/FitsImagePlugin.py
new file mode 100644
index 00000000000..e0e51aaac73
--- /dev/null
+++ b/contrib/python/Pillow/py3/PIL/FitsImagePlugin.py
@@ -0,0 +1,73 @@
+#
+# 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.
+#
+
+import math
+
+from . import Image, ImageFile
+
+
+def _accept(prefix):
+ return prefix[:6] == b"SIMPLE"
+
+
+class FitsImageFile(ImageFile.ImageFile):
+ format = "FITS"
+ format_description = "FITS"
+
+ def _open(self):
+ headers = {}
+ while True:
+ header = self.fp.read(80)
+ if not header:
+ msg = "Truncated FITS file"
+ raise OSError(msg)
+ keyword = header[:8].strip()
+ if keyword == b"END":
+ break
+ 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
+
+ naxis = int(headers[b"NAXIS"])
+ if naxis == 0:
+ msg = "No image data"
+ raise ValueError(msg)
+ elif naxis == 1:
+ self._size = 1, int(headers[b"NAXIS1"])
+ else:
+ self._size = int(headers[b"NAXIS1"]), int(headers[b"NAXIS2"])
+
+ number_of_bits = int(headers[b"BITPIX"])
+ if number_of_bits == 8:
+ self._mode = "L"
+ elif number_of_bits == 16:
+ self._mode = "I"
+ # rawmode = "I;16S"
+ elif number_of_bits == 32:
+ self._mode = "I"
+ elif number_of_bits in (-32, -64):
+ self._mode = "F"
+ # rawmode = "F" if number_of_bits == -32 else "F;64F"
+
+ offset = math.ceil(self.fp.tell() / 2880) * 2880
+ self.tile = [("raw", (0, 0) + self.size, offset, (self.mode, 0, -1))]
+
+
+# --------------------------------------------------------------------
+# Registry
+
+Image.register_open(FitsImageFile.format, FitsImageFile, _accept)
+
+Image.register_extensions(FitsImageFile.format, [".fit", ".fits"])
diff --git a/contrib/python/Pillow/py3/PIL/FliImagePlugin.py b/contrib/python/Pillow/py3/PIL/FliImagePlugin.py
new file mode 100644
index 00000000000..8f641ece998
--- /dev/null
+++ b/contrib/python/Pillow/py3/PIL/FliImagePlugin.py
@@ -0,0 +1,171 @@
+#
+# 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.
+#
+
+import os
+
+from . import Image, ImageFile, ImagePalette
+from ._binary import i16le as i16
+from ._binary import i32le as i32
+from ._binary import o8
+
+#
+# decoder
+
+
+def _accept(prefix):
+ 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 <b>seek</b>
+# 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):
+ # 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)
+ s = self.fp.read(16)
+
+ if i16(s, 4) == 0xF1FA:
+ # look for palette chunk
+ number_of_subchunks = i16(s, 6)
+ chunk_size = 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
+
+ palette = [o8(r) + o8(g) + o8(b) for (r, g, b) in palette]
+ self.palette = ImagePalette.raw("RGB", b"".join(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, shift):
+ # 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):
+ 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):
+ 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:
+ raise EOFError
+
+ framesize = i32(s)
+
+ self.decodermaxblock = framesize
+ self.tile = [("fli", (0, 0) + self.size, self.__offset, None)]
+
+ self.__offset += framesize
+
+ def tell(self):
+ return self.__frame
+
+
+#
+# registry
+
+Image.register_open(FliImageFile.format, FliImageFile, _accept)
+
+Image.register_extensions(FliImageFile.format, [".fli", ".flc"])
diff --git a/contrib/python/Pillow/py3/PIL/FontFile.py b/contrib/python/Pillow/py3/PIL/FontFile.py
new file mode 100644
index 00000000000..5ec0a6632e3
--- /dev/null
+++ b/contrib/python/Pillow/py3/PIL/FontFile.py
@@ -0,0 +1,110 @@
+#
+# 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.
+#
+
+
+import os
+
+from . import Image, _binary
+
+WIDTH = 800
+
+
+def puti16(fp, values):
+ """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 = None
+
+ def __init__(self):
+ self.info = {}
+ self.glyph = [None] * 256
+
+ def __getitem__(self, ix):
+ return self.glyph[ix]
+
+ def compile(self):
+ """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:
+ 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 = [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]
+ # yy = src[3] - src[1]
+ 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):
+ """Save font"""
+
+ self.compile()
+
+ # font data
+ 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/contrib/python/Pillow/py3/PIL/FpxImagePlugin.py b/contrib/python/Pillow/py3/PIL/FpxImagePlugin.py
new file mode 100644
index 00000000000..a878cbfd200
--- /dev/null
+++ b/contrib/python/Pillow/py3/PIL/FpxImagePlugin.py
@@ -0,0 +1,253 @@
+#
+# 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.
+#
+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):
+ return prefix[:8] == olefile.MAGIC
+
+
+##
+# Image plugin for the FlashPix images.
+
+
+class FpxImageFile(ImageFile.ImageFile):
+ format = "FPX"
+ format_description = "FlashPix"
+
+ def _open(self):
+ #
+ # 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
+
+ if self.ole.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=1):
+ #
+ # get the Image Contents Property Set
+
+ prop = self.ole.getproperties(
+ [f"Data Object Store {index:06d}", "\005Image Contents"]
+ )
+
+ # size (highest resolution)
+
+ 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]
+
+ colors = []
+ bands = i32(s, 4)
+ if bands > 4:
+ msg = "Invalid number of bands"
+ raise OSError(msg)
+ for i in range(bands):
+ # note: for now, we ignore the "uncalibrated" flag
+ colors.append(i32(s, 8 + i * 4) & 0x7FFFFFFF)
+
+ self._mode, self.rawmode = MODES[tuple(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=1, subimage=0):
+ #
+ # 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(
+ (
+ "raw",
+ (x, y, x1, y1),
+ i32(s, i) + 28,
+ (self.rawmode,),
+ )
+ )
+
+ elif compression == 1:
+ # FIXME: the fill decoder is not implemented
+ self.tile.append(
+ (
+ "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(
+ (
+ "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 = None
+
+ def load(self):
+ if not self.fp:
+ self.fp = self.ole.openstream(self.stream[:2] + ["Subimage 0000 Data"])
+
+ return ImageFile.ImageFile.load(self)
+
+ def close(self):
+ self.ole.close()
+ super().close()
+
+ def __exit__(self, *args):
+ self.ole.close()
+ super().__exit__()
+
+
+#
+# --------------------------------------------------------------------
+
+
+Image.register_open(FpxImageFile.format, FpxImageFile, _accept)
+
+Image.register_extension(FpxImageFile.format, ".fpx")
diff --git a/contrib/python/Pillow/py3/PIL/FtexImagePlugin.py b/contrib/python/Pillow/py3/PIL/FtexImagePlugin.py
new file mode 100644
index 00000000000..c2e4ead7174
--- /dev/null
+++ b/contrib/python/Pillow/py3/PIL/FtexImagePlugin.py
@@ -0,0 +1,113 @@
+"""
+A Pillow loader for .ftc and .ftu files (FTEX)
+Jerome Leclanche <[email protected]>
+
+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.
+"""
+
+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):
+ if not _accept(self.fp.read(4)):
+ msg = "not an FTEX file"
+ raise SyntaxError(msg)
+ struct.unpack("<i", self.fp.read(4)) # version
+ self._size = struct.unpack("<2i", self.fp.read(8))
+ mipmap_count, format_count = struct.unpack("<2i", self.fp.read(8))
+
+ self._mode = "RGB"
+
+ # Only support single-format files.
+ # I don't know of any multi-format file.
+ assert format_count == 1
+
+ format, where = struct.unpack("<2i", self.fp.read(8))
+ self.fp.seek(where)
+ (mipmap_size,) = struct.unpack("<i", self.fp.read(4))
+
+ data = self.fp.read(mipmap_size)
+
+ if format == Format.DXT1:
+ self._mode = "RGBA"
+ self.tile = [("bcn", (0, 0) + self.size, 0, 1)]
+ elif format == Format.UNCOMPRESSED:
+ self.tile = [("raw", (0, 0) + self.size, 0, ("RGB", 0, 1))]
+ else:
+ msg = f"Invalid texture compression format: {repr(format)}"
+ raise ValueError(msg)
+
+ self.fp.close()
+ self.fp = BytesIO(data)
+
+ def load_seek(self, pos):
+ pass
+
+
+def _accept(prefix):
+ return prefix[:4] == MAGIC
+
+
+Image.register_open(FtexImageFile.format, FtexImageFile, _accept)
+Image.register_extensions(FtexImageFile.format, [".ftc", ".ftu"])
diff --git a/contrib/python/Pillow/py3/PIL/GbrImagePlugin.py b/contrib/python/Pillow/py3/PIL/GbrImagePlugin.py
new file mode 100644
index 00000000000..ec6e9de6e7b
--- /dev/null
+++ b/contrib/python/Pillow/py3/PIL/GbrImagePlugin.py
@@ -0,0 +1,102 @@
+#
+# 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 . import Image, ImageFile
+from ._binary import i32be as i32
+
+
+def _accept(prefix):
+ 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):
+ 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):
+ if not self.im:
+ 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/contrib/python/Pillow/py3/PIL/GdImageFile.py b/contrib/python/Pillow/py3/PIL/GdImageFile.py
new file mode 100644
index 00000000000..3599994a8e3
--- /dev/null
+++ b/contrib/python/Pillow/py3/PIL/GdImageFile.py
@@ -0,0 +1,97 @@
+#
+# 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 . import ImageFile, ImagePalette, UnidentifiedImageError
+from ._binary import i16be as i16
+from ._binary import i32be as i32
+
+
+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):
+ # Header
+ 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 = "L" # FIXME: "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(
+ "XBGR", s[7 + true_color_offset + 4 : 7 + true_color_offset + 4 + 256 * 4]
+ )
+
+ self.tile = [
+ (
+ "raw",
+ (0, 0) + self.size,
+ 7 + true_color_offset + 4 + 256 * 4,
+ ("L", 0, 1),
+ )
+ ]
+
+
+def open(fp, mode="r"):
+ """
+ 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/contrib/python/Pillow/py3/PIL/GifImagePlugin.py b/contrib/python/Pillow/py3/PIL/GifImagePlugin.py
new file mode 100644
index 00000000000..92074b0d49e
--- /dev/null
+++ b/contrib/python/Pillow/py3/PIL/GifImagePlugin.py
@@ -0,0 +1,1060 @@
+#
+# 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.
+#
+
+import itertools
+import math
+import os
+import subprocess
+from enum import IntEnum
+
+from . import Image, ImageChops, ImageFile, ImagePalette, ImageSequence
+from ._binary import i16le as i16
+from ._binary import o8
+from ._binary import o16le as o16
+
+
+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):
+ return prefix[:6] in [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):
+ s = self.fp.read(1)
+ if s and s[0]:
+ return self.fp.read(s[0])
+ return None
+
+ def _is_palette_needed(self, p):
+ 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):
+ # 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)
+ self.tile = []
+ 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 = None
+ self._is_animated = None
+ self._seek(0) # get ready to read first frame
+
+ @property
+ def n_frames(self):
+ 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
+
+ @property
+ def is_animated(self):
+ if self._is_animated is None:
+ if self._n_frames is not None:
+ self._is_animated = self._n_frames != 1
+ else:
+ current = self.tell()
+ if current:
+ self._is_animated = True
+ else:
+ try:
+ self._seek(1, False)
+ self._is_animated = True
+ except EOFError:
+ self._is_animated = False
+
+ self.seek(current)
+ return self._is_animated
+
+ def seek(self, frame):
+ 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, update_image=True):
+ if frame == 0:
+ # rewind
+ self.__offset = 0
+ self.dispose = 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";":
+ raise EOFError
+
+ palette = None
+
+ info = {}
+ 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:
+ #
+ # 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:
+ #
+ # application extension
+ #
+ info["extension"] = block, self.fp.tell()
+ if block[:11] == b"NETSCAPE2.0":
+ block = self.data()
+ if 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
+
+ else:
+ pass
+ # raise OSError, "illegal GIF tag `%x`" % s[0]
+ s = None
+
+ if interlace is None:
+ # self._fp = None
+ raise EOFError
+
+ 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 not palette and self.global_palette:
+ from copy import copy
+
+ palette = copy(self.global_palette)
+ self.palette = palette
+ else:
+ if self.mode == "P":
+ if (
+ LOADING_STRATEGY != LoadingStrategy.RGB_AFTER_DIFFERENT_PALETTE_ONLY
+ or palette
+ ):
+ self.pyaccess = None
+ 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):
+ if self._frame_palette:
+ color = tuple(self._frame_palette.palette[color * 3 : color * 3 + 3])
+ else:
+ color = (color, color, color)
+ return color
+
+ self.dispose_extent = frame_dispose_extent
+ try:
+ if self.disposal_method < 2:
+ # do not dispose or none specified
+ self.dispose = None
+ elif 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 = [
+ (
+ "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):
+ 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(*self._frame_palette.getdata())
+ else:
+ self.im = None
+ self._mode = temp_mode
+ self._frame_palette = None
+
+ super().load_prepare()
+
+ def load_end(self):
+ 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._frame_transparency is not None:
+ self.im.putpalettealpha(self._frame_transparency, 0)
+ frame_im = self.im.convert("RGBA")
+ else:
+ frame_im = self.im.convert("RGB")
+ frame_im = self._crop(frame_im, self.dispose_extent)
+
+ self.im = self._prev_im
+ self._mode = self.im.mode
+ if frame_im.mode == "RGBA":
+ self.im.paste(frame_im, self.dispose_extent, frame_im)
+ else:
+ self.im.paste(frame_im, self.dispose_extent)
+
+ def tell(self):
+ return self.__frame
+
+
+# --------------------------------------------------------------------
+# Write GIF files
+
+
+RAWMODE = {"1": "L", "L": "L", "P": "P"}
+
+
+def _normalize_mode(im):
+ """
+ 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)
+ 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")
+
+
+def _normalize_palette(im, palette, info):
+ """
+ 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:
+ source_palette = im.im.getpalette("RGB")[:768]
+ 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)
+
+ if palette:
+ used_palette_colors = []
+ 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
+ im = im.remap_palette(used_palette_colors)
+ else:
+ used_palette_colors = _get_optimize(im, info)
+ if used_palette_colors is not None:
+ return im.remap_palette(used_palette_colors, source_palette)
+
+ im.palette.palette = source_palette
+ return im
+
+
+def _write_single_frame(im, fp, palette):
+ im_out = _normalize_mode(im)
+ for k, v in im_out.info.items():
+ 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, [("gif", (0, 0) + im.size, 0, RAWMODE[im_out.mode])])
+
+ fp.write(b"\0") # end of image data
+
+
+def _getbbox(base_im, im_frame):
+ if _get_palette_bytes(im_frame) == _get_palette_bytes(base_im):
+ delta = ImageChops.subtract_modulo(im_frame, base_im)
+ else:
+ delta = ImageChops.subtract_modulo(
+ im_frame.convert("RGBA"), base_im.convert("RGBA")
+ )
+ return delta.getbbox(alpha_only=False)
+
+
+def _write_multiple_frames(im, fp, palette):
+ duration = im.encoderinfo.get("duration")
+ disposal = im.encoderinfo.get("disposal", im.info.get("disposal"))
+
+ im_frames = []
+ 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
+ im.encoderinfo.setdefault(k, v)
+
+ encoderinfo = im.encoderinfo.copy()
+ im_frame = _normalize_palette(im_frame, palette, encoderinfo)
+ if "transparency" in im_frame.info:
+ encoderinfo.setdefault("transparency", im_frame.info["transparency"])
+ 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
+
+ if im_frames:
+ # delta frame
+ previous = im_frames[-1]
+ bbox = _getbbox(previous["im"], im_frame)
+ if not bbox:
+ # This frame is identical to the previous frame
+ if encoderinfo.get("duration"):
+ previous["encoderinfo"]["duration"] += encoderinfo["duration"]
+ continue
+ if encoderinfo.get("disposal") == 2:
+ if background_im is None:
+ color = im.encoderinfo.get(
+ "transparency", im.info.get("transparency", (0, 0, 0))
+ )
+ background = _get_background(im_frame, color)
+ background_im = Image.new("P", im_frame.size, background)
+ background_im.putpalette(im_frames[0]["im"].palette)
+ bbox = _getbbox(background_im, im_frame)
+ else:
+ bbox = None
+ im_frames.append({"im": im_frame, "bbox": bbox, "encoderinfo": encoderinfo})
+
+ if len(im_frames) > 1:
+ 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
+
+ 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
+ elif "duration" in im.encoderinfo and isinstance(
+ im.encoderinfo["duration"], (list, tuple)
+ ):
+ # Since multiple frames will not be written, add together the frame durations
+ im.encoderinfo["duration"] = sum(im.encoderinfo["duration"])
+
+
+def _save_all(im, fp, filename):
+ _save(im, fp, filename, save_all=True)
+
+
+def _save(im, fp, filename, save_all=False):
+ # 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["optimize"] = im.encoderinfo.get("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):
+ interlace = im.encoderinfo.get("interlace", 1)
+
+ # workaround for @PIL153
+ if min(im.size) < 16:
+ interlace = 0
+
+ return interlace
+
+
+def _write_local_header(fp, im, offset, flags):
+ transparent_color_exists = False
+ try:
+ transparency = int(im.encoderinfo["transparency"])
+ except (KeyError, ValueError):
+ pass
+ else:
+ # optimize the block away if transparent color is not used
+ transparent_color_exists = True
+
+ used_palette_colors = _get_optimize(im, im.encoderinfo)
+ if used_palette_colors is not None:
+ # adjust the transparency index after optimize
+ try:
+ transparency = used_palette_colors.index(transparency)
+ except ValueError:
+ transparent_color_exists = False
+
+ if "duration" in im.encoderinfo:
+ duration = int(im.encoderinfo["duration"] / 10)
+ else:
+ duration = 0
+
+ disposal = int(im.encoderinfo.get("disposal", 0))
+
+ if transparent_color_exists or duration != 0 or disposal:
+ packed_flag = 1 if transparent_color_exists else 0
+ packed_flag |= disposal << 2
+ if not transparent_color_exists:
+ transparency = 0
+
+ fp.write(
+ b"!"
+ + o8(249) # extension intro
+ + o8(4) # length
+ + o8(packed_flag) # packed fields
+ + o16(duration) # duration
+ + o8(transparency) # 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, fp, filename):
+ # 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
+ 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, info):
+ """
+ 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", 0):
+ # 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
+
+ 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
+
+
+def _get_color_table_size(palette_bytes):
+ # 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):
+ """
+ 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<<n in size
+ actual_target_size_diff = (2 << color_table_size) - len(palette_bytes) // 3
+ if actual_target_size_diff > 0:
+ palette_bytes += o8(0) * 3 * actual_target_size_diff
+ return palette_bytes
+
+
+def _get_palette_bytes(im):
+ """
+ Gets the palette for inclusion in the gif header
+
+ :param im: Image object
+ :returns: Bytes, len<=768 suitable for inclusion in gif header
+ """
+ return im.palette.palette if im.palette else b""
+
+
+def _get_background(im, info_background):
+ 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
+ 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, info):
+ """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, im_frame, offset, params):
+ try:
+ im_frame.encoderinfo = params
+
+ # local image header
+ _write_local_header(fp, im_frame, offset, 0)
+
+ ImageFile._save(
+ im_frame, fp, [("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, palette=None, info=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)
+
+ """
+ used_palette_colors = _get_optimize(im, info)
+
+ if info is None:
+ 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, offset=(0, 0), **params):
+ """
+ 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
+
+ """
+
+ class Collector:
+ data = []
+
+ def write(self, data):
+ self.data.append(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/contrib/python/Pillow/py3/PIL/GimpGradientFile.py b/contrib/python/Pillow/py3/PIL/GimpGradientFile.py
new file mode 100644
index 00000000000..8e801be0b8a
--- /dev/null
+++ b/contrib/python/Pillow/py3/PIL/GimpGradientFile.py
@@ -0,0 +1,137 @@
+#
+# 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 math import log, pi, sin, sqrt
+
+from ._binary import o8
+
+EPSILON = 1e-10
+"""""" # Enable auto-doc for data member
+
+
+def linear(middle, pos):
+ 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, pos):
+ return pos ** (log(0.5) / log(max(middle, EPSILON)))
+
+
+def sine(middle, pos):
+ return (sin((-pi / 2.0) + pi * linear(middle, pos)) + 1.0) / 2.0
+
+
+def sphere_increasing(middle, pos):
+ return sqrt(1.0 - (linear(middle, pos) - 1.0) ** 2)
+
+
+def sphere_decreasing(middle, pos):
+ 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 = None
+
+ def getpalette(self, entries=256):
+ 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):
+ if fp.readline()[:13] != 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)
+
+ 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)
+
+ gradient.append((x0, x1, xm, rgb0, rgb1, segment))
+
+ self.gradient = gradient
diff --git a/contrib/python/Pillow/py3/PIL/GimpPaletteFile.py b/contrib/python/Pillow/py3/PIL/GimpPaletteFile.py
new file mode 100644
index 00000000000..d388928945a
--- /dev/null
+++ b/contrib/python/Pillow/py3/PIL/GimpPaletteFile.py
@@ -0,0 +1,56 @@
+#
+# 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.
+#
+
+import re
+
+from ._binary import o8
+
+
+class GimpPaletteFile:
+ """File handler for GIMP's palette format."""
+
+ rawmode = "RGB"
+
+ def __init__(self, fp):
+ self.palette = [o8(i) * 3 for i in range(256)]
+
+ if fp.readline()[:12] != b"GIMP Palette":
+ msg = "not a GIMP palette file"
+ raise SyntaxError(msg)
+
+ for i in range(256):
+ s = fp.readline()
+ if not s:
+ break
+
+ # skip fields and comment lines
+ if re.match(rb"\w+:|#", s):
+ continue
+ if len(s) > 100:
+ msg = "bad palette file"
+ raise SyntaxError(msg)
+
+ v = tuple(map(int, s.split()[:3]))
+ if len(v) != 3:
+ msg = "bad palette entry"
+ raise ValueError(msg)
+
+ self.palette[i] = o8(v[0]) + o8(v[1]) + o8(v[2])
+
+ self.palette = b"".join(self.palette)
+
+ def getpalette(self):
+ return self.palette, self.rawmode
diff --git a/contrib/python/Pillow/py3/PIL/GribStubImagePlugin.py b/contrib/python/Pillow/py3/PIL/GribStubImagePlugin.py
new file mode 100644
index 00000000000..c1c71da08c9
--- /dev/null
+++ b/contrib/python/Pillow/py3/PIL/GribStubImagePlugin.py
@@ -0,0 +1,73 @@
+#
+# 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 . import Image, ImageFile
+
+_handler = None
+
+
+def register_handler(handler):
+ """
+ Install application-specific GRIB image handler.
+
+ :param handler: Handler object.
+ """
+ global _handler
+ _handler = handler
+
+
+# --------------------------------------------------------------------
+# Image adapter
+
+
+def _accept(prefix):
+ return prefix[:4] == b"GRIB" and prefix[7] == 1
+
+
+class GribStubImageFile(ImageFile.StubImageFile):
+ format = "GRIB"
+ format_description = "GRIB"
+
+ def _open(self):
+ offset = self.fp.tell()
+
+ if not _accept(self.fp.read(8)):
+ msg = "Not a GRIB file"
+ raise SyntaxError(msg)
+
+ self.fp.seek(offset)
+
+ # make something up
+ self._mode = "F"
+ self._size = 1, 1
+
+ loader = self._load()
+ if loader:
+ loader.open(self)
+
+ def _load(self):
+ return _handler
+
+
+def _save(im, fp, filename):
+ 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/contrib/python/Pillow/py3/PIL/Hdf5StubImagePlugin.py b/contrib/python/Pillow/py3/PIL/Hdf5StubImagePlugin.py
new file mode 100644
index 00000000000..c26b480acf2
--- /dev/null
+++ b/contrib/python/Pillow/py3/PIL/Hdf5StubImagePlugin.py
@@ -0,0 +1,73 @@
+#
+# 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 . import Image, ImageFile
+
+_handler = None
+
+
+def register_handler(handler):
+ """
+ Install application-specific HDF5 image handler.
+
+ :param handler: Handler object.
+ """
+ global _handler
+ _handler = handler
+
+
+# --------------------------------------------------------------------
+# Image adapter
+
+
+def _accept(prefix):
+ return prefix[:8] == b"\x89HDF\r\n\x1a\n"
+
+
+class HDF5StubImageFile(ImageFile.StubImageFile):
+ format = "HDF5"
+ format_description = "HDF5"
+
+ def _open(self):
+ offset = self.fp.tell()
+
+ if not _accept(self.fp.read(8)):
+ msg = "Not an HDF file"
+ raise SyntaxError(msg)
+
+ self.fp.seek(offset)
+
+ # make something up
+ self._mode = "F"
+ self._size = 1, 1
+
+ loader = self._load()
+ if loader:
+ loader.open(self)
+
+ def _load(self):
+ return _handler
+
+
+def _save(im, fp, filename):
+ 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/contrib/python/Pillow/py3/PIL/IcnsImagePlugin.py b/contrib/python/Pillow/py3/PIL/IcnsImagePlugin.py
new file mode 100644
index 00000000000..0aa4f7a8458
--- /dev/null
+++ b/contrib/python/Pillow/py3/PIL/IcnsImagePlugin.py
@@ -0,0 +1,399 @@
+#
+# 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.
+#
+
+import io
+import os
+import struct
+import sys
+
+from . import Image, ImageFile, PngImagePlugin, features
+
+enable_jpeg2k = features.check_codec("jpg_2000")
+if enable_jpeg2k:
+ from . import Jpeg2KImagePlugin
+
+MAGIC = b"icns"
+HEADERSIZE = 8
+
+
+def nextheader(fobj):
+ return struct.unpack(">4sI", fobj.read(HEADERSIZE))
+
+
+def read_32t(fobj, start_length, size):
+ # 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, start_length, size):
+ """
+ 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 = byte[0]
+ if byte & 0x80:
+ blocksize = byte - 125
+ byte = fobj.read(1)
+ for i in range(blocksize):
+ data.append(byte)
+ else:
+ blocksize = byte + 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, start_length, size):
+ # 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, start_length, size):
+ (start, length) = start_length
+ fobj.seek(start)
+ sig = fobj.read(12)
+ if sig[:8] == b"\x89PNG\x0d\x0a\x1a\x0a":
+ fobj.seek(start)
+ im = PngImagePlugin.PngImageFile(fobj)
+ Image._decompression_bomb_check(im.size)
+ return {"RGBA": im}
+ elif (
+ sig[:4] == b"\xff\x4f\xff\x51"
+ or sig[:4] == 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):
+ """
+ fobj is a file-like object as an icns resource
+ """
+ # signature : (start, length)
+ self.dct = 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
+ dct[sig] = (i, blocksize)
+ fobj.seek(blocksize, io.SEEK_CUR)
+ i += blocksize
+
+ def itersizes(self):
+ 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):
+ sizes = self.itersizes()
+ if not sizes:
+ msg = "No 32bit icon resources found"
+ raise SyntaxError(msg)
+ return max(sizes)
+
+ def dataforsize(self, size):
+ """
+ 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=None):
+ if size is None:
+ size = self.bestsize()
+ if len(size) == 2:
+ size = (size[0], size[1], 1)
+ channels = self.dataforsize(size)
+
+ im = channels.get("RGBA", None)
+ if im:
+ return im
+
+ im = channels.get("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):
+ 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
+ def size(self):
+ return self._size
+
+ @size.setter
+ def size(self, value):
+ info_size = value
+ if info_size not in self.info["sizes"] and len(info_size) == 2:
+ info_size = (info_size[0], info_size[1], 1)
+ if (
+ info_size not in self.info["sizes"]
+ and len(info_size) == 3
+ and info_size[2] == 1
+ ):
+ simple_sizes = [
+ (size[0] * size[2], size[1] * size[2]) for size in self.info["sizes"]
+ ]
+ if value in simple_sizes:
+ info_size = self.info["sizes"][simple_sizes.index(value)]
+ if info_size 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):
+ if len(self.size) == 3:
+ self.best_size = self.size
+ self.size = (
+ self.best_size[0] * self.best_size[2],
+ self.best_size[1] * self.best_size[2],
+ )
+
+ 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, fp, filename):
+ """
+ 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": type, "size": HEADERSIZE + len(stream), "stream": stream}
+ )
+
+ # Header
+ fp.write(MAGIC)
+ file_length = HEADERSIZE # Header
+ file_length += HEADERSIZE + 8 * len(entries) # TOC
+ file_length += sum(entry["size"] 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["type"])
+ fp.write(struct.pack(">i", entry["size"]))
+
+ # Data
+ for entry in entries:
+ fp.write(entry["type"])
+ fp.write(struct.pack(">i", entry["size"]))
+ fp.write(entry["stream"])
+
+ if hasattr(fp, "flush"):
+ fp.flush()
+
+
+def _accept(prefix):
+ return prefix[:4] == 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"]:
+ imf.size = size
+ imf.save("out-%s-%s-%s.png" % size)
+ with Image.open(sys.argv[1]) as im:
+ im.save("out.png")
+ if sys.platform == "windows":
+ os.startfile("out.png")
diff --git a/contrib/python/Pillow/py3/PIL/IcoImagePlugin.py b/contrib/python/Pillow/py3/PIL/IcoImagePlugin.py
new file mode 100644
index 00000000000..0445a2ab22f
--- /dev/null
+++ b/contrib/python/Pillow/py3/PIL/IcoImagePlugin.py
@@ -0,0 +1,358 @@
+#
+# 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
+
+
+import warnings
+from io import BytesIO
+from math import ceil, log
+
+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, fp, filename):
+ 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, [("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):
+ return prefix[:4] == _MAGIC
+
+
+class IcoFile:
+ def __init__(self, buf):
+ """
+ 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)
+
+ icon_header = {
+ "width": s[0],
+ "height": s[1],
+ "nb_color": s[2], # No. of colors in image (0 if >=8bpp)
+ "reserved": s[3],
+ "planes": i16(s, 4),
+ "bpp": i16(s, 6),
+ "size": i32(s, 8),
+ "offset": i32(s, 12),
+ }
+
+ # See Wikipedia
+ for j in ("width", "height"):
+ if not icon_header[j]:
+ icon_header[j] = 256
+
+ # See Wikipedia notes about color depth.
+ # We need this just to differ images with equal sizes
+ icon_header["color_depth"] = (
+ icon_header["bpp"]
+ or (
+ icon_header["nb_color"] != 0
+ and ceil(log(icon_header["nb_color"], 2))
+ )
+ or 256
+ )
+
+ icon_header["dim"] = (icon_header["width"], icon_header["height"])
+ icon_header["square"] = icon_header["width"] * icon_header["height"]
+
+ 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['width'])
+ self.entry = sorted(self.entry, key=lambda x: x["square"])
+ self.entry.reverse()
+
+ def sizes(self):
+ """
+ Get a list of all available icon sizes and color depths.
+ """
+ return {(h["width"], h["height"]) for h in self.entry}
+
+ def getentryindex(self, size, bpp=False):
+ 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, bpp=False):
+ """
+ Get an image from the icon
+ """
+ return self.frame(self.getentryindex(size, bpp))
+
+ def frame(self, idx):
+ """
+ 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"])
+
+ 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] = d, (0, 0) + im.size, o, a
+
+ # figure out where AND mask image starts
+ bpp = header["bpp"]
+ if 32 == bpp:
+ # 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
+ mask = Image.frombuffer(
+ "L", # 8bpp
+ im.size, # (w, h)
+ alpha_bytes, # source chars
+ "raw", # raw decoder
+ ("L", 0, -1), # 8bpp inverted, unpadded, reversed
+ )
+ 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
+ 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
+ )
+
+ # now we have two images, im is XOR image and mask is AND image
+
+ # apply mask image as alpha channel
+ 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):
+ self.ico = IcoFile(self.fp)
+ self.info["sizes"] = self.ico.sizes()
+ self.size = self.ico.entry[0]["dim"]
+ self.load()
+
+ @property
+ def size(self):
+ return self._size
+
+ @size.setter
+ def size(self, value):
+ 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):
+ 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.pyaccess = None
+ self._mode = im.mode
+ 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
+
+ def load_seek(self):
+ # 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/contrib/python/Pillow/py3/PIL/ImImagePlugin.py b/contrib/python/Pillow/py3/PIL/ImImagePlugin.py
new file mode 100644
index 00000000000..b42ba7cac70
--- /dev/null
+++ b/contrib/python/Pillow/py3/PIL/ImImagePlugin.py
@@ -0,0 +1,371 @@
+#
+# 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.
+#
+
+
+import os
+import re
+
+from . import Image, ImageFile, ImagePalette
+
+# --------------------------------------------------------------------
+# 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": ("RGBX", "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 i in range(2, 33):
+ OPEN[f"L*{i} image"] = ("F", f"F;{i}")
+
+
+# --------------------------------------------------------------------
+# Read IM directory
+
+split = re.compile(rb"^([A-Za-z][^:]*):[ \t]*(.*)[ \t]*$")
+
+
+def number(s):
+ 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):
+ # 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[-2:] == b"\r\n":
+ s = s[:-2]
+ elif s[-1:] == 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 = "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 s[:1] != 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[:2] == "F;":
+ # ifunc95 formats
+ try:
+ # use bit decoder (if necessary)
+ bits = int(self.rawmode[2:])
+ if bits not in [8, 16, 32]:
+ self.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 = [
+ ("raw", (0, 0) + self.size, offs, ("G", 0, -1)),
+ ("raw", (0, 0) + self.size, offs + size, ("R", 0, -1)),
+ ("raw", (0, 0) + self.size, offs + 2 * size, ("B", 0, -1)),
+ ]
+ else:
+ # LabEye/IFUNC files
+ self.tile = [("raw", (0, 0) + self.size, offs, (self.rawmode, 0, -1))]
+
+ @property
+ def n_frames(self):
+ return self.info[FRAMES]
+
+ @property
+ def is_animated(self):
+ return self.info[FRAMES] > 1
+
+ def seek(self, frame):
+ if not self._seek_check(frame):
+ return
+
+ 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 = [("raw", (0, 0) + self.size, offs, (self.rawmode, 0, -1))]
+
+ def tell(self):
+ 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, fp, filename):
+ 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
+ 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(("Image size (x*y): %d*%d\r\n" % im.size).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, [("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/contrib/python/Pillow/py3/PIL/Image.py b/contrib/python/Pillow/py3/PIL/Image.py
new file mode 100644
index 00000000000..1adca9ad5b1
--- /dev/null
+++ b/contrib/python/Pillow/py3/PIL/Image.py
@@ -0,0 +1,3940 @@
+#
+# 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.
+#
+
+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, MutableMapping
+from enum import IntEnum
+from pathlib import Path
+
+try:
+ import defusedxml.ElementTree as ElementTree
+except ImportError:
+ ElementTree = None
+
+# 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 ._util import DeferredError, is_path
+
+logger = logging.getLogger(__name__)
+
+
+class DecompressionBombWarning(RuntimeWarning):
+ pass
+
+
+class DecompressionBombError(Exception):
+ pass
+
+
+# Limit to around a quarter gigabyte for a 24-bit (3 bpp) image
+MAX_IMAGE_PIXELS = 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(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
+
+
+USE_CFFI_ACCESS = False
+try:
+ import cffi
+except ImportError:
+ cffi = None
+
+
+def isImageType(t):
+ """
+ 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
+ """
+ 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
+
+ID = []
+OPEN = {}
+MIME = {}
+SAVE = {}
+SAVE_ALL = {}
+EXTENSION = {}
+DECODERS = {}
+ENCODERS = {}
+
+# --------------------------------------------------------------------
+# Modes
+
+_ENDIAN = "<" if sys.byteorder == "little" else ">"
+
+
+def _conv_type_shape(im):
+ m = ImageMode.getmode(im.mode)
+ shape = (im.height, im.width)
+ extra = len(m.bands)
+ if extra != 1:
+ shape += (extra,)
+ return shape, m.typestr
+
+
+MODES = ["1", "CMYK", "F", "HSV", "I", "L", "LAB", "P", "RGB", "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):
+ """
+ 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):
+ """
+ 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):
+ """
+ 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):
+ """
+ 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():
+ """
+ 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():
+ """
+ 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 0
+
+ for plugin in _plugins:
+ try:
+ logger.debug("Importing %s", plugin)
+ __import__(f"PIL.{plugin}", globals(), locals(), [])
+ except ImportError as e:
+ logger.debug("Image: failed to import %s: %s", plugin, e)
+
+ if OPEN or SAVE:
+ _initialized = 2
+ return 1
+
+
+# --------------------------------------------------------------------
+# Codec factories (used by tobytes/frombytes and ImageFile.load)
+
+
+def _getdecoder(mode, decoder_name, args, extra=()):
+ # 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, 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, encoder_name, args, extra=()):
+ # 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, 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 _E:
+ def __init__(self, scale, offset):
+ self.scale = scale
+ self.offset = offset
+
+ def __neg__(self):
+ return _E(-self.scale, -self.offset)
+
+ def __add__(self, other):
+ if isinstance(other, _E):
+ return _E(self.scale + other.scale, self.offset + other.offset)
+ return _E(self.scale, self.offset + other)
+
+ __radd__ = __add__
+
+ def __sub__(self, other):
+ return self + -other
+
+ def __rsub__(self, other):
+ return other + -self
+
+ def __mul__(self, other):
+ if isinstance(other, _E):
+ return NotImplemented
+ return _E(self.scale * other, self.offset * other)
+
+ __rmul__ = __mul__
+
+ def __truediv__(self, other):
+ if isinstance(other, _E):
+ return NotImplemented
+ return _E(self.scale / other, self.offset / other)
+
+
+def _getscaleoffset(expr):
+ a = expr(_E(1, 0))
+ return (a.scale, a.offset) if isinstance(a, _E) else (0, a)
+
+
+# --------------------------------------------------------------------
+# Implementation wrapper
+
+
+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 = None
+ format_description = None
+ _close_exclusive_fp_after_loading = True
+
+ def __init__(self):
+ # FIXME: take "new" parameters / other image?
+ # FIXME: turn mode and size into delegating properties?
+ self.im = None
+ self._mode = ""
+ self._size = (0, 0)
+ self.palette = None
+ self.info = {}
+ self.readonly = 0
+ self.pyaccess = None
+ self._exif = None
+
+ @property
+ def width(self):
+ return self.size[0]
+
+ @property
+ def height(self):
+ return self.size[1]
+
+ @property
+ def size(self):
+ return self._size
+
+ @property
+ def mode(self):
+ return self._mode
+
+ def _new(self, im):
+ 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):
+ if hasattr(self, "fp") and getattr(self, "_exclusive_fp", False):
+ if getattr(self, "_fp", False):
+ if self._fp != self.fp:
+ self._fp.close()
+ self._fp = DeferredError(ValueError("Operation on closed image"))
+ if self.fp:
+ self.fp.close()
+ self.fp = None
+
+ def close(self):
+ """
+ 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:
+ if getattr(self, "_fp", False):
+ if self._fp != self.fp:
+ self._fp.close()
+ self._fp = DeferredError(ValueError("Operation on closed image"))
+ if self.fp:
+ self.fp.close()
+ self.fp = None
+ except Exception as msg:
+ logger.debug("Error closing: %s", msg)
+
+ if getattr(self, "map", None):
+ self.map = 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):
+ self.load()
+ self.im = self.im.copy()
+ self.pyaccess = None
+ self.readonly = 0
+
+ def _ensure_mutable(self):
+ if self.readonly:
+ self._copy()
+ else:
+ self.load()
+
+ def _dump(self, file=None, format=None, **options):
+ suffix = ""
+ if format:
+ suffix = "." + 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):
+ return (
+ self.__class__ is other.__class__
+ and 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):
+ return "<%s.%s image mode=%s size=%dx%d at 0x%X>" % (
+ self.__class__.__module__,
+ self.__class__.__name__,
+ self.mode,
+ self.size[0],
+ self.size[1],
+ id(self),
+ )
+
+ def _repr_pretty_(self, p, cycle):
+ """IPython plain text display support"""
+
+ # Same as __repr__ but without unpredictable id(self),
+ # to keep Jupyter notebook `text/plain` output stable.
+ p.text(
+ "<%s.%s image mode=%s size=%dx%d>"
+ % (
+ self.__class__.__module__,
+ self.__class__.__name__,
+ self.mode,
+ self.size[0],
+ self.size[1],
+ )
+ )
+
+ def _repr_image(self, image_format, **kwargs):
+ """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):
+ """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):
+ """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):
+ # numpy array interface support
+ new = {"version": 3}
+ try:
+ 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()
+ except Exception as e:
+ if not isinstance(e, (MemoryError, RecursionError)):
+ try:
+ import numpy
+ from packaging.version import parse as parse_version
+ except ImportError:
+ pass
+ else:
+ if parse_version(numpy.__version__) < parse_version("1.23"):
+ warnings.warn(e)
+ raise
+ new["shape"], new["typestr"] = _conv_type_shape(self)
+ return new
+
+ def __getstate__(self):
+ im_data = self.tobytes() # load image first
+ return [self.info, self.mode, self.size, self.getpalette(), im_data]
+
+ def __setstate__(self, state):
+ Image.__init__(self)
+ info, mode, size, palette, data = state
+ 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="raw", *args):
+ """
+ Return image as a bytes object.
+
+ .. warning::
+
+ This method returns the raw image data from the 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.
+
+ 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.
+ """
+
+ # may pass tuple instead of argument list
+ if len(args) == 1 and isinstance(args[0], tuple):
+ args = args[0]
+
+ if encoder_name == "raw" and args == ():
+ args = self.mode
+
+ self.load()
+
+ if self.width == 0 or self.height == 0:
+ return b""
+
+ # unpack data
+ e = _getencoder(self.mode, encoder_name, args)
+ e.setimage(self.im)
+
+ bufsize = max(65536, 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="image"):
+ """
+ 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, decoder_name="raw", *args):
+ """
+ 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.
+ """
+
+ # may pass tuple instead of argument list
+ if len(args) == 1 and isinstance(args[0], tuple):
+ args = args[0]
+
+ # default format
+ if decoder_name == "raw" and args == ():
+ args = self.mode
+
+ # unpack data
+ d = _getdecoder(self.mode, decoder_name, 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):
+ """
+ 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: :ref:`PixelAccess` or :py:class:`PIL.PyAccess`
+ """
+ if self.im is not None and self.palette and self.palette.dirty:
+ # realize palette
+ mode, arr = self.palette.getdata()
+ self.im.putpalette(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:
+ palette_mode = "RGBA" if mode.startswith("RGBA") else "RGB"
+ self.palette.mode = palette_mode
+ self.palette.palette = self.im.getpalette(palette_mode, palette_mode)
+
+ if self.im is not None:
+ if cffi and USE_CFFI_ACCESS:
+ if self.pyaccess:
+ return self.pyaccess
+ from . import PyAccess
+
+ self.pyaccess = PyAccess.new(self, self.readonly)
+ if self.pyaccess:
+ return self.pyaccess
+ return self.im.pixel_access(self.readonly)
+
+ def verify(self):
+ """
+ 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=None, matrix=None, dither=None, palette=Palette.WEB, colors=256
+ ):
+ """
+ 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.
+
+ The current version supports all possible conversions between
+ "L", "RGB" and "CMYK". The ``matrix`` argument only supports "L"
+ and "RGB".
+
+ When translating a color image to greyscale (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 greyscale ("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.
+ """
+
+ 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, v):
+ v = m[0] * v[0] + m[1] * v[1] + m[2] * v[2] + m[3] * 0.5
+ return max(0, min(255, int(v)))
+
+ 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(0, 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") and mode in ("LA", "RGBA")) or (
+ self.mode == "RGB" and mode == "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":
+ trns_im.putpalette(self.palette)
+ if isinstance(t, tuple):
+ err = "Couldn't allocate a palette color for transparency"
+ 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(trns, 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):
+ other_mode = mode if self.mode == "LAB" else self.mode
+ if other_mode in ("RGB", "RGBA", "RGBX"):
+ from . import ImageCms
+
+ srgb = ImageCms.createProfile("sRGB")
+ lab = ImageCms.createProfile("LAB")
+ profiles = [lab, srgb] if self.mode == "LAB" else [srgb, lab]
+ transform = ImageCms.buildTransform(
+ profiles[0], profiles[1], self.mode, mode
+ )
+ return transform.apply(self)
+
+ # 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":
+ try:
+ new_im.info["transparency"] = new_im.palette.getcolor(trns, new_im)
+ 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=256,
+ method=None,
+ kmeans=0,
+ palette=None,
+ dither=Dither.FLOYDSTEINBERG,
+ ):
+ """
+ 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
+ :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 != "RGB" and self.mode != "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)
+ new_im.palette = palette.palette.copy()
+ return new_im
+
+ im = self._new(self.im.quantize(colors, method, kmeans))
+
+ from . import ImagePalette
+
+ mode = im.im.getpalettemode()
+ palette = im.im.getpalette(mode, mode)[: colors * len(mode)]
+ im.palette = ImagePalette.ImagePalette(mode, palette)
+
+ return im
+
+ def copy(self):
+ """
+ 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=None):
+ """
+ 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, box):
+ """
+ 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, size):
+ """
+ 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 greyscale 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, ymargin=None):
+ if ymargin is None:
+ ymargin = xmargin
+ self.load()
+ return self._new(self.im.expand(xmargin, ymargin))
+
+ def filter(self, filter):
+ """
+ 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 isinstance(filter, Callable):
+ 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 = []
+ for c in range(self.im.bands):
+ ims.append(self._new(filter.filter(self.im.getband(c))))
+ return merge(self.mode, ims)
+
+ def getbands(self):
+ """
+ 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=True):
+ """
+ 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=256):
+ """
+ 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 = []
+ for i in range(256):
+ if h[i]:
+ out.append((h[i], i))
+ if len(out) > maxcolors:
+ return None
+ return out
+ return self.im.getcolors(maxcolors)
+
+ def getdata(self, band=None):
+ """
+ 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):
+ """
+ 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:
+ extrema = []
+ for i in range(self.im.bands):
+ extrema.append(self.im.getband(i).getextrema())
+ return tuple(extrema)
+ return self.im.getextrema()
+
+ def _getxmp(self, xmp_tags):
+ def get_name(tag):
+ return re.sub("^{[^}]+}", "", tag)
+
+ def get_value(element):
+ value = {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 {}
+ else:
+ root = ElementTree.fromstring(xmp_tags)
+ return {get_name(root.tag): get_value(root)}
+
+ def getexif(self):
+ """
+ Gets EXIF data from the image.
+
+ :returns: an :py:class:`~PIL.Image.Exif` object.
+ """
+ if self._exif is None:
+ self._exif = Exif()
+ self._exif._loaded = False
+ 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")
+ if xmp_tags:
+ match = re.search(r'tiff:Orientation(="|>)([0-9])', xmp_tags)
+ if match:
+ self._exif[ExifTags.Base.Orientation] = int(match[2])
+
+ return self._exif
+
+ def _reload_exif(self):
+ if self._exif is None or not self._exif._loaded:
+ return
+ self._exif._loaded = False
+ self.getexif()
+
+ def get_child_images(self):
+ 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(513):
+ ifds.append((ifd1, exif._info.next))
+
+ offset = None
+ for ifd, ifd_offset in ifds:
+ current_offset = self.fp.tell()
+ if offset is None:
+ offset = current_offset
+
+ fp = self.fp
+ thumbnail_offset = ifd.get(513)
+ if thumbnail_offset is not None:
+ try:
+ thumbnail_offset += self._exif_offset
+ except AttributeError:
+ pass
+ self.fp.seek(thumbnail_offset)
+ data = self.fp.read(ifd.get(514))
+ fp = io.BytesIO(data)
+
+ with open(fp) as im:
+ if thumbnail_offset is None:
+ im._frame_pos = [ifd_offset]
+ im._seek(0)
+ im.load()
+ child_images.append(im)
+
+ if offset is not None:
+ self.fp.seek(offset)
+ return child_images
+
+ def getim(self):
+ """
+ Returns a capsule that points to the internal image memory.
+
+ :returns: A capsule object.
+ """
+
+ self.load()
+ return self.im.ptr
+
+ def getpalette(self, rawmode="RGB"):
+ """
+ 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.
+ """
+ return (
+ self.mode in ("LA", "La", "PA", "RGBA", "RGBa")
+ or (self.mode == "P" and self.palette.mode.endswith("A"))
+ or "transparency" in self.info
+ )
+
+ def apply_transparency(self):
+ """
+ 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")
+ 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):
+ """
+ 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()
+ if self.pyaccess:
+ return self.pyaccess.getpixel(xy)
+ return self.im.getpixel(tuple(xy))
+
+ def getprojection(self):
+ """
+ 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=None, extrema=None):
+ """
+ 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 greyscale ("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 greyscale 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"):
+ if extrema is None:
+ extrema = self.getextrema()
+ return self.im.histogram(extrema)
+ return self.im.histogram()
+
+ def entropy(self, mask=None, extrema=None):
+ """
+ Calculates and returns the entropy for the image.
+
+ A bilevel image (mode "1") is treated as a greyscale ("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 greyscale 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"):
+ if extrema is None:
+ extrema = self.getextrema()
+ return self.im.entropy(extrema)
+ return self.im.entropy()
+
+ def paste(self, im, box=None, mask=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 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 isImageType(box) and mask is None:
+ # 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 isImageType(im):
+ size = im.size
+ elif isImageType(mask):
+ 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])
+
+ if isinstance(im, str):
+ from . import ImageColor
+
+ im = ImageColor.getcolor(im, self.mode)
+
+ elif isImageType(im):
+ 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)
+ im = im.im
+
+ self._ensure_mutable()
+
+ if mask:
+ mask.load()
+ self.im.paste(im, box, mask.im)
+ else:
+ self.im.paste(im, box)
+
+ def alpha_composite(self, im, dest=(0, 0), source=(0, 0)):
+ """'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 tuple"
+ raise ValueError(msg)
+ if not isinstance(dest, (list, tuple)):
+ msg = "Destination must be a tuple"
+ raise ValueError(msg)
+ if len(source) not in (2, 4):
+ msg = "Source must be a 2 or 4-tuple"
+ raise ValueError(msg)
+ if not len(dest) == 2:
+ msg = "Destination must be a 2-tuple"
+ raise ValueError(msg)
+ if min(source) < 0:
+ msg = "Source must be non-negative"
+ raise ValueError(msg)
+
+ if len(source) == 2:
+ source = source + im.size
+
+ # over image, crop if it's not the whole thing.
+ if source == (0, 0) + im.size:
+ overlay = im
+ else:
+ overlay = im.crop(source)
+
+ # target for the paste
+ box = 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, mode=None):
+ """
+ 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, data):
+ # Return result
+ :param mode: Output mode (default is same as input). In the
+ current version, 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)
+ return self._new(self.im.point_transform(scale, offset))
+ # for other modes, convert the function to a table
+ lut = [lut(i) for i in range(256)] * self.im.bands
+
+ 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":
+ lut = [round(i) for i in lut]
+ return self._new(self.im.point(lut, mode))
+
+ def putalpha(self, alpha):
+ """
+ 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 or
+ other color value.
+ """
+
+ 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"):
+ raise ValueError from e # sanity check
+ self.im = im
+ self.pyaccess = None
+ 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 isImageType(alpha):
+ # 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, scale=1.0, offset=0.0):
+ """
+ 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, rawmode="RGB"):
+ """
+ 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):
+ palette = ImagePalette.raw(data.rawmode, data.palette)
+ 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 = "RGB"
+ self.load() # install new palette
+
+ def putpixel(self, xy, value):
+ """
+ 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.pyaccess:
+ return self.pyaccess.putpixel(xy, value)
+
+ 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]
+ value = self.palette.getcolor(value, self)
+ if self.mode == "PA":
+ value = (value, alpha)
+ return self.im.putpixel(xy, value)
+
+ def remap_palette(self, dest_map, source_palette=None):
+ """
+ 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))
+
+ 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 + ";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, resample, box):
+ """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, resample=None, box=None, reducing_gap=None):
+ """
+ Returns a resized copy of this image.
+
+ :param size: The requested size in pixels, as a 2-tuple:
+ (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 specifies a number
+ of bits, such as "I;16", 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:
+ type_special = ";" in self.mode
+ resample = Resampling.NEAREST if type_special 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 += " 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)
+
+ size = tuple(size)
+
+ self.load()
+ if box is None:
+ box = (0, 0) + self.size
+ else:
+ box = tuple(box)
+
+ 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, resample, box)
+ factor = (factor_x, factor_y)
+ if callable(self.reduce):
+ self = self.reduce(factor, box=reduce_box)
+ else:
+ self = 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, box=None):
+ """
+ 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
+ else:
+ box = tuple(box)
+
+ 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,
+ resample=Resampling.NEAREST,
+ expand=0,
+ center=None,
+ translate=None,
+ fillcolor=None,
+ ):
+ """
+ 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:
+ # FIXME These should be rounded to ints?
+ rotn_center = (w / 2.0, h / 2.0)
+ else:
+ rotn_center = center
+
+ 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, y, matrix):
+ (a, b, c, d, e, f) = matrix
+ return a * x + b * y + c, d * x + e * y + f
+
+ matrix[2], matrix[5] = transform(
+ -rotn_center[0] - post_trans[0], -rotn_center[1] - post_trans[1], matrix
+ )
+ matrix[2] += rotn_center[0]
+ matrix[5] += rotn_center[1]
+
+ if expand:
+ # calculate output size
+ xx = []
+ yy = []
+ for x, y in ((0, 0), (w, 0), (w, h), (0, h)):
+ x, y = transform(x, y, matrix)
+ xx.append(x)
+ yy.append(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, format=None, **params):
+ """
+ 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), pathlib.Path 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.
+ :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 = ""
+ open_fp = False
+ if isinstance(fp, Path):
+ filename = str(fp)
+ open_fp = True
+ elif is_path(fp):
+ filename = 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 = fp.name
+
+ # may mutate self!
+ self._ensure_mutable()
+
+ save_all = params.pop("save_all", False)
+ self.encoderinfo = params
+ self.encoderconfig = ()
+
+ preinit()
+
+ ext = os.path.splitext(filename)[1].lower()
+
+ 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
+
+ if format.upper() not in SAVE:
+ init()
+ if 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")
+
+ try:
+ save_handler(self, fp, filename)
+ except Exception:
+ if open_fp:
+ fp.close()
+ if created:
+ try:
+ os.remove(filename)
+ except PermissionError:
+ pass
+ raise
+ if open_fp:
+ fp.close()
+
+ def seek(self, frame):
+ """
+ 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:
+ raise EOFError
+
+ def show(self, title=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):
+ """
+ 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:
+ ims = [self.copy()]
+ else:
+ ims = map(self._new, self.im.split())
+ return tuple(ims)
+
+ def getchannel(self, channel):
+ """
+ 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):
+ """
+ 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, resample=Resampling.BICUBIC, reducing_gap=2.0):
+ """
+ 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():
+ def round_aspect(number, key):
+ 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
+
+ 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
+
+ box = None
+ if reducing_gap is not None:
+ size = preserve_aspect_ratio()
+ if size is None:
+ return
+
+ res = self.draft(None, (size[0] * reducing_gap, size[1] * reducing_gap))
+ if res is not None:
+ box = res[1]
+ if box is None:
+ self.load()
+
+ # load() may have changed the size of the image
+ size = preserve_aspect_ratio()
+ if size is None:
+ return
+
+ if self.size != size:
+ im = self.resize(size, resample, box=box, reducing_gap=reducing_gap)
+
+ self.im = im.im
+ self._size = size
+ self._mode = self.im.mode
+
+ self.readonly = 0
+ self.pyaccess = None
+
+ # FIXME: the different transform methods need further explanation
+ # instead of bloating the method docs, add a separate chapter.
+ def transform(
+ self,
+ size,
+ method,
+ data=None,
+ resample=Resampling.NEAREST,
+ fill=1,
+ fillcolor=None,
+ ):
+ """
+ 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
+
+ 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, image, method, data, resample=Resampling.NEAREST, fill=1
+ ):
+ 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):
+ msg = {
+ Resampling.BOX: "Image.Resampling.BOX",
+ Resampling.HAMMING: "Image.Resampling.HAMMING",
+ Resampling.LANCZOS: "Image.Resampling.LANCZOS",
+ }[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 += " 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.transform2(box, image.im, method, data, resample, fill)
+
+ def transpose(self, method):
+ """
+ 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):
+ """
+ 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):
+ """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):
+ """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:
+ """
+ Used as a mixin by point transforms
+ (for use with :py:meth:`~PIL.Image.Image.point`)
+ """
+
+ pass
+
+
+class ImageTransformHandler:
+ """
+ Used as a mixin by geometry transforms
+ (for use with :py:meth:`~PIL.Image.Image.transform`)
+ """
+
+ pass
+
+
+# --------------------------------------------------------------------
+# Factories
+
+#
+# Debugging
+
+
+def _wedge():
+ """Create greyscale wedge (for debugging only)"""
+
+ return Image()._new(core.wedge("L"))
+
+
+def _check_size(size):
+ """
+ Common check to enforce type and sanity check on size tuples
+
+ :param size: Should be a 2 tuple of (width, height)
+ :returns: True, or raises a ValueError
+ """
+
+ if not isinstance(size, (list, tuple)):
+ msg = "Size must be a tuple"
+ raise ValueError(msg)
+ if len(size) != 2:
+ msg = "Size must be a tuple of length 2"
+ raise ValueError(msg)
+ if size[0] < 0 or size[1] < 0:
+ msg = "Width and height must be >= 0"
+ raise ValueError(msg)
+
+ return True
+
+
+def new(mode, size, color=0):
+ """
+ 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.
+ """
+
+ _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 len(color) in [3, 4]:
+ # RGB or RGBA value for a P image
+ from . import ImagePalette
+
+ im.palette = ImagePalette.ImagePalette()
+ color = im.palette.getcolor(color)
+ return im._new(core.fill(mode, size, color))
+
+
+def frombytes(mode, size, data, decoder_name="raw", *args):
+ """
+ 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 <file-codecs>`.
+
+ 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)
+
+ # may pass tuple instead of argument list
+ if len(args) == 1 and isinstance(args[0], tuple):
+ args = args[0]
+
+ if decoder_name == "raw" and args == ():
+ args = mode
+
+ im = new(mode, size)
+ im.frombytes(data, decoder_name, args)
+ return im
+
+
+def frombuffer(mode, size, data, decoder_name="raw", *args):
+ """
+ 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.
+
+ In the current version, 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)
+
+
+def fromarray(obj, mode=None):
+ """
+ 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``.
+
+ 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:
+ msg = "Cannot handle this data type: %s, %s" % typekey
+ raise TypeError(msg) from e
+ else:
+ 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()
+ else:
+ obj = obj.tostring()
+
+ return frombuffer(mode, size, obj, "raw", rawmode, 0, 1)
+
+
+def fromqimage(im):
+ """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):
+ """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;16"),
+ ((1, 1), ">u2"): ("I", "I;16B"),
+ ((1, 1), "<i2"): ("I", "I;16S"),
+ ((1, 1), ">i2"): ("I", "I;16BS"),
+ ((1, 1), "<u4"): ("I", "I;32"),
+ ((1, 1), ">u4"): ("I", "I;32B"),
+ ((1, 1), "<i4"): ("I", "I;32S"),
+ ((1, 1), ">i4"): ("I", "I;32BS"),
+ ((1, 1), "<f4"): ("F", "F;32F"),
+ ((1, 1), ">f4"): ("F", "F;32BF"),
+ ((1, 1), "<f8"): ("F", "F;64F"),
+ ((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), _ENDIAN + "i4"): ("I", "I"),
+ ((1, 1), _ENDIAN + "f4"): ("F", "F"),
+}
+
+
+def _decompression_bomb_check(size):
+ 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, mode="r", formats=None):
+ """
+ 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), pathlib.Path 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)}"
+ raise ValueError(msg)
+ elif isinstance(fp, io.StringIO):
+ msg = (
+ "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"
+ raise TypeError(msg)
+
+ exclusive_fp = False
+ filename = ""
+ if isinstance(fp, Path):
+ filename = str(fp.resolve())
+ elif is_path(fp):
+ filename = fp
+
+ if filename:
+ fp = builtins.open(filename, "rb")
+ exclusive_fp = True
+
+ try:
+ fp.seek(0)
+ except (AttributeError, io.UnsupportedOperation):
+ fp = io.BytesIO(fp.read())
+ exclusive_fp = True
+
+ prefix = fp.read(16)
+
+ preinit()
+
+ accept_warnings = []
+
+ def _open_core(fp, filename, prefix, formats):
+ 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 type(result) in [str, bytes]:
+ accept_warnings.append(result)
+ elif result:
+ fp.seek(0)
+ im = factory(fp, filename)
+ _decompression_bomb_check(im.size)
+ return im
+ except (SyntaxError, IndexError, TypeError, struct.error):
+ # Leave disabled by default, spams the logs with image
+ # opening failures that are entirely expected.
+ # logger.debug("", exc_info=True)
+ continue
+ 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 = formats.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 accept_warnings:
+ warnings.warn(message)
+ msg = "cannot identify image file %r" % (filename if filename else fp)
+ raise UnidentifiedImageError(msg)
+
+
+#
+# Image processing.
+
+
+def alpha_composite(im1, im2):
+ """
+ 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, im2, alpha):
+ """
+ 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, image2, mask):
+ """
+ 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, *args):
+ """
+ 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, bands):
+ """
+ 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, factory, accept=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, mimetype):
+ """
+ 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, driver):
+ """
+ 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, driver):
+ """
+ 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, extension):
+ """
+ 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, extensions):
+ """
+ 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():
+ """
+ Returns a dictionary containing all file extensions belonging
+ to registered plugins
+ """
+ init()
+ return EXTENSION
+
+
+def register_decoder(name, decoder):
+ """
+ Registers an image decoder. This function should not be
+ used in application code.
+
+ :param name: The name of the decoder
+ :param decoder: A callable(mode, args) that returns an
+ ImageFile.PyDecoder object
+
+ .. versionadded:: 4.1.0
+ """
+ DECODERS[name] = decoder
+
+
+def register_encoder(name, encoder):
+ """
+ Registers an image encoder. This function should not be
+ used in application code.
+
+ :param name: The name of the encoder
+ :param encoder: A callable(mode, args) that returns an
+ ImageFile.PyEncoder object
+
+ .. versionadded:: 4.1.0
+ """
+ ENCODERS[name] = encoder
+
+
+# --------------------------------------------------------------------
+# Simple display support.
+
+
+def _show(image, **options):
+ from . import ImageShow
+
+ ImageShow.show(image, **options)
+
+
+# --------------------------------------------------------------------
+# Effects
+
+
+def effect_mandelbrot(size, extent, quality):
+ """
+ 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, sigma):
+ """
+ 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):
+ """
+ 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):
+ """
+ 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=None):
+ if env is None:
+ env = 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:
+ continue
+
+ var = env[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(var) * units
+ except ValueError:
+ warnings.warn(f"{var_name} is not int")
+ continue
+
+ try:
+ setter(var)
+ except ValueError as e:
+ warnings.warn(f"{var_name}: {e}")
+
+
+_apply_env_variables()
+atexit.register(core.clear_cache)
+
+
+class Exif(MutableMapping):
+ """
+ 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 = None
+ bigtiff = False
+
+ def __init__(self):
+ self._data = {}
+ self._hidden_data = {}
+ self._ifds = {}
+ self._info = None
+ self._loaded_exif = None
+
+ def _fixup(self, value):
+ try:
+ if len(value) == 1 and isinstance(value, tuple):
+ return value[0]
+ except Exception:
+ pass
+ return value
+
+ def _fixup_dict(self, src_dict):
+ # 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):
+ 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):
+ pass
+ else:
+ from . import TiffImagePlugin
+
+ info = TiffImagePlugin.ImageFileDirectory_v2(self.head)
+ info.load(self.fp)
+ return self._fixup_dict(info)
+
+ def _get_head(self):
+ 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):
+ # 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()
+ if data and data.startswith(b"Exif\x00\x00"):
+ data = data[6:]
+ if not data:
+ self._info = None
+ return
+
+ self.fp = 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, offset=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):
+ merged_dict = dict(self)
+
+ # get EXIF extension
+ if ExifTags.IFD.Exif in self:
+ ifd = self._get_ifd_dict(self[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]
+ )
+
+ return merged_dict
+
+ def tobytes(self, offset=8):
+ from . import TiffImagePlugin
+
+ head = self._get_head()
+ ifd = TiffImagePlugin.ImageFileDirectory_v2(ifh=head)
+ 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):
+ if tag not in self._ifds:
+ if tag == ExifTags.IFD.IFD1:
+ if self._info is not None and self._info.next != 0:
+ self._ifds[tag] = self._get_ifd_dict(self._info.next)
+ elif tag in [ExifTags.IFD.Exif, ExifTags.IFD.GPSInfo]:
+ offset = self._hidden_data.get(tag, self.get(tag))
+ if offset is not None:
+ self._ifds[tag] = self._get_ifd_dict(offset)
+ 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[:8] == b"FUJIFILM":
+ ifd_offset = i32le(tag_data, 8)
+ ifd_data = tag_data[ifd_offset:]
+
+ makernote = {}
+ for i in range(0, struct.unpack("<H", ifd_data[:2])[0]):
+ ifd_tag, typ, count, data = struct.unpack(
+ "<HHL4s", ifd_data[i * 12 + 2 : (i + 1) * 12 + 2]
+ )
+ try:
+ (
+ unit_size,
+ handler,
+ ) = ImageFileDirectory_v2._load_dispatch[typ]
+ except KeyError:
+ continue
+ size = count * unit_size
+ if size > 4:
+ (offset,) = struct.unpack("<L", data)
+ data = ifd_data[offset - 12 : offset + size - 12]
+ else:
+ data = data[:size]
+
+ if len(data) != size:
+ warnings.warn(
+ "Possibly corrupt EXIF MakerNote data. "
+ f"Expecting to read {size} bytes but only got "
+ f"{len(data)}. Skipping tag {ifd_tag}"
+ )
+ continue
+
+ if not data:
+ continue
+
+ makernote[ifd_tag] = handler(
+ ImageFileDirectory_v2(), data, False
+ )
+ self._ifds[tag] = dict(self._fixup_dict(makernote))
+ elif self.get(0x010F) == "Nintendo":
+ makernote = {}
+ for i in range(0, 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 = {"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
+ )
+
+ self.fp.read(4)
+ camerainfo["Category"] = self.fp.read(2)
+
+ makernote = {0x1101: dict(self._fixup_dict(camerainfo))}
+ self._ifds[tag] = makernote
+ else:
+ # Interop
+ self._ifds[tag] = self._get_ifd_dict(tag_data)
+ ifd = self._ifds.get(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):
+ 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):
+ 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):
+ keys = set(self._data)
+ if self._info is not None:
+ keys.update(self._info)
+ return len(keys)
+
+ def __getitem__(self, tag):
+ 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):
+ return tag in self._data or (self._info is not None and tag in self._info)
+
+ def __setitem__(self, tag, value):
+ if self._info is not None and tag in self._info:
+ del self._info[tag]
+ self._data[tag] = value
+
+ def __delitem__(self, tag):
+ if self._info is not None and tag in self._info:
+ del self._info[tag]
+ else:
+ del self._data[tag]
+
+ def __iter__(self):
+ keys = set(self._data)
+ if self._info is not None:
+ keys.update(self._info)
+ return iter(keys)
diff --git a/contrib/python/Pillow/py3/PIL/ImageChops.py b/contrib/python/Pillow/py3/PIL/ImageChops.py
new file mode 100644
index 00000000000..70120031797
--- /dev/null
+++ b/contrib/python/Pillow/py3/PIL/ImageChops.py
@@ -0,0 +1,303 @@
+#
+# 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 . import Image
+
+
+def constant(image, value):
+ """Fill a channel with a given grey level.
+
+ :rtype: :py:class:`~PIL.Image.Image`
+ """
+
+ return Image.new("L", image.size, value)
+
+
+def duplicate(image):
+ """Copy a channel. Alias for :py:meth:`PIL.Image.Image.copy`.
+
+ :rtype: :py:class:`~PIL.Image.Image`
+ """
+
+ return image.copy()
+
+
+def invert(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, image2):
+ """
+ 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, image2):
+ """
+ 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, image2):
+ """
+ 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, image2):
+ """
+ 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, image2):
+ """
+ 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, image2):
+ """
+ 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, image2):
+ """
+ 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, image2):
+ """
+ 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, image2, scale=1.0, offset=0):
+ """
+ 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, image2, scale=1.0, offset=0):
+ """
+ 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, image2):
+ """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, image2):
+ """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, image2):
+ """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, image2):
+ """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, image2):
+ """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, image2, alpha):
+ """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, image2, mask):
+ """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, xoffset, yoffset=None):
+ """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/contrib/python/Pillow/py3/PIL/ImageCms.py b/contrib/python/Pillow/py3/PIL/ImageCms.py
new file mode 100644
index 00000000000..3a337f9f209
--- /dev/null
+++ b/contrib/python/Pillow/py3/PIL/ImageCms.py
@@ -0,0 +1,1009 @@
+# The Python Imaging Library.
+# $Id$
+
+# Optional color management support, based on Kevin Cazabon's PyCMS
+# library.
+
+# 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.
+
+import sys
+from enum import IntEnum
+
+from . import Image
+
+try:
+ from . import _imagingcms
+except ImportError as ex:
+ # Allow error import for doc purposes, but error out when accessing
+ # anything in core.
+ from ._util import DeferredError
+
+ _imagingcms = DeferredError(ex)
+
+DESCRIPTION = """
+pyCMS
+
+ a Python / PIL interface to the littleCMS ICC Color Management System
+ Copyright (C) 2002-2003 Kevin Cazabon
+ 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"
+
+# --------------------------------------------------------------------.
+
+core = _imagingcms
+
+#
+# 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
+
+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
+}
+
+_MAX_FLAG = 0
+for flag in FLAGS.values():
+ if isinstance(flag, int):
+ _MAX_FLAG = _MAX_FLAG | flag
+
+
+# --------------------------------------------------------------------.
+# Experimental PIL-level API
+# --------------------------------------------------------------------.
+
+##
+# Profile.
+
+
+class ImageCmsProfile:
+ def __init__(self, profile):
+ """
+ :param profile: Either a string representing a filename,
+ a file like object containing a profile or a
+ low-level profile object
+
+ """
+
+ 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._set(core.profile_frombytes(f.read()))
+ return
+ self._set(core.profile_open(profile), profile)
+ elif hasattr(profile, "read"):
+ self._set(core.profile_frombytes(profile.read()))
+ elif isinstance(profile, _imagingcms.CmsProfile):
+ self._set(profile)
+ else:
+ msg = "Invalid type for Profile"
+ raise TypeError(msg)
+
+ def _set(self, profile, filename=None):
+ self.profile = profile
+ self.filename = filename
+ self.product_name = None # profile.product_name
+ self.product_info = None # profile.product_info
+
+ def tobytes(self):
+ """
+ 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,
+ output,
+ input_mode,
+ output_mode,
+ intent=Intent.PERCEPTUAL,
+ proof=None,
+ proof_intent=Intent.ABSOLUTE_COLORIMETRIC,
+ flags=0,
+ ):
+ 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):
+ return self.apply(im)
+
+ def apply(self, im, imOut=None):
+ im.load()
+ if imOut is None:
+ imOut = Image.new(self.output_mode, im.size, None)
+ self.transform.apply(im.im.id, imOut.im.id)
+ imOut.info["icc_profile"] = self.output_profile.tobytes()
+ return imOut
+
+ def apply_in_place(self, im):
+ im.load()
+ if im.mode != self.output_mode:
+ msg = "mode mismatch"
+ raise ValueError(msg) # wrong output mode
+ self.transform.apply(im.im.id, im.im.id)
+ im.info["icc_profile"] = self.output_profile.tobytes()
+ return im
+
+
+def get_display_profile(handle=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
+
+ if isinstance(handle, ImageWin.HDC):
+ profile = core.get_display_profile_win32(handle, 1)
+ else:
+ profile = core.get_display_profile_win32(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,
+ inputProfile,
+ outputProfile,
+ renderingIntent=Intent.PERCEPTUAL,
+ outputMode=None,
+ inPlace=False,
+ flags=0,
+):
+ """
+ (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):
+ """
+ (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,
+ outputProfile,
+ inMode,
+ outMode,
+ renderingIntent=Intent.PERCEPTUAL,
+ flags=0,
+):
+ """
+ (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 = "flags must be an integer between 0 and %s" + _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,
+ outputProfile,
+ proofProfile,
+ inMode,
+ outMode,
+ renderingIntent=Intent.PERCEPTUAL,
+ proofRenderingIntent=Intent.ABSOLUTE_COLORIMETRIC,
+ flags=FLAGS["SOFTPROOFING"],
+):
+ """
+ (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 = "flags must be an integer between 0 and %s" + _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, transform, inPlace=False):
+ """
+ (pyCMS) Applies a transform to a given image.
+
+ If ``im.mode != transform.inMode``, a :exc:`PyCMSError` is raised.
+
+ If ``inPlace`` is ``True`` and ``transform.inMode != transform.outMode``, a
+ :exc:`PyCMSError` is raised.
+
+ If ``im.mode``, ``transform.inMode`` or ``transform.outMode`` 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.inMode`` and ``transform.outMode`` 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.outMode``.
+
+ :param im: An :py:class:`~PIL.Image.Image` object, and im.mode must be the same
+ as the ``inMode`` 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, colorTemp=-1):
+ """
+ (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 integer 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):
+ """
+
+ (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 len(model) > 30:
+ return model + "\n"
+ return f"{model} - {manufacturer}\n"
+
+ except (AttributeError, OSError, TypeError, ValueError) as v:
+ raise PyCMSError(v) from v
+
+
+def getProfileInfo(profile):
+ """
+ (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
+ arr = []
+ for elt in (description, cpright):
+ if elt:
+ arr.append(elt)
+ return "\r\n\r\n".join(arr) + "\r\n\r\n"
+
+ except (AttributeError, OSError, TypeError, ValueError) as v:
+ raise PyCMSError(v) from v
+
+
+def getProfileCopyright(profile):
+ """
+ (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):
+ """
+ (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):
+ """
+ (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):
+ """
+ (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):
+ """
+ (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, intent, direction):
+ """
+ (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():
+ """
+ (pyCMS) Fetches versions.
+ """
+
+ return VERSION, core.littlecms_version, sys.version.split()[0], Image.__version__
diff --git a/contrib/python/Pillow/py3/PIL/ImageColor.py b/contrib/python/Pillow/py3/PIL/ImageColor.py
new file mode 100644
index 00000000000..befc1fd1d88
--- /dev/null
+++ b/contrib/python/Pillow/py3/PIL/ImageColor.py
@@ -0,0 +1,313 @@
+#
+# 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.
+#
+
+import re
+
+from . import Image
+
+
+def getrgb(color):
+ """
+ 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
+ colormap[color] = rgb = getrgb(rgb)
+ return rgb
+
+ # 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 = hls_to_rgb(
+ float(m.group(1)) / 360.0,
+ float(m.group(3)) / 100.0,
+ float(m.group(2)) / 100.0,
+ )
+ return (
+ int(rgb[0] * 255 + 0.5),
+ int(rgb[1] * 255 + 0.5),
+ int(rgb[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 = hsv_to_rgb(
+ float(m.group(1)) / 360.0,
+ float(m.group(2)) / 100.0,
+ float(m.group(3)) / 100.0,
+ )
+ return (
+ int(rgb[0] * 255 + 0.5),
+ int(rgb[1] * 255 + 0.5),
+ int(rgb[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)
+
+
+def getcolor(color, mode):
+ """
+ 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 greyscale 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[, alpha]) or (red, green, blue[, alpha])``
+ """
+ # same as getrgb, but converts the result to the given mode
+ color, alpha = getrgb(color), 255
+ if len(color) == 4:
+ color, alpha = color[:3], color[3]
+
+ if mode == "HSV":
+ from colorsys import rgb_to_hsv
+
+ r, g, b = color
+ 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 = color
+ # ITU-R Recommendation 601-2 for nonlinear RGB
+ # scaled to 24 bits to match the convert's implementation.
+ color = (r * 19595 + g * 38470 + b * 7471 + 0x8000) >> 16
+ if mode[-1] == "A":
+ return color, alpha
+ else:
+ if mode[-1] == "A":
+ return color + (alpha,)
+ return color
+
+
+colormap = {
+ # 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/contrib/python/Pillow/py3/PIL/ImageDraw.py b/contrib/python/Pillow/py3/PIL/ImageDraw.py
new file mode 100644
index 00000000000..fbf320d72a9
--- /dev/null
+++ b/contrib/python/Pillow/py3/PIL/ImageDraw.py
@@ -0,0 +1,1062 @@
+#
+# 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.
+#
+
+import math
+import numbers
+
+from . import Image, ImageColor
+
+"""
+A simple 2D drawing interface for PIL images.
+<p>
+Application code should use the <b>Draw</b> factory, instead of
+directly.
+"""
+
+
+class ImageDraw:
+ font = None
+
+ def __init__(self, im, mode=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):
+ """
+ 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):
+ if font_size is not None:
+ from . import ImageFont
+
+ font = ImageFont.load_default(font_size)
+ else:
+ font = self.getfont()
+ return font
+
+ def _getink(self, ink, fill=None):
+ if ink is None and fill is None:
+ if self.fill:
+ fill = self.ink
+ else:
+ ink = self.ink
+ else:
+ if ink is not None:
+ if isinstance(ink, str):
+ ink = ImageColor.getcolor(ink, self.mode)
+ if self.palette and not isinstance(ink, numbers.Number):
+ ink = self.palette.getcolor(ink, self._image)
+ 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 not isinstance(fill, numbers.Number):
+ fill = self.palette.getcolor(fill, self._image)
+ fill = self.draw.draw_ink(fill)
+ return ink, fill
+
+ def arc(self, xy, start, end, fill=None, width=1):
+ """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, bitmap, fill=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, start, end, fill=None, outline=None, width=1):
+ """Draw a chord."""
+ ink, fill = self._getink(outline, fill)
+ if fill is not None:
+ self.draw.draw_chord(xy, start, end, fill, 1)
+ if ink is not None and ink != fill and width != 0:
+ self.draw.draw_chord(xy, start, end, ink, 0, width)
+
+ def ellipse(self, xy, fill=None, outline=None, width=1):
+ """Draw an ellipse."""
+ ink, fill = self._getink(outline, fill)
+ if fill is not None:
+ self.draw.draw_ellipse(xy, fill, 1)
+ if ink is not None and ink != fill and width != 0:
+ self.draw.draw_ellipse(xy, ink, 0, width)
+
+ def line(self, xy, fill=None, width=0, joint=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:
+ if not isinstance(xy[0], (list, tuple)):
+ xy = [tuple(xy[i : i + 2]) for i in range(0, len(xy), 2)]
+ for i in range(1, len(xy) - 1):
+ point = xy[i]
+ angles = [
+ math.degrees(math.atan2(end[0] - start[0], start[1] - end[1]))
+ % 360
+ for start, end in ((xy[i - 1], point), (point, xy[i + 1]))
+ ]
+ if angles[0] == angles[1]:
+ # This is a straight line, so no joint is required
+ continue
+
+ def coord_at_angle(coord, angle):
+ 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, fill=None, outline=None):
+ """(Experimental) Draw a shape."""
+ shape.close()
+ ink, fill = self._getink(outline, fill)
+ if fill is not None:
+ self.draw.draw_outline(shape, fill, 1)
+ if ink is not None and ink != fill:
+ self.draw.draw_outline(shape, ink, 0)
+
+ def pieslice(self, xy, start, end, fill=None, outline=None, width=1):
+ """Draw a pieslice."""
+ ink, fill = self._getink(outline, fill)
+ if fill is not None:
+ self.draw.draw_pieslice(xy, start, end, fill, 1)
+ if ink is not None and ink != fill and width != 0:
+ self.draw.draw_pieslice(xy, start, end, ink, 0, width)
+
+ def point(self, xy, fill=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, fill=None, outline=None, width=1):
+ """Draw a polygon."""
+ ink, fill = self._getink(outline, fill)
+ if fill is not None:
+ self.draw.draw_polygon(xy, fill, 1)
+ if ink is not None and ink != fill and width != 0:
+ if width == 1:
+ self.draw.draw_polygon(xy, ink, 0, width)
+ else:
+ # 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]
+
+ fill_im = mask.copy()
+ draw = Draw(fill_im)
+ draw.draw.draw_polygon(xy, mask_ink, 1)
+
+ ink_im = mask.copy()
+ draw = Draw(ink_im)
+ width = width * 2 - 1
+ draw.draw.draw_polygon(xy, mask_ink, 0, width)
+
+ mask.paste(ink_im, mask=fill_im)
+
+ im = Image.new(self.mode, self.im.size)
+ draw = Draw(im)
+ draw.draw.draw_polygon(xy, ink, 0, width)
+ self.im.paste(im.im, (0, 0) + im.size, mask.im)
+
+ def regular_polygon(
+ self, bounding_circle, n_sides, rotation=0, fill=None, outline=None, width=1
+ ):
+ """Draw a regular polygon."""
+ xy = _compute_regular_polygon_vertices(bounding_circle, n_sides, rotation)
+ self.polygon(xy, fill, outline, width)
+
+ def rectangle(self, xy, fill=None, outline=None, width=1):
+ """Draw a rectangle."""
+ ink, fill = self._getink(outline, fill)
+ if fill is not None:
+ self.draw.draw_rectangle(xy, fill, 1)
+ if ink is not None and ink != fill and width != 0:
+ self.draw.draw_rectangle(xy, ink, 0, width)
+
+ def rounded_rectangle(
+ self, xy, radius=0, fill=None, outline=None, width=1, *, corners=None
+ ):
+ """Draw a rounded rectangle."""
+ if isinstance(xy[0], (list, tuple)):
+ (x0, y0), (x1, y1) = xy
+ else:
+ x0, y0, x1, y1 = 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
+
+ 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 = d // 2
+ ink, fill = self._getink(outline, fill)
+
+ def draw_corners(pieslice):
+ 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 = []
+ 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]:
+ parts.append(part)
+ for part in parts:
+ if pieslice:
+ self.draw.draw_pieslice(*(part + (fill, 1)))
+ else:
+ self.draw.draw_arc(*(part + (ink, width)))
+
+ if fill is not None:
+ draw_corners(True)
+
+ if full_x:
+ self.draw.draw_rectangle((x0, y0 + r + 1, x1, y1 - r - 1), fill, 1)
+ else:
+ self.draw.draw_rectangle((x0 + r + 1, y0, x1 - r - 1, y1), fill, 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, 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, 1)
+ if ink is not None and ink != fill 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):
+ split_character = "\n" if isinstance(text, str) else b"\n"
+
+ return split_character in text
+
+ def _multiline_split(self, text):
+ split_character = "\n" if isinstance(text, str) else b"\n"
+
+ return text.split(split_character)
+
+ def _multiline_spacing(self, font, spacing, stroke_width):
+ return (
+ self.textbbox((0, 0), "A", font, stroke_width=stroke_width)[3]
+ + stroke_width
+ + spacing
+ )
+
+ def text(
+ self,
+ xy,
+ text,
+ fill=None,
+ font=None,
+ anchor=None,
+ spacing=4,
+ align="left",
+ direction=None,
+ features=None,
+ language=None,
+ stroke_width=0,
+ stroke_fill=None,
+ embedded_color=False,
+ *args,
+ **kwargs,
+ ):
+ """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, fill = self._getink(fill)
+ if ink is None:
+ return fill
+ return ink
+
+ def draw_text(ink, stroke_width=0, stroke_offset=None):
+ mode = self.fontmode
+ if stroke_width == 0 and embedded_color:
+ mode = "RGBA"
+ coord = []
+ start = []
+ for i in range(2):
+ coord.append(int(xy[i]))
+ start.append(math.modf(xy[i])[0])
+ try:
+ mask, offset = font.getmask2(
+ text,
+ mode,
+ direction=direction,
+ features=features,
+ language=language,
+ stroke_width=stroke_width,
+ anchor=anchor,
+ ink=ink,
+ start=start,
+ *args,
+ **kwargs,
+ )
+ coord = coord[0] + offset[0], coord[1] + offset[1]
+ except AttributeError:
+ try:
+ mask = font.getmask(
+ text,
+ mode,
+ direction,
+ features,
+ language,
+ stroke_width,
+ anchor,
+ ink,
+ start=start,
+ *args,
+ **kwargs,
+ )
+ except TypeError:
+ mask = font.getmask(text)
+ if stroke_offset:
+ coord = coord[0] + stroke_offset[0], coord[1] + stroke_offset[1]
+ 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)
+ color.fillband(3, (ink >> 24) & 0xFF)
+ x, y = coord
+ 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
+ draw_text(ink, 0)
+ else:
+ # Only draw normal text
+ draw_text(ink)
+
+ def multiline_text(
+ self,
+ xy,
+ text,
+ fill=None,
+ font=None,
+ anchor=None,
+ spacing=4,
+ align="left",
+ direction=None,
+ features=None,
+ language=None,
+ stroke_width=0,
+ stroke_fill=None,
+ embedded_color=False,
+ *,
+ font_size=None,
+ ):
+ if direction == "ttb":
+ msg = "ttb direction is unsupported for multiline text"
+ raise ValueError(msg)
+
+ if anchor is None:
+ anchor = "la"
+ elif len(anchor) != 2:
+ msg = "anchor must be a 2 character string"
+ raise ValueError(msg)
+ elif anchor[1] in "tb":
+ msg = "anchor not supported for multiline text"
+ raise ValueError(msg)
+
+ if font is None:
+ font = self._getfont(font_size)
+
+ widths = []
+ max_width = 0
+ lines = self._multiline_split(text)
+ line_spacing = self._multiline_spacing(font, spacing, stroke_width)
+ for line in lines:
+ line_width = self.textlength(
+ line, font, direction=direction, features=features, language=language
+ )
+ widths.append(line_width)
+ max_width = max(max_width, line_width)
+
+ top = xy[1]
+ 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]
+
+ # first align left by anchor
+ if anchor[0] == "m":
+ left -= width_difference / 2.0
+ elif anchor[0] == "r":
+ left -= width_difference
+
+ # then align by align parameter
+ if align == "left":
+ pass
+ elif align == "center":
+ left += width_difference / 2.0
+ elif align == "right":
+ left += width_difference
+ else:
+ msg = 'align must be "left", "center" or "right"'
+ raise ValueError(msg)
+
+ self.text(
+ (left, top),
+ line,
+ fill,
+ font,
+ anchor,
+ direction=direction,
+ features=features,
+ language=language,
+ stroke_width=stroke_width,
+ stroke_fill=stroke_fill,
+ embedded_color=embedded_color,
+ )
+ top += line_spacing
+
+ def textlength(
+ self,
+ text,
+ font=None,
+ direction=None,
+ features=None,
+ language=None,
+ embedded_color=False,
+ *,
+ font_size=None,
+ ):
+ """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,
+ text,
+ font=None,
+ anchor=None,
+ spacing=4,
+ align="left",
+ direction=None,
+ features=None,
+ language=None,
+ stroke_width=0,
+ embedded_color=False,
+ *,
+ font_size=None,
+ ):
+ """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,
+ text,
+ font=None,
+ anchor=None,
+ spacing=4,
+ align="left",
+ direction=None,
+ features=None,
+ language=None,
+ stroke_width=0,
+ embedded_color=False,
+ *,
+ font_size=None,
+ ):
+ if direction == "ttb":
+ msg = "ttb direction is unsupported for multiline text"
+ raise ValueError(msg)
+
+ if anchor is None:
+ anchor = "la"
+ elif len(anchor) != 2:
+ msg = "anchor must be a 2 character string"
+ raise ValueError(msg)
+ elif anchor[1] in "tb":
+ msg = "anchor not supported for multiline text"
+ raise ValueError(msg)
+
+ if font is None:
+ font = self._getfont(font_size)
+
+ widths = []
+ max_width = 0
+ lines = self._multiline_split(text)
+ line_spacing = self._multiline_spacing(font, spacing, stroke_width)
+ 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)
+
+ top = xy[1]
+ if anchor[1] == "m":
+ top -= (len(lines) - 1) * line_spacing / 2.0
+ elif anchor[1] == "d":
+ top -= (len(lines) - 1) * line_spacing
+
+ bbox = None
+
+ for idx, line in enumerate(lines):
+ left = xy[0]
+ width_difference = max_width - widths[idx]
+
+ # first align left by anchor
+ if anchor[0] == "m":
+ left -= width_difference / 2.0
+ elif anchor[0] == "r":
+ left -= width_difference
+
+ # then align by align parameter
+ if align == "left":
+ pass
+ elif align == "center":
+ left += width_difference / 2.0
+ elif align == "right":
+ left += width_difference
+ else:
+ msg = 'align must be "left", "center" or "right"'
+ raise ValueError(msg)
+
+ bbox_line = self.textbbox(
+ (left, top),
+ 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]),
+ )
+
+ top += line_spacing
+
+ if bbox is None:
+ return xy[0], xy[1], xy[0], xy[1]
+ return bbox
+
+
+def Draw(im, mode=None):
+ """
+ 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 im.getdraw(mode)
+ except AttributeError:
+ return ImageDraw(im, mode)
+
+
+# experimental access to the outline API
+try:
+ Outline = Image.core.outline
+except AttributeError:
+ Outline = None
+
+
+def getdraw(im=None, hints=None):
+ """
+ (Experimental) A more advanced 2D drawing interface for PIL images,
+ based on the WCK interface.
+
+ :param im: The image to draw in.
+ :param hints: An optional list of hints.
+ :returns: A (drawing context, drawing resource factory) tuple.
+ """
+ # FIXME: this needs more work!
+ # FIXME: come up with a better 'hints' scheme.
+ handler = None
+ if not hints or "nicest" in hints:
+ try:
+ from . import _imagingagg as handler
+ except ImportError:
+ pass
+ if handler is None:
+ from . import ImageDraw2 as handler
+ if im:
+ im = handler.Draw(im)
+ return im, handler
+
+
+def floodfill(image, xy, value, border=None, thresh=0):
+ """
+ (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()
+ 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 != value and p != 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, n_sides, rotation):
+ """
+ Generate a list of vertices for a 2D regular polygon.
+
+ :param bounding_circle: The bounding circle is a tuple 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"
+ 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 tuple"
+ raise TypeError(msg)
+
+ if len(bounding_circle) == 3:
+ *centroid, polygon_radius = bounding_circle
+ elif len(bounding_circle) == 2:
+ centroid, polygon_radius = bounding_circle
+ else:
+ msg = (
+ "bounding_circle should contain 2D coordinates "
+ "and a radius (e.g. (x, y, r) or ((x, y), r) )"
+ )
+ raise ValueError(msg)
+
+ if not all(isinstance(i, (int, float)) for i in (*centroid, polygon_radius)):
+ msg = "bounding_circle should only contain numeric data"
+ raise ValueError(msg)
+
+ if not len(centroid) == 2:
+ msg = "bounding_circle centre should contain 2D coordinates (e.g. (x, y))"
+ 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"
+ raise ValueError(msg)
+
+ # 2. Define Helper Functions
+ def _apply_rotation(point, degrees, centroid):
+ 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(centroid, polygon_radius, angle):
+ start_point = [polygon_radius, 0]
+ return _apply_rotation(start_point, angle, centroid)
+
+ def _get_angles(n_sides, rotation):
+ angles = []
+ degrees = 360 / n_sides
+ # Start with the bottom left polygon vertex
+ current_angle = (270 - 0.5 * degrees) + rotation
+ for _ in range(0, 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(centroid, polygon_radius, angle) for angle in angles
+ ]
+
+
+def _color_diff(color1, color2):
+ """
+ Uses 1-norm distance to calculate difference between two values.
+ """
+ if isinstance(color2, tuple):
+ return sum(abs(color1[i] - color2[i]) for i in range(0, len(color2)))
+ else:
+ return abs(color1 - color2)
diff --git a/contrib/python/Pillow/py3/PIL/ImageDraw2.py b/contrib/python/Pillow/py3/PIL/ImageDraw2.py
new file mode 100644
index 00000000000..7ce0224a67c
--- /dev/null
+++ b/contrib/python/Pillow/py3/PIL/ImageDraw2.py
@@ -0,0 +1,193 @@
+#
+# 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 . import Image, ImageColor, ImageDraw, ImageFont, ImagePath
+
+
+class Pen:
+ """Stores an outline color and width."""
+
+ def __init__(self, color, width=1, opacity=255):
+ self.color = ImageColor.getrgb(color)
+ self.width = width
+
+
+class Brush:
+ """Stores a fill color"""
+
+ def __init__(self, color, opacity=255):
+ self.color = ImageColor.getrgb(color)
+
+
+class Font:
+ """Stores a TrueType font and color"""
+
+ def __init__(self, color, file, size=12):
+ # 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, size=None, color=None):
+ if not hasattr(image, "im"):
+ image = Image.new(image, size, color)
+ self.draw = ImageDraw.Draw(image)
+ self.image = image
+ self.transform = None
+
+ def flush(self):
+ return self.image
+
+ def render(self, op, xy, pen, brush=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:
+ xy = ImagePath.Path(xy)
+ xy.transform(self.transform)
+ # render the item
+ if op == "line":
+ self.draw.line(xy, fill=outline, width=width)
+ else:
+ getattr(self.draw, op)(xy, fill=fill, outline=outline)
+
+ def settransform(self, offset):
+ """Sets a transformation offset."""
+ (xoffset, yoffset) = offset
+ self.transform = (1, 0, xoffset, 0, 1, yoffset)
+
+ def arc(self, xy, start, end, *options):
+ """
+ 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, start, end, *options)
+
+ def chord(self, xy, start, end, *options):
+ """
+ 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, start, end, *options)
+
+ def ellipse(self, xy, *options):
+ """
+ Draws an ellipse inside the given bounding box.
+
+ .. seealso:: :py:meth:`PIL.ImageDraw.ImageDraw.ellipse`
+ """
+ self.render("ellipse", xy, *options)
+
+ def line(self, xy, *options):
+ """
+ Draws a line between the coordinates in the ``xy`` list.
+
+ .. seealso:: :py:meth:`PIL.ImageDraw.ImageDraw.line`
+ """
+ self.render("line", xy, *options)
+
+ def pieslice(self, xy, start, end, *options):
+ """
+ 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, start, end, *options)
+
+ def polygon(self, xy, *options):
+ """
+ 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, *options)
+
+ def rectangle(self, xy, *options):
+ """
+ Draws a rectangle.
+
+ .. seealso:: :py:meth:`PIL.ImageDraw.ImageDraw.rectangle`
+ """
+ self.render("rectangle", xy, *options)
+
+ def text(self, xy, text, font):
+ """
+ Draws the string at the given position.
+
+ .. seealso:: :py:meth:`PIL.ImageDraw.ImageDraw.text`
+ """
+ if self.transform:
+ xy = ImagePath.Path(xy)
+ xy.transform(self.transform)
+ self.draw.text(xy, text, font=font.font, fill=font.color)
+
+ def textbbox(self, xy, text, font):
+ """
+ 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:
+ xy = ImagePath.Path(xy)
+ xy.transform(self.transform)
+ return self.draw.textbbox(xy, text, font=font.font)
+
+ def textlength(self, text, font):
+ """
+ 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/contrib/python/Pillow/py3/PIL/ImageEnhance.py b/contrib/python/Pillow/py3/PIL/ImageEnhance.py
new file mode 100644
index 00000000000..3b79d5c46a1
--- /dev/null
+++ b/contrib/python/Pillow/py3/PIL/ImageEnhance.py
@@ -0,0 +1,103 @@
+#
+# 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 . import Image, ImageFilter, ImageStat
+
+
+class _Enhance:
+ def enhance(self, factor):
+ """
+ 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):
+ self.image = image
+ self.intermediate_mode = "L"
+ if "A" in image.getbands():
+ self.intermediate_mode = "LA"
+
+ self.degenerate = image.convert(self.intermediate_mode).convert(image.mode)
+
+
+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 grey image. A factor of 1.0 gives the original image.
+ """
+
+ def __init__(self, image):
+ self.image = image
+ mean = int(ImageStat.Stat(image.convert("L")).mean[0] + 0.5)
+ self.degenerate = Image.new("L", image.size, mean).convert(image.mode)
+
+ if "A" in image.getbands():
+ self.degenerate.putalpha(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):
+ 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):
+ self.image = image
+ self.degenerate = image.filter(ImageFilter.SMOOTH)
+
+ if "A" in image.getbands():
+ self.degenerate.putalpha(image.getchannel("A"))
diff --git a/contrib/python/Pillow/py3/PIL/ImageFile.py b/contrib/python/Pillow/py3/PIL/ImageFile.py
new file mode 100644
index 00000000000..8e4f7dfb2c8
--- /dev/null
+++ b/contrib/python/Pillow/py3/PIL/ImageFile.py
@@ -0,0 +1,773 @@
+#
+# 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.
+#
+
+import io
+import itertools
+import struct
+import sys
+
+from . import Image
+from ._util import is_path
+
+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 raise_oserror(error):
+ try:
+ msg = Image.core.getcodecstatus(error)
+ except AttributeError:
+ msg = ERRORS.get(error)
+ if not msg:
+ msg = f"decoder error {error}"
+ msg += " when reading image file"
+ raise OSError(msg)
+
+
+def _tilesort(t):
+ # sort on offset
+ return t[2]
+
+
+#
+# --------------------------------------------------------------------
+# ImageFile base class
+
+
+class ImageFile(Image.Image):
+ """Base class for image file format handlers."""
+
+ def __init__(self, fp=None, filename=None):
+ super().__init__()
+
+ self._min_frame = 0
+
+ self.custom_mimetype = None
+
+ self.tile = None
+ """ A list of tile descriptors, or ``None`` """
+
+ self.readonly = 1 # until we know better
+
+ self.decoderconfig = ()
+ self.decodermaxblock = MAXBLOCK
+
+ if is_path(fp):
+ # filename
+ self.fp = open(fp, "rb")
+ self.filename = fp
+ self._exclusive_fp = True
+ else:
+ # stream
+ self.fp = fp
+ self.filename = filename
+ # can be overridden
+ self._exclusive_fp = None
+
+ 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 get_format_mimetype(self):
+ if self.custom_mimetype:
+ return self.custom_mimetype
+ if self.format is not None:
+ return Image.MIME.get(self.format.upper())
+
+ def __setstate__(self, state):
+ self.tile = []
+ super().__setstate__(state)
+
+ def verify(self):
+ """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):
+ """Load image data based on tile list"""
+
+ if self.tile is None:
+ msg = "cannot load this image"
+ raise OSError(msg)
+
+ pixel = Image.Image.load(self)
+ if not self.tile:
+ return pixel
+
+ self.map = None
+ use_mmap = self.filename and len(self.tile) == 1
+ # As of pypy 2.1.0, memory mapping was failing here.
+ use_mmap = use_mmap and not hasattr(sys, "pypy_version_info")
+
+ readonly = 0
+
+ # look for read/seek overrides
+ try:
+ read = self.load_read
+ # don't use mmap if there are custom read/seek functions
+ use_mmap = False
+ except AttributeError:
+ read = self.fp.read
+
+ try:
+ seek = self.load_seek
+ use_mmap = False
+ except AttributeError:
+ seek = self.fp.seek
+
+ if use_mmap:
+ # try memory mapping
+ decoder_name, extents, offset, args = self.tile[0]
+ if (
+ decoder_name == "raw"
+ 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():
+ # buffer is not large enough
+ raise OSError
+ 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)
+
+ try:
+ # FIXME: This is a hack to handle TIFF's JpegTables tag.
+ prefix = self.tile_prefix
+ except AttributeError:
+ 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 decoder_name, extents, offset, args in 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:
+ try:
+ s = read(self.decodermaxblock)
+ 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_oserror(err_code)
+
+ return Image.Image.load(self)
+
+ def load_prepare(self):
+ # create image memory if necessary
+ if not self.im or self.im.mode != self.mode or self.im.size != self.size:
+ self.im = Image.core.new(self.mode, self.size)
+ # create palette (optional)
+ if self.mode == "P":
+ Image.Image.load(self)
+
+ def load_end(self):
+ # may be overridden
+ pass
+
+ # may be defined for contained formats
+ # def load_seek(self, pos):
+ # pass
+
+ # may be defined for blocked formats (e.g. PNG)
+ # def load_read(self, bytes):
+ # pass
+
+ def _seek_check(self, frame):
+ 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 >= self.n_frames + self._min_frame
+ )
+ ):
+ msg = "attempt to seek outside sequence"
+ raise EOFError(msg)
+
+ return self.tell() != frame
+
+
+class StubImageFile(ImageFile):
+ """
+ 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.
+ """
+
+ def _open(self):
+ msg = "StubImageFile subclass must implement _open"
+ raise NotImplementedError(msg)
+
+ def load(self):
+ 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__
+ self.__dict__ = image.__dict__
+ return image.load()
+
+ def _load(self):
+ """(Hook) Find actual image loader."""
+ msg = "StubImageFile subclass must implement _load"
+ raise NotImplementedError(msg)
+
+
+class Parser:
+ """
+ Incremental image parser. This class implements the standard
+ feed/close consumer interface.
+ """
+
+ incremental = None
+ image = None
+ data = None
+ decoder = None
+ offset = 0
+ finished = 0
+
+ def reset(self):
+ """
+ (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):
+ """
+ (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_oserror(e)
+ 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:
+ # traceback.print_exc()
+ 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):
+ return self
+
+ def __exit__(self, *args):
+ self.close()
+
+ def close(self):
+ """
+ (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, fp, tile, bufsize=0):
+ """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, fp, tile, bufsize, fh, exc=None):
+ for e, b, o, a in tile:
+ if o > 0:
+ fp.seek(o)
+ encoder = Image._getencoder(im.mode, e, a, im.encoderconfig)
+ try:
+ encoder.setimage(im.im, b)
+ 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
+ errcode = encoder.encode_to_file(fh, bufsize)
+ if errcode < 0:
+ msg = f"encoder error {errcode} when writing image file"
+ raise OSError(msg) from exc
+ finally:
+ encoder.cleanup()
+
+
+def _safe_read(fp, size):
+ """
+ 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 <b>read</b> method.
+ :param size: Number of bytes to read.
+ :returns: A string containing <i>size</i> 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
+ data = []
+ remaining_size = size
+ while remaining_size > 0:
+ block = fp.read(min(remaining_size, SAFEBLOCK))
+ if not block:
+ break
+ data.append(block)
+ remaining_size -= len(block)
+ if sum(len(d) for d in data) < size:
+ msg = "Truncated File Read"
+ raise OSError(msg)
+ return b"".join(data)
+
+
+class PyCodecState:
+ def __init__(self):
+ self.xsize = 0
+ self.ysize = 0
+ self.xoff = 0
+ self.yoff = 0
+
+ def extents(self):
+ return self.xoff, self.yoff, self.xoff + self.xsize, self.yoff + self.ysize
+
+
+class PyCodec:
+ def __init__(self, mode, *args):
+ self.im = None
+ self.state = PyCodecState()
+ self.fd = None
+ self.mode = mode
+ self.init(args)
+
+ def init(self, args):
+ """
+ Override to perform codec specific initialization
+
+ :param args: Array of args items from the tile entry
+ :returns: None
+ """
+ self.args = args
+
+ def cleanup(self):
+ """
+ Override to perform codec specific cleanup
+
+ :returns: None
+ """
+ pass
+
+ def setfd(self, fd):
+ """
+ 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, extents=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<file-codecs-py>`
+ """
+
+ _pulls_fd = False
+
+ @property
+ def pulls_fd(self):
+ return self._pulls_fd
+
+ def decode(self, buffer):
+ """
+ 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`.
+ """
+ raise NotImplementedError()
+
+ def set_as_raw(self, data, rawmode=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
+ :returns: None
+ """
+
+ if not rawmode:
+ rawmode = self.mode
+ d = Image._getdecoder(self.mode, "raw", rawmode)
+ 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<file-codecs-py>`
+ """
+
+ _pushes_fd = False
+
+ @property
+ def pushes_fd(self):
+ return self._pushes_fd
+
+ def encode(self, bufsize):
+ """
+ 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`.
+ """
+ raise NotImplementedError()
+
+ def encode_to_pyfd(self):
+ """
+ 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:
+ self.fd.write(data)
+ return bytes_consumed, errcode
+
+ def encode_to_file(self, fh, bufsize):
+ """
+ :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:
+ fh.write(buf[status:])
+ return errcode
diff --git a/contrib/python/Pillow/py3/PIL/ImageFilter.py b/contrib/python/Pillow/py3/PIL/ImageFilter.py
new file mode 100644
index 00000000000..57268b8f53d
--- /dev/null
+++ b/contrib/python/Pillow/py3/PIL/ImageFilter.py
@@ -0,0 +1,566 @@
+#
+# 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.
+#
+import functools
+
+
+class Filter:
+ pass
+
+
+class MultibandFilter(Filter):
+ pass
+
+
+class BuiltinFilter(MultibandFilter):
+ def filter(self, image):
+ if image.mode == "P":
+ msg = "cannot filter palette images"
+ raise ValueError(msg)
+ return image.filter(*self.filterargs)
+
+
+class Kernel(BuiltinFilter):
+ """
+ Create a convolution kernel. The current version only
+ supports 3x3 and 5x5 integer and floating point kernels.
+
+ In the current version, kernels can only be applied to
+ "L" and "RGB" images.
+
+ :param size: Kernel size, given as (width, height). In the current
+ version, 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, kernel, scale=None, offset=0):
+ 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, rank):
+ self.size = size
+ self.rank = rank
+
+ def filter(self, image):
+ 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=3):
+ 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=3):
+ 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=3):
+ 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=3):
+ self.size = size
+
+ def filter(self, image):
+ 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
+ <https://www.mia.uni-saarland.de/Publications/gwosdek-ssvm11.pdf>
+
+ :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=2):
+ self.radius = radius
+
+ def filter(self, image):
+ xy = self.radius
+ if not isinstance(xy, (tuple, list)):
+ 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):
+ xy = radius
+ if not isinstance(xy, (tuple, list)):
+ xy = (xy, xy)
+ if xy[0] < 0 or xy[1] < 0:
+ msg = "radius must be >= 0"
+ raise ValueError(msg)
+ self.radius = radius
+
+ def filter(self, image):
+ xy = self.radius
+ if not isinstance(xy, (tuple, list)):
+ 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
+
+ """ # noqa: E501
+
+ name = "UnsharpMask"
+
+ def __init__(self, radius=2, percent=150, threshold=3):
+ self.radius = radius
+ self.percent = percent
+ self.threshold = threshold
+
+ def filter(self, image):
+ 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, table, channels=3, target_mode=None, **kwargs):
+ 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 = None
+ if hasattr(table, "shape"):
+ try:
+ import numpy
+ except ImportError: # pragma: no cover
+ pass
+
+ if numpy and isinstance(table, numpy.ndarray):
+ if copy_table:
+ table = table.copy()
+
+ if table.shape in [
+ (items * channels,),
+ (items, channels),
+ (size[2], size[1], size[0], channels),
+ ]:
+ table = 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)):
+ table, raw_table = [], table
+ 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)
+ table.extend(pixel)
+
+ 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):
+ 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 = [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, callback, channels=3, target_mode=None):
+ """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 = [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, with_normals=False, channels=None, target_mode=None):
+ """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 = [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):
+ 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):
+ from . import Image
+
+ return image.color_lut_3d(
+ self.mode or image.mode,
+ Image.Resampling.BILINEAR,
+ self.channels,
+ self.size[0],
+ self.size[1],
+ self.size[2],
+ self.table,
+ )
diff --git a/contrib/python/Pillow/py3/PIL/ImageFont.py b/contrib/python/Pillow/py3/PIL/ImageFont.py
new file mode 100644
index 00000000000..c2956213519
--- /dev/null
+++ b/contrib/python/Pillow/py3/PIL/ImageFont.py
@@ -0,0 +1,1242 @@
+#
+# 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.
+#
+
+import base64
+import os
+import sys
+import warnings
+from enum import IntEnum
+from io import BytesIO
+
+from . import Image
+from ._util import is_directory, is_path
+
+
+class Layout(IntEnum):
+ BASIC = 0
+ RAQM = 1
+
+
+MAX_STRING_LENGTH = 1_000_000
+
+
+try:
+ from . import _imagingft as core
+except ImportError as ex:
+ from ._util import DeferredError
+
+ core = DeferredError(ex)
+
+
+def _string_length_check(text):
+ 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"""
+
+ def _load_pilfont(self, filename):
+ with open(filename, "rb") as fp:
+ image = None
+ for ext in (".png", ".gif", ".pbm"):
+ if image:
+ image.close()
+ try:
+ fullname = os.path.splitext(filename)[0] + ext
+ image = Image.open(fullname)
+ except Exception:
+ pass
+ else:
+ if image and image.mode in ("1", "L"):
+ break
+ else:
+ if image:
+ image.close()
+ msg = "cannot find glyph data file"
+ raise OSError(msg)
+
+ self.file = fullname
+
+ self._load_pilfont_data(fp, image)
+ image.close()
+
+ def _load_pilfont_data(self, file, image):
+ # 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, mode="", *args, **kwargs):
+ """
+ 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.
+ """
+ return self.font.getmask(text, mode)
+
+ def getbbox(self, text, *args, **kwargs):
+ """
+ Returns bounding box (in pixels) of given text.
+
+ .. versionadded:: 9.2.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.
+
+ :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, *args, **kwargs):
+ """
+ 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
+# <b>truetype</b> factory function to create font objects.
+
+
+class FreeTypeFont:
+ """FreeType font wrapper (requires _imagingft service)"""
+
+ def __init__(self, font=None, size=10, index=0, encoding="", layout_engine=None):
+ # FIXME: use service provider instead
+
+ self.path = font
+ self.size = size
+ self.index = index
+ self.encoding = encoding
+
+ 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):
+ self.font_bytes = f.read()
+ self.font = core.getfont(
+ "", size, index, encoding, self.font_bytes, layout_engine
+ )
+
+ if is_path(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(font)
+
+ def __getstate__(self):
+ return [self.path, self.size, self.index, self.encoding, self.layout_engine]
+
+ def __setstate__(self, state):
+ path, size, index, encoding, layout_engine = state
+ self.__init__(path, size, index, encoding, layout_engine)
+
+ def getname(self):
+ """
+ :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):
+ """
+ :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, mode="", direction=None, features=None, language=None):
+ """
+ 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
+ <https://www.w3.org/International/articles/language-tags/>`_
+ 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,
+ mode="",
+ direction=None,
+ features=None,
+ language=None,
+ stroke_width=0,
+ anchor=None,
+ ):
+ """
+ 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
+ <https://www.w3.org/International/articles/language-tags/>`_
+ 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.
+ See :ref:`text-anchors` for valid values.
+
+ :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,
+ mode="",
+ direction=None,
+ features=None,
+ language=None,
+ stroke_width=0,
+ anchor=None,
+ ink=0,
+ start=None,
+ ):
+ """
+ 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
+ <https://www.w3.org/International/articles/language-tags/>`_
+ 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.
+ See :ref:`text-anchors` for valid values.
+
+ .. 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,
+ mode="",
+ direction=None,
+ features=None,
+ language=None,
+ stroke_width=0,
+ anchor=None,
+ ink=0,
+ start=None,
+ *args,
+ **kwargs,
+ ):
+ """
+ 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
+ <https://www.w3.org/International/articles/language-tags/>`_
+ 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.
+ See :ref:`text-anchors` for valid values.
+
+ .. 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)
+ im = None
+ size = None
+
+ def fill(mode, im_size):
+ nonlocal im, size
+
+ size = im_size
+ if Image.MAX_IMAGE_PIXELS is not None:
+ pixels = max(1, size[0]) * max(1, size[1])
+ if pixels > 2 * Image.MAX_IMAGE_PIXELS:
+ return
+
+ im = Image.core.fill(mode, size)
+ return im
+
+ offset = self.font.render(
+ text,
+ fill,
+ mode,
+ direction,
+ features,
+ language,
+ stroke_width,
+ anchor,
+ ink,
+ start[0],
+ start[1],
+ )
+ Image._decompression_bomb_check(size)
+ return im, offset
+
+ def font_variant(
+ self, font=None, size=None, index=None, encoding=None, layout_engine=None
+ ):
+ """
+ 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):
+ """
+ :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):
+ """
+ :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):
+ """
+ :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:
+ axis["name"] = axis["name"].replace(b"\x00", b"")
+ return axes
+
+ def set_variation_by_axes(self, axes):
+ """
+ :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, orientation=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, mode="", *args, **kwargs):
+ 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, *args, **kwargs):
+ # 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, *args, **kwargs):
+ 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)
+ _string_length_check(text)
+ return self.font.getlength(text, *args, **kwargs)
+
+
+def load(filename):
+ """
+ Load a font file. This function loads a font object from the given
+ bitmap font file, and returns the corresponding font object.
+
+ :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=None, size=10, index=0, encoding="", layout_engine=None):
+ """
+ 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.
+
+ 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 or :file:`/Library/Fonts/`,
+ :file:`/System/Library/Fonts/` and :file:`~/Library/Fonts/` on
+ macOS.
+
+ :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:
+ :data:`.ImageFont.Layout.BASIC` or :data:`.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.
+ """
+
+ def freetype(font):
+ 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"):
+ lindirs = os.environ.get("XDG_DATA_DIRS")
+ if not lindirs:
+ # According to the freedesktop spec, XDG_DATA_DIRS should
+ # default to /usr/share
+ lindirs = "/usr/share"
+ dirs += [os.path.join(lindir, "fonts") for lindir in lindirs.split(":")]
+ 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):
+ """
+ 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.
+ """
+ for directory in sys.path:
+ if is_directory(directory):
+ if not isinstance(filename, str):
+ filename = filename.decode("utf-8")
+ try:
+ return load(os.path.join(directory, filename))
+ except OSError:
+ pass
+ msg = "cannot find font file"
+ raise OSError(msg)
+
+
+def load_default(size=None):
+ """If FreeType support is available, load a version of Aileron Regular,
+ https://dotcolon.net/font/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 core.__class__.__name__ == "module" or size is not None:
+ f = 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
+2ziMONvb29s4jAAAAAMANf+mAg4DDAAfACYALAAAJRQGBxUjNS4BJzMeARcRLgE0Njc1MxUeARcjJ
+icVHgEBFBYXNQ4BExU+ATU0Ag5xWDpgcgRcBz41Xl9oVTpVYwpcC1ttXP6cLTQuM5szOrVRZwlOTQ
+ZqVzZECAEAGlukZAlOTQdrUG8O7iNlAQgxNhDlCDj+8/YGOjReAAAAAAUAKf/yArsCvAAHAAsAFQA
+dACcAABIyFhQGIiY0EyMBMwQiBhUUFjI2NTQSMhYUBiImNDYiBhUUFjI2NTR5iFBQiFCVVwHAV/5c
+OiMjOiPmiFBQiFCxOiMjOiMCvFaSVlaS/ZoCsjIzMC80NC8w/uNWklZWkhozMC80NC8wAAAAAgBA/
+/ICbgLAACIALgAAARUjEQYjIiY1NDY3LgE1NDYzMhcVJiMiBhUUFhcWOwE1MxUFFBYzMjc1IyIHDg
+ECbmBcYYOOVkg7R4hsQjY4Q0RNRD4SLDxW/pJUXzksPCkUUk0BgUb+zBVUZ0BkDw5RO1huCkULQzp
+COAMBcHDHRz0J/AIHRQAAAAEAKwHlAIUC3wADAAATIycze0YKWgHl+gAAAAABAGT/sAEXAwwACQAA
+EzMGEBcjLgE0Nt06dXU6OUBAAwzG/jDGVePs4wAAAAEAHv+wANEDDAAJAAATMx4BFAYHIzYQHjo5Q
+EA5OnUDDFXj7ONVxgHQAAAAAQBVAFIB2wHbAA4AAAE3FwcXBycHJzcnNxcnMwEtmxOfcTJjYzJxnx
+ObCj4BKD07KYolmZkliik7PbMAAQBQAFUB9AIlAAsAAAEjFSM1IzUzNTMVMwH0tTq1tTq1AR/Kyjj
+OzgAAAAAB/+H/iACMAGQABAAANwcjNzOMWlFOXVrS3AAAAQAgAP8A8gE3AAMAABMjNTPy0tIA/zgA
+AQAl//IApQByAAcAADYyFhQGIiY0STgkJDgkciQ4JCQ4AAAAAf/7/+IBNALQAAMAABcjEzM5Pvs+H
+gLuAAAAAAIAKf/yAhsCwAADAAcAABIgECA2IBAgKQHy/g5gATL+zgLA/TJEAkYAAAAAAQCCAAABlg
+KyAAgAAAERIxEHNTc2MwGWVr6SIygCsv1OAldxW1sWAAEAPAAAAg4CwAAZAAA3IRUhNRM+ATU0JiM
+iDwEjNz4BMzIWFRQGB7kBUv4x+kI2QTt+EAFWAQp8aGVtSl5GRjEA/0RVLzlLmAoKa3FsUkNxXQAA
+AAEALf/yAhYCwAAqAAABHgEVFAYjIi8BMxceATMyNjU0KwE1MzI2NTQmIyIGDwEjNz4BMzIWFRQGA
+YxBSZJo2RUBVgEHV0JBUaQREUBUQzc5TQcBVgEKfGhfcEMBbxJbQl1x0AoKRkZHPn9GSD80QUVCCg
+pfbGBPOlgAAAACACEAAAIkArIACgAPAAAlIxUjNSE1ATMRMyMRBg8BAiRXVv6qAVZWV60dHLCurq4
+rAdn+QgFLMibzAAABADn/8gIZArIAHQAAATIWFRQGIyIvATMXFjMyNjU0JiMiByMTIRUhBzc2ATNv
+d5Fl1RQBVgIad0VSTkVhL1IwAYj+vh8rMAHHgGdtgcUKCoFXTU5bYgGRRvAuHQAAAAACACv/8gITA
+sAAFwAjAAABMhYVFAYjIhE0NjMyFh8BIycmIyIDNzYTMjY1NCYjIgYVFBYBLmp7imr0l3RZdAgBXA
+IYZ5wKJzU6QVNJSz5SUAHSgWltiQFGxcNlVQoKdv7sPiz+ZF1LTmJbU0lhAAAAAQAyAAACGgKyAAY
+AAAEVASMBITUCGv6oXAFL/oECsij9dgJsRgAAAAMALP/xAhgCwAAWACAALAAAAR4BFRQGIyImNTQ2
+Ny4BNTQ2MhYVFAYmIgYVFBYyNjU0AzI2NTQmIyIGFRQWAZQ5S5BmbIpPOjA7ecp5P2F8Q0J8RIVJS
+0pLTEtOAW0TXTxpZ2ZqPF0SE1A3VWVlVTdQ/UU0N0RENzT9/ko+Ok1NOj1LAAIAMf/yAhkCwAAXAC
+MAAAEyERQGIyImLwEzFxYzMhMHBiMiJjU0NhMyNjU0JiMiBhUUFgEl9Jd0WXQIAVwCGGecCic1SWp
+7imo+UlBAQVNJAsD+usXDZVUKCnYBFD4sgWltif5kW1NJYV1LTmIAAAACACX/8gClAiAABwAPAAAS
+MhYUBiImNBIyFhQGIiY0STgkJDgkJDgkJDgkAiAkOCQkOP52JDgkJDgAAAAC/+H/iAClAiAABwAMA
+AASMhYUBiImNBMHIzczSTgkJDgkaFpSTl4CICQ4JCQ4/mba5gAAAQBnAB4B+AH0AAYAAAENARUlNS
+UB+P6qAVb+bwGRAbCmpkbJRMkAAAIAUAC7AfQBuwADAAcAAAEhNSERITUhAfT+XAGk/lwBpAGDOP8
+AOAABAEQAHgHVAfQABgAAARUFNS0BNQHV/m8BVv6qAStEyUSmpkYAAAAAAgAj//IB1ALAABgAIAAA
+ATIWFRQHDgEHIz4BNz4BNTQmIyIGByM+ARIyFhQGIiY0AQRibmktIAJWBSEqNig+NTlHBFoDezQ4J
+CQ4JALAZ1BjaS03JS1DMD5LLDQ/SUVgcv2yJDgkJDgAAAAAAgA2/5gDFgKYADYAQgAAAQMGFRQzMj
+Y1NCYjIg4CFRQWMzI2NxcGIyImNTQ+AjMyFhUUBiMiJwcGIyImNTQ2MzIfATcHNzYmIyIGFRQzMjY
+Cej8EJjJJlnBAfGQ+oHtAhjUYg5OPx0h2k06Os3xRWQsVLjY5VHtdPBwJETcJDyUoOkZEJz8B0f74
+EQ8kZl6EkTFZjVOLlyknMVm1pmCiaTq4lX6CSCknTVRmmR8wPdYnQzxuSWVGAAIAHQAAAncCsgAHA
+AoAACUjByMTMxMjATMDAcj+UVz4dO5d/sjPZPT0ArL9TgE6ATQAAAADAGQAAAJMArIAEAAbACcAAA
+EeARUUBgcGKwERMzIXFhUUJRUzMjc2NTQnJiMTPgE1NCcmKwEVMzIBvkdHZkwiNt7LOSGq/oeFHBt
+hahIlSTM+cB8Yj5UWAW8QT0VYYgwFArIEF5Fv1eMED2NfDAL93AU+N24PBP0AAAAAAQAv//ICjwLA
+ABsAAAEyFh8BIycmIyIGFRQWMzI/ATMHDgEjIiY1NDYBdX+PCwFWAiKiaHx5ZaIiAlYBCpWBk6a0A
+sCAagoKpqN/gaOmCgplhcicn8sAAAIAZAAAAp8CsgAMABkAAAEeARUUBgcGKwERMzITPgE1NCYnJi
+sBETMyAY59lJp8IzXN0jUVWmdjWRs5d3I4Aq4QqJWUug8EArL9mQ+PeHGHDgX92gAAAAABAGQAAAI
+vArIACwAAJRUhESEVIRUhFSEVAi/+NQHB/pUBTf6zRkYCskbwRvAAAAABAGQAAAIlArIACQAAExUh
+FSERIxEhFboBQ/69VgHBAmzwRv7KArJGAAAAAAEAL//yAo8CwAAfAAABMxEjNQcGIyImNTQ2MzIWH
+wEjJyYjIgYVFBYzMjY1IwGP90wfPnWTprSSf48LAVYCIqJofHllVG+hAU3+s3hARsicn8uAagoKpq
+N/gaN1XAAAAAEAZAAAAowCsgALAAABESMRIREjETMRIRECjFb+hFZWAXwCsv1OAS7+0gKy/sQBPAA
+AAAABAGQAAAC6ArIAAwAAMyMRM7pWVgKyAAABADf/8gHoArIAEwAAAREUBw4BIyImLwEzFxYzMjc2
+NREB6AIFcGpgbQIBVgIHfXQKAQKy/lYxIltob2EpKYyEFD0BpwAAAAABAGQAAAJ0ArIACwAACQEjA
+wcVIxEzEQEzATsBJ3ntQlZWAVVlAWH+nwEnR+ACsv6RAW8AAQBkAAACLwKyAAUAACUVIREzEQIv/j
+VWRkYCsv2UAAABAGQAAAMUArIAFAAAAREjETQ3BgcDIwMmJxYVESMRMxsBAxRWAiMxemx8NxsCVo7
+MywKy/U4BY7ZLco7+nAFmoFxLtP6dArL9lwJpAAAAAAEAZAAAAoACsgANAAAhIwEWFREjETMBJjUR
+MwKAhP67A1aEAUUDVAJeeov+pwKy/aJ5jAFZAAAAAgAv//ICuwLAAAkAEwAAEiAWFRQGICY1NBIyN
+jU0JiIGFRTbATSsrP7MrNrYenrYegLAxaKhxsahov47nIeIm5uIhwACAGQAAAJHArIADgAYAAABHg
+EVFAYHBisBESMRMzITNjQnJisBETMyAZRUX2VOHzuAVtY7GlxcGDWIiDUCrgtnVlVpCgT+5gKy/rU
+V1BUF/vgAAAACAC//zAK9AsAAEgAcAAAlFhcHJiMiBwYjIiY1NDYgFhUUJRQWMjY1NCYiBgI9PUMx
+UDcfKh8omqysATSs/dR62Hp62HpICTg7NgkHxqGixcWitbWHnJyHiJubAAIAZAAAAlgCsgAXACMAA
+CUWFyMmJyYnJisBESMRMzIXHgEVFAYHFiUzMjc+ATU0JyYrAQIqDCJfGQwNWhAhglbiOx9QXEY1Tv
+6bhDATMj1lGSyMtYgtOXR0BwH+1wKyBApbU0BSESRAAgVAOGoQBAABADT/8gIoAsAAJQAAATIWFyM
+uASMiBhUUFhceARUUBiMiJiczHgEzMjY1NCYnLgE1NDYBOmd2ClwGS0E6SUNRdW+HZnKKC1wPWkQ9
+Uk1cZGuEAsBwXUJHNjQ3OhIbZVZZbm5kREo+NT5DFRdYUFdrAAAAAAEAIgAAAmQCsgAHAAABIxEjE
+SM1IQJk9lb2AkICbP2UAmxGAAEAXv/yAmQCsgAXAAABERQHDgEiJicmNREzERQXHgEyNjc2NRECZA
+IIgfCBCAJWAgZYmlgGAgKy/k0qFFxzc1wUKgGz/lUrEkRQUEQSKwGrAAAAAAEAIAAAAnoCsgAGAAA
+hIwMzGwEzAYJ07l3N1FwCsv2PAnEAAAEAGgAAA7ECsgAMAAABAyMLASMDMxsBMxsBA7HAcZyicrZi
+kaB0nJkCsv1OAlP9rQKy/ZsCW/2kAmYAAAEAGQAAAm8CsgALAAAhCwEjEwMzGwEzAxMCCsrEY/bkY
+re+Y/D6AST+3AFcAVb+5gEa/q3+oQAAAQATAAACUQKyAAgAAAERIxEDMxsBMwFdVvRjwLphARD+8A
+EQAaL+sQFPAAABAC4AAAI5ArIACQAAJRUhNQEhNSEVAQI5/fUBof57Aen+YUZGQgIqRkX92QAAAAA
+BAGL/sAEFAwwABwAAARUjETMVIxEBBWlpowMMOP0UOANcAAAB//v/4gE0AtAAAwAABSMDMwE0Pvs+
+HgLuAAAAAQAi/7AAxQMMAAcAABcjNTMRIzUzxaNpaaNQOALsOAABAFAA1wH0AmgABgAAJQsBIxMzE
+wGwjY1GsESw1wFZ/qcBkf5vAAAAAQAy/6oBwv/iAAMAAAUhNSEBwv5wAZBWOAAAAAEAKQJEALYCsg
+ADAAATIycztjhVUAJEbgAAAAACACT/8gHQAiAAHQAlAAAhJwcGIyImNTQ2OwE1NCcmIyIHIz4BMzI
+XFh0BFBcnMjY9ASYVFAF6CR0wVUtgkJoiAgdgaQlaBm1Zrg4DCuQ9R+5MOSFQR1tbDiwUUXBUXowf
+J8c9SjRORzYSgVwAAAAAAgBK//ICRQLfABEAHgAAATIWFRQGIyImLwEVIxEzETc2EzI2NTQmIyIGH
+QEUFgFUcYCVbiNJEyNWVigySElcU01JXmECIJd4i5QTEDRJAt/+3jkq/hRuZV55ZWsdX14AAQAe//
+IB9wIgABgAAAEyFhcjJiMiBhUUFjMyNjczDgEjIiY1NDYBF152DFocbEJXU0A1Rw1aE3pbaoKQAiB
+oWH5qZm1tPDlaXYuLgZcAAAACAB7/8gIZAt8AEQAeAAABESM1BwYjIiY1NDYzMhYfAREDMjY9ATQm
+IyIGFRQWAhlWKDJacYCVbiNJEyOnSV5hQUlcUwLf/SFVOSqXeIuUExA0ARb9VWVrHV9ebmVeeQACA
+B7/8gH9AiAAFQAbAAABFAchHgEzMjY3Mw4BIyImNTQ2MzIWJyIGByEmAf0C/oAGUkA1SwlaD4FXbI
+WObmt45UBVBwEqDQEYFhNjWD84W16Oh3+akU9aU60AAAEAFQAAARoC8gAWAAATBh0BMxUjESMRIzU
+zNTQ3PgEzMhcVJqcDbW1WOTkDB0k8Hx5oAngVITRC/jQBzEIsJRs5PwVHEwAAAAIAHv8uAhkCIAAi
+AC8AAAERFAcOASMiLwEzFx4BMzI2NzY9AQcGIyImNTQ2MzIWHwE1AzI2PQE0JiMiBhUUFgIZAQSEd
+NwRAVcBBU5DTlUDASgyWnGAlW4jSRMjp0leYUFJXFMCEv5wSh1zeq8KCTI8VU0ZIQk5Kpd4i5QTED
+RJ/iJlax1fXm5lXnkAAQBKAAACCgLkABcAAAEWFREjETQnLgEHDgEdASMRMxE3NjMyFgIIAlYCBDs
+6RVRWViE5UVViAYUbQP7WASQxGzI7AQJyf+kC5P7TPSxUAAACAD4AAACsAsAABwALAAASMhYUBiIm
+NBMjETNeLiAgLiBiVlYCwCAuICAu/WACEgAC//P/LgCnAsAABwAVAAASMhYUBiImNBcRFAcGIyInN
+RY3NjURWS4gIC4gYgMLcRwNSgYCAsAgLiAgLo79wCUbZAJGBzMOHgJEAAAAAQBKAAACCALfAAsAAC
+EnBxUjETMREzMHEwGTwTJWVvdu9/rgN6kC3/4oAQv6/ugAAQBG//wA3gLfAA8AABMRFBceATcVBiM
+iJicmNRGcAQIcIxkkKi4CAQLf/bkhERoSBD4EJC8SNAJKAAAAAQBKAAADEAIgACQAAAEWFREjETQn
+JiMiFREjETQnJiMiFREjETMVNzYzMhYXNzYzMhYDCwVWBAxedFYEDF50VlYiJko7ThAvJkpEVAGfI
+jn+vAEcQyRZ1v76ARxDJFnW/voCEk08HzYtRB9HAAAAAAEASgAAAgoCIAAWAAABFhURIxE0JyYjIg
+YdASMRMxU3NjMyFgIIAlYCCXBEVVZWITlRVWIBhRtA/tYBJDEbbHR/6QISWz0sVAAAAAACAB7/8gI
+sAiAABwARAAASIBYUBiAmNBIyNjU0JiIGFRSlAQCHh/8Ah7ieWlqeWgIgn/Cfn/D+s3ZfYHV1YF8A
+AgBK/zwCRQIgABEAHgAAATIWFRQGIyImLwERIxEzFTc2EzI2NTQmIyIGHQEUFgFUcYCVbiNJEyNWV
+igySElcU01JXmECIJd4i5QTEDT+8wLWVTkq/hRuZV55ZWsdX14AAgAe/zwCGQIgABEAHgAAAREjEQ
+cGIyImNTQ2MzIWHwE1AzI2PQE0JiMiBhUUFgIZVigyWnGAlW4jSRMjp0leYUFJXFMCEv0qARk5Kpd
+4i5QTEDRJ/iJlax1fXm5lXnkAAQBKAAABPgIeAA0AAAEyFxUmBhURIxEzFTc2ARoWDkdXVlYwIwIe
+B0EFVlf+0gISU0cYAAEAGP/yAa0CIAAjAAATMhYXIyYjIgYVFBYXHgEVFAYjIiYnMxYzMjY1NCYnL
+gE1NDbkV2MJWhNdKy04PF1XbVhWbgxaE2ktOjlEUllkAiBaS2MrJCUoEBlPQkhOVFZoKCUmLhIWSE
+BIUwAAAAEAFP/4ARQCiQAXAAATERQXHgE3FQYjIiYnJjURIzUzNTMVMxWxAQMmMx8qMjMEAUdHVmM
+BzP7PGw4mFgY/BSwxDjQBNUJ7e0IAAAABAEL/8gICAhIAFwAAAREjNQcGIyImJyY1ETMRFBceATMy
+Nj0BAgJWITlRT2EKBVYEBkA1RFECEv3uWj4qTToiOQE+/tIlJC43c4DpAAAAAAEAAQAAAfwCEgAGA
+AABAyMDMxsBAfzJaclfop8CEv3uAhL+LQHTAAABAAEAAAMLAhIADAAAAQMjCwEjAzMbATMbAQMLqW
+Z2dmapY3t0a3Z7AhL97gG+/kICEv5AAcD+QwG9AAAB//oAAAHWAhIACwAAARMjJwcjEwMzFzczARq
+8ZIuKY763ZoWFYwEO/vLV1QEMAQbNzQAAAQAB/y4B+wISABEAAAEDDgEjIic1FjMyNj8BAzMbAQH7
+2iFZQB8NDRIpNhQH02GenQIS/cFVUAJGASozEwIt/i4B0gABABQAAAGxAg4ACQAAJRUhNQEhNSEVA
+QGx/mMBNP7iAYL+zkREQgGIREX+ewAAAAABAED/sAEOAwwALAAAASMiBhUUFxYVFAYHHgEVFAcGFR
+QWOwEVIyImNTQ3NjU0JzU2NTQnJjU0NjsBAQ4MKiMLDS4pKS4NCyMqDAtERAwLUlILDERECwLUGBk
+WTlsgKzUFBTcrIFtOFhkYOC87GFVMIkUIOAhFIkxVGDsvAAAAAAEAYP84AJoDIAADAAAXIxEzmjo6
+yAPoAAEAIf+wAO8DDAAsAAATFQYVFBcWFRQGKwE1MzI2NTQnJjU0NjcuATU0NzY1NCYrATUzMhYVF
+AcGFRTvUgsMREQLDCojCw0uKSkuDQsjKgwLREQMCwF6OAhFIkxVGDsvOBgZFk5bICs1BQU3KyBbTh
+YZGDgvOxhVTCJFAAABAE0A3wH2AWQAEwAAATMUIyImJyYjIhUjNDMyFhcWMzIBvjhuGywtQR0xOG4
+bLC1BHTEBZIURGCNMhREYIwAAAwAk/94DIgLoAAcAEQApAAAAIBYQBiAmECQgBhUUFiA2NTQlMhYX
+IyYjIgYUFjMyNjczDgEjIiY1NDYBAQFE3d3+vN0CB/7wubkBELn+xVBnD1wSWDo+QTcqOQZcEmZWX
+HN2Aujg/rbg4AFKpr+Mjb6+jYxbWEldV5ZZNShLVn5na34AAgB4AFIB9AGeAAUACwAAAQcXIyc3Mw
+cXIyc3AUqJiUmJifOJiUmJiQGepqampqampqYAAAIAHAHSAQ4CwAAHAA8AABIyFhQGIiY0NiIGFBY
+yNjRgakREakSTNCEhNCECwEJqQkJqCiM4IyM4AAAAAAIAUAAAAfQCCwALAA8AAAEzFSMVIzUjNTM1
+MxMhNSEBP7W1OrW1OrX+XAGkAVs4tLQ4sP31OAAAAQB0AkQBAQKyAAMAABMjNzOsOD1QAkRuAAAAA
+AEAIADsAKoBdgAHAAASMhYUBiImNEg6KCg6KAF2KDooKDoAAAIAOQBSAbUBngAFAAsAACUHIzcnMw
+UHIzcnMwELiUmJiUkBM4lJiYlJ+KampqampqYAAAABADYB5QDhAt8ABAAAEzczByM2Xk1OXQHv8Po
+AAQAWAeUAwQLfAAQAABMHIzczwV5NTl0C1fD6AAIANgHlAYsC3wAEAAkAABM3MwcjPwEzByM2Xk1O
+XapeTU5dAe/w+grw+gAAAgAWAeUBawLfAAQACQAAEwcjNzMXByM3M8FeTU5dql5NTl0C1fD6CvD6A
+AADACX/8gI1AHIABwAPABcAADYyFhQGIiY0NjIWFAYiJjQ2MhYUBiImNEk4JCQ4JOw4JCQ4JOw4JC
+Q4JHIkOCQkOCQkOCQkOCQkOCQkOAAAAAEAeABSAUoBngAFAAABBxcjJzcBSomJSYmJAZ6mpqamAAA
+AAAEAOQBSAQsBngAFAAAlByM3JzMBC4lJiYlJ+KampgAAAf9qAAABgQKyAAMAACsBATM/VwHAVwKy
+AAAAAAIAFAHIAdwClAAHABQAABMVIxUjNSM1BRUjNwcjJxcjNTMXN9pKMkoByDICKzQqATJLKysCl
+CmjoykBy46KiY3Lm5sAAQAVAAABvALyABgAAAERIxEjESMRIzUzNTQ3NjMyFxUmBgcGHQEBvFbCVj
+k5AxHHHx5iVgcDAg798gHM/jQBzEIOJRuWBUcIJDAVIRYAAAABABX//AHkAvIAJQAAJR4BNxUGIyI
+mJyY1ESYjIgcGHQEzFSMRIxEjNTM1NDc2MzIXERQBowIcIxkkKi4CAR4nXgwDbW1WLy8DEbNdOmYa
+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,
+ )
+ else:
+ 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
diff --git a/contrib/python/Pillow/py3/PIL/ImageGrab.py b/contrib/python/Pillow/py3/PIL/ImageGrab.py
new file mode 100644
index 00000000000..bcfffc3dc13
--- /dev/null
+++ b/contrib/python/Pillow/py3/PIL/ImageGrab.py
@@ -0,0 +1,177 @@
+#
+# 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.
+#
+
+import io
+import os
+import shutil
+import subprocess
+import sys
+import tempfile
+
+from . import Image
+
+
+def grab(bbox=None, include_layered_windows=False, all_screens=False, xdisplay=None):
+ 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":
+ offset, size, data = Image.core.grabscreen_win32(
+ include_layered_windows, all_screens
+ )
+ 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
+ try:
+ if not Image.core.HAVE_XCB:
+ msg = "Pillow was built without XCB support"
+ raise OSError(msg)
+ size, data = Image.core.grabscreen_x11(xdisplay)
+ except OSError:
+ if (
+ xdisplay is None
+ and sys.platform not in ("darwin", "win32")
+ and shutil.which("gnome-screenshot")
+ ):
+ fh, filepath = tempfile.mkstemp(".png")
+ os.close(fh)
+ subprocess.call(["gnome-screenshot", "-f", 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():
+ if sys.platform == "darwin":
+ fh, filepath = tempfile.mkstemp(".png")
+ os.close(fh)
+ commands = [
+ 'set theFile to (open for access POSIX file "'
+ + filepath
+ + '" with write permission)',
+ "try",
+ " write (the clipboard as «class PNGf») to theFile",
+ "end try",
+ "close access theFile",
+ ]
+ script = ["osascript"]
+ for command in commands:
+ script += ["-e", command]
+ subprocess.call(script)
+
+ im = None
+ if os.stat(filepath).st_size != 0:
+ im = Image.open(filepath)
+ im.load()
+ os.unlink(filepath)
+ return im
+ 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("utf-16le").split("\0")
+ else:
+ files = data[o:].decode("mbcs").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):
+ output = subprocess.check_output(["wl-paste", "-l"]).decode()
+ mimetypes = output.splitlines()
+ if "image/png" in mimetypes:
+ mimetype = "image/png"
+ elif mimetypes:
+ mimetype = mimetypes[0]
+ else:
+ mimetype = None
+
+ args = ["wl-paste"]
+ if mimetype:
+ args.extend(["-t", mimetype])
+ 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)
+ err = p.stderr
+ if err:
+ msg = f"{args[0]} error: {err.strip().decode()}"
+ raise ChildProcessError(msg)
+ data = io.BytesIO(p.stdout)
+ im = Image.open(data)
+ im.load()
+ return im
diff --git a/contrib/python/Pillow/py3/PIL/ImageMath.py b/contrib/python/Pillow/py3/PIL/ImageMath.py
new file mode 100644
index 00000000000..eb6bbe6c62e
--- /dev/null
+++ b/contrib/python/Pillow/py3/PIL/ImageMath.py
@@ -0,0 +1,263 @@
+#
+# 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.
+#
+
+import builtins
+
+from . import Image, _imagingmath
+
+
+def _isconstant(v):
+ return isinstance(v, (int, float))
+
+
+class _Operand:
+ """Wraps an image operand, providing standard operators"""
+
+ def __init__(self, im):
+ self.im = im
+
+ def __fixup(self, im1):
+ # 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 _isconstant(im1) 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, im1, im2=None, mode=None):
+ im1 = self.__fixup(im1)
+ if im2 is None:
+ # unary operation
+ out = Image.new(mode or im1.mode, im1.size, None)
+ im1.load()
+ try:
+ op = getattr(_imagingmath, op + "_" + im1.mode)
+ except AttributeError as e:
+ msg = f"bad operand type for '{op}'"
+ raise TypeError(msg) from e
+ _imagingmath.unop(op, out.im.id, im1.im.id)
+ else:
+ # binary operation
+ im2 = self.__fixup(im2)
+ if im1.mode != im2.mode:
+ # convert both arguments to floating point
+ if im1.mode != "F":
+ im1 = im1.convert("F")
+ if im2.mode != "F":
+ im2 = im2.convert("F")
+ if im1.size != im2.size:
+ # crop both arguments to a common size
+ size = (min(im1.size[0], im2.size[0]), min(im1.size[1], im2.size[1]))
+ if im1.size != size:
+ im1 = im1.crop((0, 0) + size)
+ if im2.size != size:
+ im2 = im2.crop((0, 0) + size)
+ out = Image.new(mode or im1.mode, im1.size, None)
+ im1.load()
+ im2.load()
+ try:
+ op = getattr(_imagingmath, op + "_" + im1.mode)
+ except AttributeError as e:
+ msg = f"bad operand type for '{op}'"
+ raise TypeError(msg) from e
+ _imagingmath.binop(op, out.im.id, im1.im.id, im2.im.id)
+ return _Operand(out)
+
+ # unary operators
+ def __bool__(self):
+ # an image is "true" if it contains at least one non-zero pixel
+ return self.im.getbbox() is not None
+
+ def __abs__(self):
+ return self.apply("abs", self)
+
+ def __pos__(self):
+ return self
+
+ def __neg__(self):
+ return self.apply("neg", self)
+
+ # binary operators
+ def __add__(self, other):
+ return self.apply("add", self, other)
+
+ def __radd__(self, other):
+ return self.apply("add", other, self)
+
+ def __sub__(self, other):
+ return self.apply("sub", self, other)
+
+ def __rsub__(self, other):
+ return self.apply("sub", other, self)
+
+ def __mul__(self, other):
+ return self.apply("mul", self, other)
+
+ def __rmul__(self, other):
+ return self.apply("mul", other, self)
+
+ def __truediv__(self, other):
+ return self.apply("div", self, other)
+
+ def __rtruediv__(self, other):
+ return self.apply("div", other, self)
+
+ def __mod__(self, other):
+ return self.apply("mod", self, other)
+
+ def __rmod__(self, other):
+ return self.apply("mod", other, self)
+
+ def __pow__(self, other):
+ return self.apply("pow", self, other)
+
+ def __rpow__(self, other):
+ return self.apply("pow", other, self)
+
+ # bitwise
+ def __invert__(self):
+ return self.apply("invert", self)
+
+ def __and__(self, other):
+ return self.apply("and", self, other)
+
+ def __rand__(self, other):
+ return self.apply("and", other, self)
+
+ def __or__(self, other):
+ return self.apply("or", self, other)
+
+ def __ror__(self, other):
+ return self.apply("or", other, self)
+
+ def __xor__(self, other):
+ return self.apply("xor", self, other)
+
+ def __rxor__(self, other):
+ return self.apply("xor", other, self)
+
+ def __lshift__(self, other):
+ return self.apply("lshift", self, other)
+
+ def __rshift__(self, other):
+ return self.apply("rshift", self, other)
+
+ # logical
+ def __eq__(self, other):
+ return self.apply("eq", self, other)
+
+ def __ne__(self, other):
+ return self.apply("ne", self, other)
+
+ def __lt__(self, other):
+ return self.apply("lt", self, other)
+
+ def __le__(self, other):
+ return self.apply("le", self, other)
+
+ def __gt__(self, other):
+ return self.apply("gt", self, other)
+
+ def __ge__(self, other):
+ return self.apply("ge", self, other)
+
+
+# conversions
+def imagemath_int(self):
+ return _Operand(self.im.convert("I"))
+
+
+def imagemath_float(self):
+ return _Operand(self.im.convert("F"))
+
+
+# logical
+def imagemath_equal(self, other):
+ return self.apply("eq", self, other, mode="I")
+
+
+def imagemath_notequal(self, other):
+ return self.apply("ne", self, other, mode="I")
+
+
+def imagemath_min(self, other):
+ return self.apply("min", self, other)
+
+
+def imagemath_max(self, other):
+ return self.apply("max", self, other)
+
+
+def imagemath_convert(self, mode):
+ return _Operand(self.im.convert(mode))
+
+
+ops = {}
+for k, v in list(globals().items()):
+ if k[:10] == "imagemath_":
+ ops[k[10:]] = v
+
+
+def eval(expression, _dict={}, **kw):
+ """
+ Evaluates an image expression.
+
+ :param expression: A string containing a Python-style expression.
+ :param options: 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.
+ """
+
+ # build execution namespace
+ args = ops.copy()
+ args.update(_dict)
+ args.update(kw)
+ for k, v in list(args.items()):
+ if hasattr(v, "im"):
+ args[k] = _Operand(v)
+
+ compiled_code = compile(expression, "<string>", "eval")
+
+ def scan(code):
+ 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
diff --git a/contrib/python/Pillow/py3/PIL/ImageMode.py b/contrib/python/Pillow/py3/PIL/ImageMode.py
new file mode 100644
index 00000000000..a0b33514296
--- /dev/null
+++ b/contrib/python/Pillow/py3/PIL/ImageMode.py
@@ -0,0 +1,90 @@
+#
+# 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.
+#
+
+import sys
+
+# mode descriptor cache
+_modes = None
+
+
+class ModeDescriptor:
+ """Wrapper for mode strings."""
+
+ def __init__(self, mode, bands, basemode, basetype, typestr):
+ self.mode = mode
+ self.bands = bands
+ self.basemode = basemode
+ self.basetype = basetype
+ self.typestr = typestr
+
+ def __str__(self):
+ return self.mode
+
+
+def getmode(mode):
+ """Gets a mode descriptor for the given mode."""
+ global _modes
+ if not _modes:
+ # initialize mode cache
+ modes = {}
+ endian = "<" if sys.byteorder == "little" else ">"
+ for m, (basemode, basetype, bands, typestr) in {
+ # core modes
+ # Bits need to be extended to bytes
+ "1": ("L", "L", ("1",), "|b1"),
+ "L": ("L", "L", ("L",), "|u1"),
+ "I": ("L", "I", ("I",), endian + "i4"),
+ "F": ("L", "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"),
+ }.items():
+ modes[m] = ModeDescriptor(m, bands, basemode, basetype, typestr)
+ # mapping modes
+ for i16mode, typestr in {
+ # I;16 == I;16L, and I;32 == I;32L
+ "I;16": "<u2",
+ "I;16S": "<i2",
+ "I;16L": "<u2",
+ "I;16LS": "<i2",
+ "I;16B": ">u2",
+ "I;16BS": ">i2",
+ "I;16N": endian + "u2",
+ "I;16NS": endian + "i2",
+ "I;32": "<u4",
+ "I;32B": ">u4",
+ "I;32L": "<u4",
+ "I;32S": "<i4",
+ "I;32BS": ">i4",
+ "I;32LS": "<i4",
+ }.items():
+ modes[i16mode] = ModeDescriptor(i16mode, ("I",), "L", "L", typestr)
+ # set global mode cache atomically
+ _modes = modes
+ return _modes[mode]
diff --git a/contrib/python/Pillow/py3/PIL/ImageMorph.py b/contrib/python/Pillow/py3/PIL/ImageMorph.py
new file mode 100644
index 00000000000..6fccc315b3d
--- /dev/null
+++ b/contrib/python/Pillow/py3/PIL/ImageMorph.py
@@ -0,0 +1,254 @@
+# A binary morphology add-on for the Python Imaging Library
+#
+# History:
+# 2014-06-04 Initial version.
+#
+# Copyright (c) 2014 Dov Grobgeld <[email protected]>
+
+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=None, op_name=None):
+ if patterns is not None:
+ self.patterns = patterns
+ else:
+ self.patterns = []
+ self.lut = 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 = "Unknown pattern " + op_name + "!"
+ raise Exception(msg)
+
+ self.patterns = known_patterns[op_name]
+
+ def add_patterns(self, patterns):
+ self.patterns += patterns
+
+ def build_default_lut(self):
+ 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):
+ return self.lut
+
+ def _string_permute(self, pattern, permutation):
+ """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, options, basic_result):
+ """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):
+ """Compile all patterns into a morphology lut.
+
+ TBD :Build based on (file) morphlut:modify_lut
+ """
+ self.build_default_lut()
+ 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
+ for i, pattern in enumerate(patterns):
+ p = pattern[0].replace(".", "X").replace("X", "[01]")
+ p = re.compile(p)
+ patterns[i] = (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 p, r in patterns:
+ if p.match(bitpattern):
+ self.lut[i] = [0, 1][r]
+
+ return self.lut
+
+
+class MorphOp:
+ """A class for binary morphological operators"""
+
+ def __init__(self, lut=None, op_name=None, patterns=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):
+ """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.im.id, outimage.im.id)
+ return count, outimage
+
+ def match(self, image):
+ """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.im.id)
+
+ def get_on_pixels(self, image):
+ """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.im.id)
+
+ def load_lut(self, filename):
+ """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):
+ """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):
+ """Set the lut from an external source"""
+ self.lut = lut
diff --git a/contrib/python/Pillow/py3/PIL/ImageOps.py b/contrib/python/Pillow/py3/PIL/ImageOps.py
new file mode 100644
index 00000000000..42f2152b39a
--- /dev/null
+++ b/contrib/python/Pillow/py3/PIL/ImageOps.py
@@ -0,0 +1,658 @@
+#
+# 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.
+#
+
+import functools
+import operator
+import re
+
+from . import ExifTags, Image, ImagePalette
+
+#
+# helpers
+
+
+def _border(border):
+ 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, mode):
+ if isinstance(color, str):
+ from . import ImageColor
+
+ color = ImageColor.getcolor(color, mode)
+ return color
+
+
+def _lut(image, lut):
+ 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 = "not supported for this image mode"
+ raise OSError(msg)
+
+
+#
+# actions
+
+
+def autocontrast(image, cutoff=0, ignore=None, mask=None, preserve_tone=False):
+ """
+ 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
+ try:
+ h[ignore] = 0
+ except TypeError:
+ # assume sequence
+ 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 = 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 = 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, black, white, mid=None, blackpoint=0, whitepoint=255, midpoint=127):
+ """
+ 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
+ black = _color(black, "RGB")
+ white = _color(white, "RGB")
+ if mid is not None:
+ mid = _color(mid, "RGB")
+
+ # Empty lists for the mapping
+ red = []
+ green = []
+ blue = []
+
+ # Create the low-end values
+ for i in range(0, blackpoint):
+ red.append(black[0])
+ green.append(black[1])
+ blue.append(black[2])
+
+ # Create the mapping (2-color)
+ if mid is None:
+ range_map = range(0, whitepoint - blackpoint)
+
+ for i in range_map:
+ red.append(black[0] + i * (white[0] - black[0]) // len(range_map))
+ green.append(black[1] + i * (white[1] - black[1]) // len(range_map))
+ blue.append(black[2] + i * (white[2] - black[2]) // len(range_map))
+
+ # Create the mapping (3-color)
+ else:
+ range_map1 = range(0, midpoint - blackpoint)
+ range_map2 = range(0, whitepoint - midpoint)
+
+ for i in range_map1:
+ red.append(black[0] + i * (mid[0] - black[0]) // len(range_map1))
+ green.append(black[1] + i * (mid[1] - black[1]) // len(range_map1))
+ blue.append(black[2] + i * (mid[2] - black[2]) // len(range_map1))
+ for i in range_map2:
+ red.append(mid[0] + i * (white[0] - mid[0]) // len(range_map2))
+ green.append(mid[1] + i * (white[1] - mid[1]) // len(range_map2))
+ blue.append(mid[2] + i * (white[2] - mid[2]) // len(range_map2))
+
+ # Create the high-end values
+ for i in range(0, 256 - whitepoint):
+ red.append(white[0])
+ green.append(white[1])
+ blue.append(white[2])
+
+ # Return converted image
+ image = image.convert("RGB")
+ return _lut(image, red + green + blue)
+
+
+def contain(image, size, method=Image.Resampling.BICUBIC):
+ """
+ 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, size, method=Image.Resampling.BICUBIC):
+ """
+ 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, size, method=Image.Resampling.BICUBIC, color=None, centering=(0.5, 0.5)):
+ """
+ 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:
+ out.putpalette(resized.getpalette())
+ 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, border=0):
+ """
+ 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, factor, resample=Image.Resampling.BICUBIC):
+ """
+ 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)
+
+
+def deform(image, deformer, resample=Image.Resampling.BILINEAR):
+ """
+ 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, mask=None):
+ """
+ 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, border=0, fill=0):
+ """
+ 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):
+ 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, size, method=Image.Resampling.BICUBIC, bleed=0.0, centering=(0.5, 0.5)):
+ """
+ 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
+ # https://www.cazabon.com
+
+ # ensure centering is mutable
+ centering = list(centering)
+
+ if not 0.0 <= centering[0] <= 1.0:
+ centering[0] = 0.5
+ if not 0.0 <= centering[1] <= 1.0:
+ centering[1] = 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[0]
+ crop_top = bleed_pixels[1] + (live_size[1] - crop_height) * centering[1]
+
+ 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):
+ """
+ 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):
+ """
+ Convert the image to grayscale.
+
+ :param image: The image to convert.
+ :return: An image.
+ """
+ return image.convert("L")
+
+
+def invert(image):
+ """
+ Invert (negate) the image.
+
+ :param image: The image to invert.
+ :return: An image.
+ """
+ lut = []
+ for i in range(256):
+ lut.append(255 - i)
+ return image.point(lut) if image.mode == "1" else _lut(image, lut)
+
+
+def mirror(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, bits):
+ """
+ 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.
+ """
+ lut = []
+ mask = ~(2 ** (8 - bits) - 1)
+ for i in range(256):
+ lut.append(i & mask)
+ return _lut(image, lut)
+
+
+def solarize(image, threshold=128):
+ """
+ Invert all pixel values above a threshold.
+
+ :param image: The image to solarize.
+ :param threshold: All pixels above this greyscale 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)
+
+
+def exif_transpose(image, *, in_place=False):
+ """
+ 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)
+ 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:
+ transposed_image = image.transpose(method)
+ if in_place:
+ image.im = transposed_image.im
+ image.pyaccess = None
+ image._size = transposed_image._size
+ 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()
+ elif "XML:com.adobe.xmp" in exif_image.info:
+ for pattern in (
+ r'tiff:Orientation="([0-9])"',
+ r"<tiff:Orientation>([0-9])</tiff:Orientation>",
+ ):
+ exif_image.info["XML:com.adobe.xmp"] = re.sub(
+ pattern, "", exif_image.info["XML:com.adobe.xmp"]
+ )
+ if not in_place:
+ return transposed_image
+ elif not in_place:
+ return image.copy()
diff --git a/contrib/python/Pillow/py3/PIL/ImagePalette.py b/contrib/python/Pillow/py3/PIL/ImagePalette.py
new file mode 100644
index 00000000000..f0c09470863
--- /dev/null
+++ b/contrib/python/Pillow/py3/PIL/ImagePalette.py
@@ -0,0 +1,266 @@
+#
+# 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.
+#
+
+import array
+
+from . import GimpGradientFile, GimpPaletteFile, ImageColor, PaletteFile
+
+
+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="RGB", palette=None):
+ self.mode = mode
+ self.rawmode = None # if set, palette contains raw data
+ self.palette = palette or bytearray()
+ self.dirty = None
+
+ @property
+ def palette(self):
+ return self._palette
+
+ @palette.setter
+ def palette(self, palette):
+ self._colors = None
+ self._palette = palette
+
+ @property
+ def colors(self):
+ 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):
+ self._colors = colors
+
+ def copy(self):
+ 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):
+ """
+ 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):
+ """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 getcolor(self, color, image=None):
+ """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
+ if not isinstance(self.palette, bytearray):
+ self._palette = bytearray(self.palette)
+ index = len(self.palette) // 3
+ special_colors = ()
+ 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
+ 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)}"
+ raise ValueError(msg)
+
+ def save(self, fp):
+ """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, data):
+ palette = ImagePalette()
+ palette.rawmode = rawmode
+ palette.palette = data
+ palette.dirty = 1
+ return palette
+
+
+# --------------------------------------------------------------------
+# Factories
+
+
+def make_linear_lut(black, white):
+ lut = []
+ if black == 0:
+ for i in range(256):
+ lut.append(white * i // 255)
+ else:
+ raise NotImplementedError # FIXME
+ return lut
+
+
+def make_gamma_lut(exp):
+ lut = []
+ for i in range(256):
+ lut.append(int(((i / 255.0) ** exp) * 255.0 + 0.5))
+ return lut
+
+
+def negative(mode="RGB"):
+ palette = list(range(256 * len(mode)))
+ palette.reverse()
+ return ImagePalette(mode, [i // len(mode) for i in palette])
+
+
+def random(mode="RGB"):
+ from random import randint
+
+ palette = []
+ for i in range(256 * len(mode)):
+ palette.append(randint(0, 255))
+ return ImagePalette(mode, palette)
+
+
+def sepia(white="#fff0c0"):
+ 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="RGB"):
+ palette = list(range(256 * len(mode)))
+ return ImagePalette(mode, [i // len(mode) for i in palette])
+
+
+def load(filename):
+ # FIXME: supports GIMP gradients only
+
+ with open(filename, "rb") as fp:
+ for paletteHandler in [
+ GimpPaletteFile.GimpPaletteFile,
+ GimpGradientFile.GimpGradientFile,
+ PaletteFile.PaletteFile,
+ ]:
+ try:
+ fp.seek(0)
+ lut = paletteHandler(fp).getpalette()
+ if lut:
+ break
+ except (SyntaxError, ValueError):
+ # import traceback
+ # traceback.print_exc()
+ pass
+ else:
+ msg = "cannot load palette"
+ raise OSError(msg)
+
+ return lut # data, rawmode
diff --git a/contrib/python/Pillow/py3/PIL/ImagePath.py b/contrib/python/Pillow/py3/PIL/ImagePath.py
new file mode 100644
index 00000000000..3d3538c97b7
--- /dev/null
+++ b/contrib/python/Pillow/py3/PIL/ImagePath.py
@@ -0,0 +1,19 @@
+#
+# 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 . import Image
+
+Path = Image.core.path
diff --git a/contrib/python/Pillow/py3/PIL/ImageSequence.py b/contrib/python/Pillow/py3/PIL/ImageSequence.py
new file mode 100644
index 00000000000..c4bb6334acf
--- /dev/null
+++ b/contrib/python/Pillow/py3/PIL/ImageSequence.py
@@ -0,0 +1,76 @@
+#
+# 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.
+#
+
+##
+
+
+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):
+ 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):
+ try:
+ self.im.seek(ix)
+ return self.im
+ except EOFError as e:
+ raise IndexError from e # end of sequence
+
+ def __iter__(self):
+ return self
+
+ def __next__(self):
+ try:
+ self.im.seek(self.position)
+ self.position += 1
+ return self.im
+ except EOFError as e:
+ raise StopIteration from e
+
+
+def all_frames(im, func=None):
+ """
+ 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/contrib/python/Pillow/py3/PIL/ImageShow.py b/contrib/python/Pillow/py3/PIL/ImageShow.py
new file mode 100644
index 00000000000..8b1c3f8bb63
--- /dev/null
+++ b/contrib/python/Pillow/py3/PIL/ImageShow.py
@@ -0,0 +1,323 @@
+#
+# 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.
+#
+import os
+import shutil
+import subprocess
+import sys
+from shlex import quote
+
+from . import Image
+
+_viewers = []
+
+
+def register(viewer, order=1):
+ """
+ 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.
+ """
+ try:
+ if issubclass(viewer, Viewer):
+ viewer = viewer()
+ except TypeError:
+ pass # raised if viewer wasn't a class
+ if order > 0:
+ _viewers.append(viewer)
+ else:
+ _viewers.insert(0, viewer)
+
+
+def show(image, title=None, **options):
+ 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, **options):
+ """
+ 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 = None
+ """The format to convert the image into."""
+ options = {}
+ """Additional options used to convert the image."""
+
+ def get_format(self, image):
+ """Return format name, or ``None`` to save as PGM/PPM."""
+ return self.format
+
+ def get_command(self, file, **options):
+ """
+ Returns the command used to display the file.
+ Not implemented in the base class.
+ """
+ raise NotImplementedError
+
+ def save_image(self, image):
+ """Save to temporary file and return filename."""
+ return image._dump(format=self.get_format(image), **self.options)
+
+ def show_image(self, image, **options):
+ """Display the given image."""
+ return self.show_file(self.save_image(image), **options)
+
+ def show_file(self, path, **options):
+ """
+ Display given file.
+ """
+ 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, **options):
+ return (
+ f'start "Pillow" /WAIT "{file}" '
+ "&& ping -n 4 127.0.0.1 >NUL "
+ f'&& del /f "{file}"'
+ )
+
+
+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, **options):
+ # 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, **options):
+ """
+ Display given file.
+ """
+ subprocess.call(["open", "-a", "Preview.app", path])
+ executable = 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(Viewer):
+ format = "PNG"
+ options = {"compress_level": 1, "save_all": True}
+
+ def get_command(self, file, **options):
+ 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, **options):
+ command = executable = "xdg-open"
+ return command, executable
+
+ def show_file(self, path, **options):
+ """
+ Display given file.
+ """
+ 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, title=None, **options):
+ command = executable = "display"
+ if title:
+ command += f" -title {quote(title)}"
+ return command, executable
+
+ def show_file(self, path, **options):
+ """
+ Display given file.
+ """
+ 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, **options):
+ executable = "gm"
+ command = "gm display"
+ return command, executable
+
+ def show_file(self, path, **options):
+ """
+ Display given file.
+ """
+ subprocess.Popen(["gm", "display", path])
+ return 1
+
+
+class EogViewer(UnixViewer):
+ """The GNOME Image Viewer ``eog`` command."""
+
+ def get_command_ex(self, file, **options):
+ executable = "eog"
+ command = "eog -n"
+ return command, executable
+
+ def show_file(self, path, **options):
+ """
+ Display given file.
+ """
+ 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, title=None, **options):
+ # 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, **options):
+ """
+ Display given file.
+ """
+ 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, **options):
+ 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/contrib/python/Pillow/py3/PIL/ImageStat.py b/contrib/python/Pillow/py3/PIL/ImageStat.py
new file mode 100644
index 00000000000..b7ebddf066a
--- /dev/null
+++ b/contrib/python/Pillow/py3/PIL/ImageStat.py
@@ -0,0 +1,148 @@
+#
+# 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.
+#
+
+import functools
+import math
+import operator
+
+
+class Stat:
+ def __init__(self, image_or_list, mask=None):
+ try:
+ if mask:
+ self.h = image_or_list.histogram(mask)
+ else:
+ self.h = image_or_list.histogram()
+ except AttributeError:
+ self.h = image_or_list # assume it to be a histogram list
+ if not isinstance(self.h, list):
+ msg = "first argument must be image or list"
+ raise TypeError(msg)
+ self.bands = list(range(len(self.h) // 256))
+
+ def __getattr__(self, id):
+ """Calculate missing attribute"""
+ if id[:4] == "_get":
+ raise AttributeError(id)
+ # calculate missing attribute
+ v = getattr(self, "_get" + id)()
+ setattr(self, id, v)
+ return v
+
+ def _getextrema(self):
+ """Get min/max values for each band in the image"""
+
+ def minmax(histogram):
+ n = 255
+ x = 0
+ for i in range(256):
+ if histogram[i]:
+ n = min(n, i)
+ x = max(x, i)
+ return n, x # returns (255, 0) if there's no data in the histogram
+
+ v = []
+ for i in range(0, len(self.h), 256):
+ v.append(minmax(self.h[i:]))
+ return v
+
+ def _getcount(self):
+ """Get total number of pixels in each layer"""
+
+ v = []
+ for i in range(0, len(self.h), 256):
+ v.append(functools.reduce(operator.add, self.h[i : i + 256]))
+ return v
+
+ def _getsum(self):
+ """Get sum of all pixels in each layer"""
+
+ 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
+
+ def _getsum2(self):
+ """Get squared sum of all pixels in each layer"""
+
+ 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
+
+ def _getmean(self):
+ """Get average pixel level for each layer"""
+
+ v = []
+ for i in self.bands:
+ v.append(self.sum[i] / self.count[i])
+ return v
+
+ def _getmedian(self):
+ """Get median pixel level for each layer"""
+
+ 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
+
+ def _getrms(self):
+ """Get RMS for each layer"""
+
+ v = []
+ for i in self.bands:
+ v.append(math.sqrt(self.sum2[i] / self.count[i]))
+ return v
+
+ def _getvar(self):
+ """Get variance for each layer"""
+
+ v = []
+ for i in self.bands:
+ n = self.count[i]
+ v.append((self.sum2[i] - (self.sum[i] ** 2.0) / n) / n)
+ return v
+
+ def _getstddev(self):
+ """Get standard deviation for each layer"""
+
+ v = []
+ for i in self.bands:
+ v.append(math.sqrt(self.var[i]))
+ return v
+
+
+Global = Stat # compatibility
diff --git a/contrib/python/Pillow/py3/PIL/ImageTransform.py b/contrib/python/Pillow/py3/PIL/ImageTransform.py
new file mode 100644
index 00000000000..7881f0d262b
--- /dev/null
+++ b/contrib/python/Pillow/py3/PIL/ImageTransform.py
@@ -0,0 +1,102 @@
+#
+# 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 . import Image
+
+
+class Transform(Image.ImageTransformHandler):
+ def __init__(self, data):
+ self.data = data
+
+ def getdata(self):
+ return self.method, self.data
+
+ def transform(self, size, image, **options):
+ # 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 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:`~PIL.Image.Image.transform`
+
+ :param matrix: A 6-tuple (a, b, c, d, e, f) containing the first two rows
+ from an affine transform matrix.
+ """
+
+ method = Image.Transform.AFFINE
+
+
+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:`~PIL.Image.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:`~PIL.Image.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:`~PIL.Image.Image.transform`
+
+ :param data: A list of (bbox, quad) tuples.
+ """
+
+ method = Image.Transform.MESH
diff --git a/contrib/python/Pillow/py3/PIL/ImageWin.py b/contrib/python/Pillow/py3/PIL/ImageWin.py
new file mode 100644
index 00000000000..ca9b14c8adf
--- /dev/null
+++ b/contrib/python/Pillow/py3/PIL/ImageWin.py
@@ -0,0 +1,230 @@
+#
+# 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 . 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):
+ self.dc = dc
+
+ def __int__(self):
+ 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):
+ self.wnd = wnd
+
+ def __int__(self):
+ 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 greylevels
+ are allocated. For an "RGB" image, a 6x6x6 colour cube is used, together
+ with 20 greylevels.
+
+ 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, size=None):
+ if hasattr(image, "mode") and hasattr(image, "size"):
+ mode = image.mode
+ size = image.size
+ else:
+ mode = image
+ image = None
+ 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:
+ self.paste(image)
+
+ def expose(self, handle):
+ """
+ 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.
+ """
+ if isinstance(handle, HWND):
+ dc = self.image.getdc(handle)
+ try:
+ result = self.image.expose(dc)
+ finally:
+ self.image.releasedc(handle, dc)
+ else:
+ result = self.image.expose(handle)
+ return result
+
+ def draw(self, handle, dst, src=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 not src:
+ src = (0, 0) + self.size
+ if isinstance(handle, HWND):
+ dc = self.image.getdc(handle)
+ try:
+ result = self.image.draw(dc, dst, src)
+ finally:
+ self.image.releasedc(handle, dc)
+ else:
+ result = self.image.draw(handle, dst, src)
+ return result
+
+ def query_palette(self, handle):
+ """
+ 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: A true value if one or more entries were changed (this
+ indicates that the image should be redrawn).
+ """
+ if isinstance(handle, HWND):
+ handle = self.image.getdc(handle)
+ try:
+ result = self.image.query_palette(handle)
+ finally:
+ self.image.releasedc(handle, handle)
+ else:
+ result = self.image.query_palette(handle)
+ return result
+
+ def paste(self, im, box=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):
+ """
+ Load display memory contents from byte data.
+
+ :param buffer: A buffer containing display data (usually
+ data returned from :py:func:`~PIL.ImageWin.Dib.tobytes`)
+ """
+ return self.image.frombytes(buffer)
+
+ def tobytes(self):
+ """
+ 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="PIL", width=None, height=None):
+ self.hwnd = Image.core.createwindow(
+ title, self.__dispatcher, width or 0, height or 0
+ )
+
+ def __dispatcher(self, action, *args):
+ return getattr(self, "ui_handle_" + action)(*args)
+
+ def ui_handle_clear(self, dc, x0, y0, x1, y1):
+ pass
+
+ def ui_handle_damage(self, x0, y0, x1, y1):
+ pass
+
+ def ui_handle_destroy(self):
+ pass
+
+ def ui_handle_repair(self, dc, x0, y0, x1, y1):
+ pass
+
+ def ui_handle_resize(self, width, height):
+ pass
+
+ def mainloop(self):
+ Image.core.eventloop()
+
+
+class ImageWindow(Window):
+ """Create an image window which displays the given image."""
+
+ def __init__(self, image, title="PIL"):
+ 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, x0, y0, x1, y1):
+ self.image.draw(dc, (x0, y0, x1, y1))
diff --git a/contrib/python/Pillow/py3/PIL/ImtImagePlugin.py b/contrib/python/Pillow/py3/PIL/ImtImagePlugin.py
new file mode 100644
index 00000000000..d409fcd59de
--- /dev/null
+++ b/contrib/python/Pillow/py3/PIL/ImtImagePlugin.py
@@ -0,0 +1,101 @@
+#
+# 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.
+#
+
+
+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):
+ # Quick rejection: if there's not a LF among the first
+ # 100 bytes, this is (probably) not a text header.
+
+ 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 = [
+ (
+ "raw",
+ (0, 0) + self.size,
+ self.fp.tell() - len(buffer),
+ (self.mode, 0, 1),
+ )
+ ]
+
+ 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/contrib/python/Pillow/py3/PIL/IptcImagePlugin.py b/contrib/python/Pillow/py3/PIL/IptcImagePlugin.py
new file mode 100644
index 00000000000..316cd17c732
--- /dev/null
+++ b/contrib/python/Pillow/py3/PIL/IptcImagePlugin.py
@@ -0,0 +1,230 @@
+#
+# 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.
+#
+import os
+import tempfile
+
+from . import Image, ImageFile
+from ._binary import i8
+from ._binary import i16be as i16
+from ._binary import i32be as i32
+from ._binary import o8
+
+COMPRESSION = {1: "raw", 5: "jpeg"}
+
+PAD = o8(0) * 4
+
+
+#
+# Helpers
+
+
+def i(c):
+ return i32((PAD + c)[-4:])
+
+
+def dump(c):
+ for i in c:
+ print("%02x" % i8(i), end=" ")
+ print()
+
+
+##
+# Image plugin for IPTC/NAA datastreams. To read IPTC/NAA fields
+# from TIFF and JPEG files, use the <b>getiptcinfo</b> function.
+
+
+class IptcImageFile(ImageFile.ImageFile):
+ format = "IPTC"
+ format_description = "IPTC/NAA"
+
+ def getint(self, key):
+ return i(self.info[key])
+
+ def field(self):
+ #
+ # 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):
+ # 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 = i8(self.info[(3, 60)][0])
+ component = i8(self.info[(3, 60)][1])
+ if (3, 65) in self.info:
+ id = i8(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 = [
+ ("iptc", (compression, offset), (0, 0, self.size[0], self.size[1]))
+ ]
+
+ def load(self):
+ if len(self.tile) != 1 or self.tile[0][0] != "iptc":
+ return ImageFile.ImageFile.load(self)
+
+ type, tile, box = self.tile[0]
+
+ encoding, offset = tile
+
+ self.fp.seek(offset)
+
+ # Copy image data to temporary file
+ o_fd, outfile = tempfile.mkstemp(text=False)
+ o = os.fdopen(o_fd)
+ if encoding == "raw":
+ # To simplify access to the extracted file,
+ # prepend a PPM header
+ o.write("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)
+ o.close()
+
+ try:
+ with Image.open(outfile) as _im:
+ _im.load()
+ self.im = _im.im
+ finally:
+ try:
+ os.unlink(outfile)
+ except OSError:
+ pass
+
+
+Image.register_open(IptcImageFile.format, IptcImageFile)
+
+Image.register_extension(IptcImageFile.format, ".iim")
+
+
+def getiptcinfo(im):
+ """
+ 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.
+ """
+ import io
+
+ from . import JpegImagePlugin, TiffImagePlugin
+
+ data = None
+
+ if isinstance(im, IptcImageFile):
+ # return info dictionary right away
+ return im.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.tagdata[TiffImagePlugin.IPTC_NAA_CHUNK]
+ except (AttributeError, KeyError):
+ pass
+
+ if data is None:
+ return None # no properties
+
+ # create an IptcImagePlugin object without initializing it
+ class FakeImage:
+ pass
+
+ im = FakeImage()
+ im.__class__ = IptcImageFile
+
+ # parse the IPTC information chunk
+ im.info = {}
+ im.fp = io.BytesIO(data)
+
+ try:
+ im._open()
+ except (IndexError, KeyError):
+ pass # expected failure
+
+ return im.info
diff --git a/contrib/python/Pillow/py3/PIL/Jpeg2KImagePlugin.py b/contrib/python/Pillow/py3/PIL/Jpeg2KImagePlugin.py
new file mode 100644
index 00000000000..963d6c1a31c
--- /dev/null
+++ b/contrib/python/Pillow/py3/PIL/Jpeg2KImagePlugin.py
@@ -0,0 +1,399 @@
+#
+# 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.
+#
+import io
+import os
+import struct
+
+from . import Image, ImageFile, _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, length=-1):
+ self.fp = fp
+ self.has_length = length >= 0
+ self.length = length
+ self.remaining_in_box = -1
+
+ def _can_read(self, num_bytes):
+ 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):
+ 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):
+ size = struct.calcsize(field_format)
+ data = self._read_bytes(size)
+ return struct.unpack(field_format, data)
+
+ def read_boxes(self):
+ size = self.remaining_in_box
+ data = self._read_bytes(size)
+ return BoxReader(io.BytesIO(data), size)
+
+ def has_next_box(self):
+ if self.has_length:
+ return self.fp.tell() + self.remaining_in_box < self.length
+ else:
+ return True
+
+ def next_box_type(self):
+ # 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 = self.read_fields(">I4s")
+ if lbox == 1:
+ lbox = 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):
+ """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
+ )
+ ssiz = [None] * csiz
+ xrsiz = [None] * csiz
+ yrsiz = [None] * csiz
+ for i in range(csiz):
+ ssiz[i], xrsiz[i], yrsiz[i] = struct.unpack_from(">BBB", siz, 36 + 3 * i)
+
+ size = (xsiz - xosiz, ysiz - yosiz)
+ if csiz == 1:
+ if (yrsiz[0] & 0x7F) > 8:
+ mode = "I;16"
+ else:
+ mode = "L"
+ elif csiz == 2:
+ mode = "LA"
+ elif csiz == 3:
+ mode = "RGB"
+ elif csiz == 4:
+ mode = "RGBA"
+ else:
+ mode = None
+
+ return size, mode
+
+
+def _res_to_dpi(num, denom, exp):
+ """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 (254 * num * (10**exp)) / (10000 * denom)
+
+
+def _parse_jp2_header(fp):
+ """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"
+
+ size = None
+ mode = None
+ bpc = None
+ nc = None
+ dpi = None # 2-tuple of DPI info, or None
+
+ while header.has_next_box():
+ tbox = header.next_box_type()
+
+ if tbox == b"ihdr":
+ height, width, nc, bpc = header.read_fields(">IIHB")
+ 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"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")
+ 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
+
+
+##
+# Image plugin for JPEG2000 images.
+
+
+class Jpeg2KImageFile(ImageFile.ImageFile):
+ format = "JPEG2000"
+ format_description = "JPEG 2000 (ISO 15444)"
+
+ def _open(self):
+ sig = self.fp.read(4)
+ if sig == b"\xff\x4f\xff\x51":
+ self.codec = "j2k"
+ self._size, self._mode = _parse_codestream(self.fp)
+ 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 = header
+ if dpi is not None:
+ self.info["dpi"] = dpi
+ if self.fp.read(12).endswith(b"jp2c\xff\x4f\xff\x51"):
+ self._parse_comment()
+ else:
+ msg = "not a JPEG 2000 file"
+ raise SyntaxError(msg)
+
+ if self.size is None or self.mode is None:
+ msg = "unable to determine size/mode"
+ 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 = [
+ (
+ "jpeg2k",
+ (0, 0) + self.size,
+ 0,
+ (self.codec, self._reduce, self.layers, fd, length),
+ )
+ ]
+
+ def _parse_comment(self):
+ hdr = self.fp.read(2)
+ length = _binary.i16be(hdr)
+ self.fp.seek(length - 2, os.SEEK_CUR)
+
+ 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
+ def reduce(self):
+ # 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):
+ self._reduce = value
+
+ def load(self):
+ 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]
+ t3 = (t[3][0], self._reduce, self.layers, t[3][3], t[3][4])
+ self.tile = [(t[0], (0, 0) + self.size, t[2], t3)]
+
+ return ImageFile.ImageFile.load(self)
+
+
+def _accept(prefix):
+ return (
+ prefix[:4] == b"\xff\x4f\xff\x51"
+ or prefix[:12] == b"\x00\x00\x00\x0cjP \x0d\x0a\x87\x0a"
+ )
+
+
+# ------------------------------------------------------------
+# Save support
+
+
+def _save(im, fp, filename):
+ # Get the keyword arguments
+ info = im.encoderinfo
+
+ if filename.endswith(".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, [("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/contrib/python/Pillow/py3/PIL/JpegImagePlugin.py b/contrib/python/Pillow/py3/PIL/JpegImagePlugin.py
new file mode 100644
index 00000000000..917bbf39fbb
--- /dev/null
+++ b/contrib/python/Pillow/py3/PIL/JpegImagePlugin.py
@@ -0,0 +1,861 @@
+#
+# 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.
+#
+import array
+import io
+import math
+import os
+import struct
+import subprocess
+import sys
+import tempfile
+import warnings
+
+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 .JpegPresets import presets
+
+#
+# Parser
+
+
+def Skip(self, marker):
+ n = i16(self.fp.read(2)) - 2
+ ImageFile._safe_read(self.fp, n)
+
+
+def APP(self, marker):
+ #
+ # 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 = "APP%d" % (marker & 15)
+
+ self.app[app] = s # compatibility
+ self.applist.append((app, s))
+
+ if marker == 0xFFE0 and s[:4] == 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
+ self.info["jfif_unit"] = jfif_unit
+ self.info["jfif_density"] = jfif_density
+ elif marker == 0xFFE1 and s[:5] == b"Exif\0":
+ if "exif" not in self.info:
+ # extract EXIF information (incomplete)
+ self.info["exif"] = s # FIXME: value will change
+ self._exif_offset = self.fp.tell() - n + 6
+ elif marker == 0xFFE2 and s[:5] == b"FPXR\0":
+ # extract FlashPix information (incomplete)
+ self.info["flashpix"] = s # FIXME: value will change
+ elif marker == 0xFFE2 and s[:12] == 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[:14] == 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
+ data = {
+ "XResolution": i32(data, 0) / 65536,
+ "DisplayedUnitsX": i16(data, 4),
+ "YResolution": i32(data, 8) / 65536,
+ "DisplayedUnitsY": i16(data, 12),
+ }
+ photoshop[code] = data
+ offset += size
+ offset += offset & 1 # align
+ except struct.error:
+ break # insufficient data
+
+ elif marker == 0xFFEE and s[:5] == 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[:4] == 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
+
+ # If DPI isn't in JPEG header, fetch from EXIF
+ if "dpi" not in self.info and "exif" in self.info:
+ 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):
+ raise ValueError
+ if resolution_unit == 3: # cm
+ # 1 dpcm = 2.54 dpi
+ dpi *= 2.54
+ self.info["dpi"] = dpi, dpi
+ except (
+ struct.error,
+ KeyError,
+ SyntaxError,
+ TypeError,
+ ValueError,
+ ZeroDivisionError,
+ ):
+ # struct.error for truncated EXIF
+ # KeyError for dpi not included
+ # SyntaxError for invalid/unreadable EXIF
+ # ValueError or TypeError for dpi being an invalid float
+ # ZeroDivisionError for invalid dpi rational value
+ self.info["dpi"] = 72, 72
+
+
+def COM(self, marker):
+ #
+ # 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, marker):
+ #
+ # 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 = []
+ for p in self.icclist:
+ profile.append(p[14:])
+ 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, marker):
+ #
+ # 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):
+ # Magic number was taken from https://en.wikipedia.org/wiki/JPEG
+ return prefix[:3] == 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):
+ 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
+
+ # JPEG specifics (internal)
+ self.layer = []
+ self.huffman_dc = {}
+ self.huffman_ac = {}
+ self.quantization = {}
+ self.app = {} # compatibility
+ self.applist = []
+ self.icclist = []
+
+ 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 = [("jpeg", (0, 0) + self.size, 0, (rawmode, ""))]
+ # self.__offset = self.fp.tell()
+ break
+ s = self.fp.read(1)
+ elif i == 0 or i == 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)
+
+ def load_read(self, read_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, size):
+ if len(self.tile) != 1:
+ return
+
+ # Protect from second call
+ if self.decoderconfig:
+ return
+
+ d, e, o, a = self.tile[0]
+ scale = 1
+ original_size = self.size
+
+ 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
+ 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 = [(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):
+ # 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):
+ return _getexif(self)
+
+ def _getmp(self):
+ return _getmp(self)
+
+ def getxmp(self):
+ """
+ Returns a dictionary containing the XMP tags.
+ Requires defusedxml to be installed.
+
+ :returns: XMP tags in a dictionary.
+ """
+
+ for segment, content in self.applist:
+ if segment == "APP1":
+ marker, xmp_tags = content.split(b"\x00")[:2]
+ if marker == b"http://ns.adobe.com/xap/1.0/":
+ return self._getxmp(xmp_tags)
+ return {}
+
+
+def _getexif(self):
+ if "exif" not in self.info:
+ return None
+ return self.getexif()._get_merged_dict()
+
+
+def _getmp(self):
+ # 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[:4] == 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(0, 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):
+ # 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 hasattr(im, "layers") 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, fp, filename):
+ 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):
+ 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:
+ raise TypeError
+ table = array.array("H", table)
+ except TypeError as e:
+ msg = "Invalid quantization table"
+ raise ValueError(msg) from e
+ else:
+ qtables[idx] = list(table)
+ 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
+ icc_profile = info.get("icc_profile")
+ if icc_profile:
+ ICC_OVERHEAD_LEN = 14
+ MAX_DATA_BYTES_IN_MARKER = MAX_BYTES_IN_MARKER - ICC_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 + ICC_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("streamtype", 0),
+ dpi[0],
+ dpi[1],
+ subsampling,
+ 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
+ bufsize = 0
+ 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(bufsize, len(exif) + 5, len(extra) + 1)
+
+ ImageFile._save(im, fp, [("jpeg", (0, 0) + im.size, 0, rawmode)], bufsize)
+
+
+def _save_cjpeg(im, fp, filename):
+ # 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=None, filename=None):
+ im = JpegImageFile(fp, filename)
+ try:
+ mpheader = im._getmp()
+ if mpheader[45057] > 1:
+ # 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/contrib/python/Pillow/py3/PIL/JpegPresets.py b/contrib/python/Pillow/py3/PIL/JpegPresets.py
new file mode 100644
index 00000000000..a678e248e9a
--- /dev/null
+++ b/contrib/python/Pillow/py3/PIL/JpegPresets.py
@@ -0,0 +1,240 @@
+"""
+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
+
+"""
+
+# 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/contrib/python/Pillow/py3/PIL/McIdasImagePlugin.py b/contrib/python/Pillow/py3/PIL/McIdasImagePlugin.py
new file mode 100644
index 00000000000..bb79e71de5d
--- /dev/null
+++ b/contrib/python/Pillow/py3/PIL/McIdasImagePlugin.py
@@ -0,0 +1,75 @@
+#
+# 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.
+#
+
+import struct
+
+from . import Image, ImageFile
+
+
+def _accept(s):
+ return s[:8] == 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):
+ # parse area file directory
+ 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] + list(struct.unpack("!64i", s))
+
+ # get mode
+ if w[11] == 1:
+ mode = rawmode = "L"
+ elif w[11] == 2:
+ # FIXME: add memory map support
+ mode = "I"
+ 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 = [("raw", (0, 0) + self.size, offset, (rawmode, stride, 1))]
+
+
+# --------------------------------------------------------------------
+# registry
+
+Image.register_open(McIdasImageFile.format, McIdasImageFile, _accept)
+
+# no default extension
diff --git a/contrib/python/Pillow/py3/PIL/MicImagePlugin.py b/contrib/python/Pillow/py3/PIL/MicImagePlugin.py
new file mode 100644
index 00000000000..801318930d5
--- /dev/null
+++ b/contrib/python/Pillow/py3/PIL/MicImagePlugin.py
@@ -0,0 +1,103 @@
+#
+# 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.
+#
+
+
+import olefile
+
+from . import Image, TiffImagePlugin
+
+#
+# --------------------------------------------------------------------
+
+
+def _accept(prefix):
+ return prefix[:8] == 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):
+ # 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 = []
+ for path in self.ole.listdir():
+ if path[1:] and path[0][-4:] == ".ACI" and path[1] == "Image":
+ self.images.append(path)
+
+ # 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 = None
+ self._n_frames = len(self.images)
+ self.is_animated = self._n_frames > 1
+
+ self.seek(0)
+
+ def seek(self, frame):
+ if not self._seek_check(frame):
+ return
+ try:
+ filename = self.images[frame]
+ except IndexError as e:
+ msg = "no such frame"
+ raise EOFError(msg) from e
+
+ self.fp = self.ole.openstream(filename)
+
+ TiffImagePlugin.TiffImageFile._open(self)
+
+ self.frame = frame
+
+ def tell(self):
+ return self.frame
+
+ def close(self):
+ self.ole.close()
+ super().close()
+
+ def __exit__(self, *args):
+ self.ole.close()
+ super().__exit__()
+
+
+#
+# --------------------------------------------------------------------
+
+Image.register_open(MicImageFile.format, MicImageFile, _accept)
+
+Image.register_extension(MicImageFile.format, ".mic")
diff --git a/contrib/python/Pillow/py3/PIL/MpegImagePlugin.py b/contrib/python/Pillow/py3/PIL/MpegImagePlugin.py
new file mode 100644
index 00000000000..bfa88fe99c4
--- /dev/null
+++ b/contrib/python/Pillow/py3/PIL/MpegImagePlugin.py
@@ -0,0 +1,82 @@
+#
+# 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 . import Image, ImageFile
+from ._binary import i8
+
+#
+# Bitstream parser
+
+
+class BitStream:
+ def __init__(self, fp):
+ self.fp = fp
+ self.bits = 0
+ self.bitbuffer = 0
+
+ def next(self):
+ return i8(self.fp.read(1))
+
+ def peek(self, bits):
+ while self.bits < bits:
+ c = self.next()
+ if c < 0:
+ self.bits = 0
+ continue
+ self.bitbuffer = (self.bitbuffer << 8) + c
+ self.bits += 8
+ return self.bitbuffer >> (self.bits - bits) & (1 << bits) - 1
+
+ def skip(self, bits):
+ 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):
+ v = self.peek(bits)
+ self.bits = self.bits - bits
+ return v
+
+
+##
+# 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):
+ 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)
+
+Image.register_extensions(MpegImageFile.format, [".mpg", ".mpeg"])
+
+Image.register_mime(MpegImageFile.format, "video/mpeg")
diff --git a/contrib/python/Pillow/py3/PIL/MpoImagePlugin.py b/contrib/python/Pillow/py3/PIL/MpoImagePlugin.py
new file mode 100644
index 00000000000..f9261c77d68
--- /dev/null
+++ b/contrib/python/Pillow/py3/PIL/MpoImagePlugin.py
@@ -0,0 +1,197 @@
+#
+# 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.
+#
+
+import itertools
+import os
+import struct
+
+from . import (
+ ExifTags,
+ Image,
+ ImageFile,
+ ImageSequence,
+ JpegImagePlugin,
+ TiffImagePlugin,
+)
+from ._binary import i16be as i16
+from ._binary import o32le
+
+# def _accept(prefix):
+# return JpegImagePlugin._accept(prefix)
+
+
+def _save(im, fp, filename):
+ JpegImagePlugin._save(im, fp, filename)
+
+
+def _save_all(im, fp, filename):
+ append_images = im.encoderinfo.get("append_images", [])
+ if not append_images:
+ try:
+ animated = im.is_animated
+ except AttributeError:
+ animated = False
+ if not animated:
+ _save(im, fp, filename)
+ return
+
+ mpf_offset = 28
+ offsets = []
+ for imSequence in itertools.chain([im], append_images):
+ for im_frame in ImageSequence.Iterator(imSequence):
+ if not offsets:
+ # APP2 marker
+ im_frame.encoderinfo["extra"] = (
+ b"\xFF\xE2" + struct.pack(">H", 6 + 82) + b"MPF\0" + b" " * 82
+ )
+ 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:
+ im_frame.save(fp, "JPEG")
+ 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("<LLLHH", mptype, size, data_offset, 0, 0)
+ if i == 0:
+ data_offset -= mpf_offset
+ data_offset += size
+ ifd[0xB002] = mpentries
+
+ fp.seek(mpf_offset)
+ fp.write(b"II\x2A\x00" + o32le(8) + ifd.tobytes(8))
+ fp.seek(0, os.SEEK_END)
+
+
+##
+# Image plugin for MPO images.
+
+
+class MpoImageFile(JpegImagePlugin.JpegImageFile):
+ format = "MPO"
+ format_description = "MPO (CIPA DC-007)"
+ _close_exclusive_fp_after_loading = False
+
+ def _open(self):
+ 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=None):
+ self._initial_size = self.size
+ self.mpinfo = mpheader if mpheader is not None else self._getmp()
+ 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):
+ self._fp.seek(pos)
+
+ def seek(self, frame):
+ if not self._seek_check(frame):
+ return
+ self.fp = self._fp
+ self.offset = self.__mpoffsets[frame]
+
+ self.fp.seek(self.offset + 2) # skip SOI marker
+ segment = self.fp.read(2)
+ if not segment:
+ msg = "No data found for frame"
+ raise ValueError(msg)
+ self._size = self._initial_size
+ if i16(segment) == 0xFFE1: # APP1
+ n = i16(self.fp.read(2)) - 2
+ self.info["exif"] = ImageFile._safe_read(self.fp, n)
+ self._reload_exif()
+
+ mptype = self.mpinfo[0xB002][frame]["Attribute"]["MPType"]
+ if mptype.startswith("Large Thumbnail"):
+ exif = self.getexif().get_ifd(ExifTags.IFD.Exif)
+ if 40962 in exif and 40963 in exif:
+ self._size = (exif[40962], exif[40963])
+ elif "exif" in self.info:
+ del self.info["exif"]
+ self._reload_exif()
+
+ self.tile = [("jpeg", (0, 0) + self.size, self.offset, (self.mode, ""))]
+ self.__frame = frame
+
+ def tell(self):
+ return self.__frame
+
+ @staticmethod
+ def adopt(jpeg_instance, mpheader=None):
+ """
+ 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
+ jpeg_instance._after_jpeg_open(mpheader)
+ return jpeg_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/contrib/python/Pillow/py3/PIL/MspImagePlugin.py b/contrib/python/Pillow/py3/PIL/MspImagePlugin.py
new file mode 100644
index 00000000000..3f3609f1c20
--- /dev/null
+++ b/contrib/python/Pillow/py3/PIL/MspImagePlugin.py
@@ -0,0 +1,194 @@
+#
+# 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
+
+import io
+import struct
+
+from . import Image, ImageFile
+from ._binary import i16le as i16
+from ._binary import o16le as o16
+
+#
+# read MSP files
+
+
+def _accept(prefix):
+ return prefix[:4] in [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):
+ # Header
+ 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[:4] == b"DanM":
+ self.tile = [("raw", (0, 0) + self.size, 32, ("1", 0, 1))]
+ else:
+ self.tile = [("MSP", (0, 0) + self.size, 32, None)]
+
+
+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):
+ 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", 0, 1))
+
+ return -1, 0
+
+
+Image.register_decoder("MSP", MspDecoder)
+
+
+#
+# write MSP files (uncompressed only)
+
+
+def _save(im, fp, filename):
+ 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, [("raw", (0, 0) + im.size, 32, ("1", 0, 1))])
+
+
+#
+# registry
+
+Image.register_open(MspImageFile.format, MspImageFile, _accept)
+Image.register_save(MspImageFile.format, _save)
+
+Image.register_extension(MspImageFile.format, ".msp")
diff --git a/contrib/python/Pillow/py3/PIL/PSDraw.py b/contrib/python/Pillow/py3/PIL/PSDraw.py
new file mode 100644
index 00000000000..13b3048f67e
--- /dev/null
+++ b/contrib/python/Pillow/py3/PIL/PSDraw.py
@@ -0,0 +1,229 @@
+#
+# 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.
+#
+
+import sys
+
+from . import EpsImagePlugin
+
+##
+# Simple PostScript graphics interface.
+
+
+class PSDraw:
+ """
+ Sets up printing to the given file. If ``fp`` is omitted,
+ ``sys.stdout.buffer`` or ``sys.stdout`` is assumed.
+ """
+
+ def __init__(self, fp=None):
+ if not fp:
+ try:
+ fp = sys.stdout.buffer
+ except AttributeError:
+ fp = sys.stdout
+ self.fp = fp
+
+ def begin_document(self, id=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 = {}
+
+ def end_document(self):
+ """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, size):
+ """
+ Selects which font to use.
+
+ :param font: A PostScript font name
+ :param size: Size in points.
+ """
+ font = bytes(font, "UTF-8")
+ if font not in self.isofont:
+ # reencode font
+ self.fp.write(b"/PSDraw-%s ISOLatin1Encoding /%s E\n" % (font, font))
+ self.isofont[font] = 1
+ # rough
+ self.fp.write(b"/F0 %d /PSDraw-%s F\n" % (size, font))
+
+ def line(self, xy0, xy1):
+ """
+ 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):
+ """
+ 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, text):
+ """
+ Draws text at the given position. You must use
+ :py:meth:`~PIL.PSDraw.PSDraw.setfont` before calling this method.
+ """
+ text = bytes(text, "UTF-8")
+ text = b"\\(".join(text.split(b"("))
+ text = b"\\)".join(text.split(b")"))
+ xy += (text,)
+ self.fp.write(b"%d %d M (%s) S\n" % xy)
+
+ def image(self, box, im, dpi=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 # greyscale
+ # 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, None, 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/contrib/python/Pillow/py3/PIL/PaletteFile.py b/contrib/python/Pillow/py3/PIL/PaletteFile.py
new file mode 100644
index 00000000000..4a2c497fc49
--- /dev/null
+++ b/contrib/python/Pillow/py3/PIL/PaletteFile.py
@@ -0,0 +1,51 @@
+#
+# 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 ._binary import o8
+
+
+class PaletteFile:
+ """File handler for Teragon-style palette files."""
+
+ rawmode = "RGB"
+
+ def __init__(self, fp):
+ self.palette = [(i, i, i) for i in range(256)]
+
+ while True:
+ s = fp.readline()
+
+ if not s:
+ break
+ if s[:1] == 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:
+ self.palette[i] = o8(r) + o8(g) + o8(b)
+
+ self.palette = b"".join(self.palette)
+
+ def getpalette(self):
+ return self.palette, self.rawmode
diff --git a/contrib/python/Pillow/py3/PIL/PalmImagePlugin.py b/contrib/python/Pillow/py3/PIL/PalmImagePlugin.py
new file mode 100644
index 00000000000..a88a907917d
--- /dev/null
+++ b/contrib/python/Pillow/py3/PIL/PalmImagePlugin.py
@@ -0,0 +1,225 @@
+#
+# The Python Imaging Library.
+# $Id$
+#
+
+##
+# Image plugin for Palm pixmap images (output only).
+##
+
+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.new("L", (1, len(_Palm8BitColormapValues)))
+ image.putdata(list(range(len(_Palm8BitColormapValues))))
+ palettedata = ()
+ 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, fp, filename):
+ if im.mode == "P":
+ # we assume this is a color Palm image with the standard colormap,
+ # unless the "info" dict has a "custom-colormap" field
+
+ 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 greyscale from white (0) to black (1)
+ bpp = im.encoderinfo["bpp"]
+ im = im.point(
+ lambda x, shift=8 - bpp, maxval=(1 << bpp) - 1: 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"]
+ im = im.point(lambda x, maxval=(1 << bpp) - 1: 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 = "P;" + str(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" and "custom-colormap" in im.info:
+ flags = flags & _FLAGS["custom-colormap"]
+ colormapsize = 4 * 256 + 2
+ colormapmode = im.palette.mode
+ colormap = im.getdata().getpalette()
+ 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 > 0:
+ fp.write(o16b(256))
+ for i in range(256):
+ fp.write(o8(i))
+ if colormapmode == "RGB":
+ fp.write(
+ o8(colormap[3 * i])
+ + o8(colormap[3 * i + 1])
+ + o8(colormap[3 * i + 2])
+ )
+ elif colormapmode == "RGBA":
+ fp.write(
+ o8(colormap[4 * i])
+ + o8(colormap[4 * i + 1])
+ + o8(colormap[4 * i + 2])
+ )
+
+ # now convert data to raw form
+ ImageFile._save(im, fp, [("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/contrib/python/Pillow/py3/PIL/PcdImagePlugin.py b/contrib/python/Pillow/py3/PIL/PcdImagePlugin.py
new file mode 100644
index 00000000000..c7cbca8c5d7
--- /dev/null
+++ b/contrib/python/Pillow/py3/PIL/PcdImagePlugin.py
@@ -0,0 +1,62 @@
+#
+# 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 . 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):
+ # rough
+ self.fp.seek(2048)
+ s = self.fp.read(2048)
+
+ if s[:4] != 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 = [("pcd", (0, 0) + self.size, 96 * 2048, None)]
+
+ def load_end(self):
+ 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/contrib/python/Pillow/py3/PIL/PcfFontFile.py b/contrib/python/Pillow/py3/PIL/PcfFontFile.py
new file mode 100644
index 00000000000..8db5822fe7d
--- /dev/null
+++ b/contrib/python/Pillow/py3/PIL/PcfFontFile.py
@@ -0,0 +1,256 @@
+#
+# 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.
+#
+
+import io
+
+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 = [
+ 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, o):
+ 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, charset_encoding="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):
+ 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):
+ #
+ # font properties
+
+ properties = {}
+
+ fp, format, i16, i32 = self._getformat(PCF_PROPERTIES)
+
+ nprops = i32(fp.read(4))
+
+ # read property description
+ p = []
+ for i in range(nprops):
+ p.append((i32(fp.read(4)), i8(fp.read(1)), i32(fp.read(4))))
+ 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:
+ k = sz(data, k)
+ if s:
+ v = sz(data, v)
+ properties[k] = v
+
+ return properties
+
+ def _load_metrics(self):
+ #
+ # font metrics
+
+ metrics = []
+
+ 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):
+ #
+ # bitmap data
+
+ bitmaps = []
+
+ 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 = []
+ for i in range(nbitmaps):
+ offsets.append(i32(fp.read(4)))
+
+ bitmap_sizes = []
+ for i in range(4):
+ bitmap_sizes.append(i32(fp.read(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"
+
+ 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):
+ 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 = [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/contrib/python/Pillow/py3/PIL/PcxImagePlugin.py b/contrib/python/Pillow/py3/PIL/PcxImagePlugin.py
new file mode 100644
index 00000000000..854d9e83ee7
--- /dev/null
+++ b/contrib/python/Pillow/py3/PIL/PcxImagePlugin.py
@@ -0,0 +1,221 @@
+#
+# 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.
+#
+
+import io
+import logging
+
+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):
+ 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):
+ # header
+ s = self.fp.read(128)
+ 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)
+
+ # 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 = "P;%dL" % planes
+ 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 greyscale
+ 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:])
+ self.fp.seek(128)
+
+ 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 = [("pcx", bbox, self.fp.tell(), (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, fp, filename):
+ 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, [("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":
+ # greyscale 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/contrib/python/Pillow/py3/PIL/PdfImagePlugin.py b/contrib/python/Pillow/py3/PIL/PdfImagePlugin.py
new file mode 100644
index 00000000000..09fc0c7e6ce
--- /dev/null
+++ b/contrib/python/Pillow/py3/PIL/PdfImagePlugin.py
@@ -0,0 +1,302 @@
+#
+# 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).
+##
+
+import io
+import math
+import os
+import time
+
+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, fp, filename):
+ _save(im, fp, filename, save_all=True)
+
+
+##
+# (Internal) Image save plugin for the PDF format.
+
+
+def _write_image(im, filename, existing_pdf, image_refs):
+ # 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 = {"BitsPerComponent": 8}
+ if im.mode == "1":
+ if features.check("libtiff"):
+ filter = "CCITTFaxDecode"
+ dict_obj["BitsPerComponent"] = 1
+ params = PdfParser.PdfArray(
+ [
+ PdfParser.PdfDict(
+ {
+ "K": -1,
+ "BlackIs1": True,
+ "Columns": width,
+ "Rows": height,
+ }
+ )
+ ]
+ )
+ else:
+ filter = "DCTDecode"
+ dict_obj["ColorSpace"] = PdfParser.PdfName("DeviceGray")
+ procset = "ImageB" # grayscale
+ elif im.mode == "L":
+ filter = "DCTDecode"
+ # params = f"<< /Predictor 15 /Columns {width-2} >>"
+ dict_obj["ColorSpace"] = PdfParser.PdfName("DeviceGray")
+ procset = "ImageB" # grayscale
+ elif im.mode == "LA":
+ filter = "JPXDecode"
+ # params = f"<< /Predictor 15 /Columns {width-2} >>"
+ procset = "ImageB" # grayscale
+ dict_obj["SMaskInData"] = 1
+ elif im.mode == "P":
+ filter = "ASCIIHexDecode"
+ palette = im.getpalette()
+ dict_obj["ColorSpace"] = [
+ PdfParser.PdfName("Indexed"),
+ PdfParser.PdfName("DeviceRGB"),
+ 255,
+ 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":
+ filter = "DCTDecode"
+ dict_obj["ColorSpace"] = PdfParser.PdfName("DeviceRGB")
+ procset = "ImageC" # color images
+ elif im.mode == "RGBA":
+ filter = "JPXDecode"
+ procset = "ImageC" # color images
+ dict_obj["SMaskInData"] = 1
+ elif im.mode == "CMYK":
+ 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 filter == "ASCIIHexDecode":
+ ImageFile._save(im, op, [("hex", (0, 0) + im.size, 0, im.mode)])
+ elif filter == "CCITTFaxDecode":
+ im.save(
+ op,
+ "TIFF",
+ compression="group4",
+ # use a single strip
+ strip_size=math.ceil(width / 8) * height,
+ )
+ elif filter == "DCTDecode":
+ Image.SAVE["JPEG"](im, op, filename)
+ elif filter == "JPXDecode":
+ del dict_obj["BitsPerComponent"]
+ Image.SAVE["JPEG2000"](im, op, filename)
+ else:
+ msg = f"unsupported PDF filter ({filter})"
+ raise ValueError(msg)
+
+ stream = op.getvalue()
+ if filter == "CCITTFaxDecode":
+ stream = stream[8:]
+ filter = PdfParser.PdfArray([PdfParser.PdfName(filter)])
+ else:
+ filter = PdfParser.PdfName(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, fp, filename, save_all=False):
+ is_appending = im.encoderinfo.get("append", False)
+ if is_appending:
+ existing_pdf = PdfParser.PdfParser(f=fp, filename=filename, mode="r+b")
+ else:
+ existing_pdf = PdfParser.PdfParser(f=fp, filename=filename, 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:
+ try:
+ im_number_of_pages = im.n_frames
+ except AttributeError:
+ # Image format does not have n_frames.
+ # It is a single frame image
+ pass
+ 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(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/contrib/python/Pillow/py3/PIL/PdfParser.py b/contrib/python/Pillow/py3/PIL/PdfParser.py
new file mode 100644
index 00000000000..dc1012f54d3
--- /dev/null
+++ b/contrib/python/Pillow/py3/PIL/PdfParser.py
@@ -0,0 +1,996 @@
+import calendar
+import codecs
+import collections
+import mmap
+import os
+import re
+import time
+import zlib
+
+
+# see 7.9.2.2 Text String Type on page 86 and D.3 PDFDocEncoding Character Set
+# on page 656
+def encode_text(s):
+ 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):
+ 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, error_message):
+ if not condition:
+ raise PdfFormatError(error_message)
+
+
+class IndirectReference(
+ collections.namedtuple("IndirectReferenceTuple", ["object_id", "generation"])
+):
+ def __str__(self):
+ return "%s %s R" % self
+
+ def __bytes__(self):
+ return self.__str__().encode("us-ascii")
+
+ def __eq__(self, other):
+ return (
+ other.__class__ is self.__class__
+ and other.object_id == self.object_id
+ and other.generation == self.generation
+ )
+
+ def __ne__(self, other):
+ return not (self == other)
+
+ def __hash__(self):
+ return hash((self.object_id, self.generation))
+
+
+class IndirectObjectDef(IndirectReference):
+ def __str__(self):
+ return "%s %s obj" % self
+
+
+class XrefTable:
+ def __init__(self):
+ self.existing_entries = {} # object ID => (offset, generation)
+ self.new_entries = {} # object ID => (offset, generation)
+ self.deleted_entries = {0: 65536} # object ID => generation
+ self.reading_finished = False
+
+ def __setitem__(self, key, value):
+ 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):
+ try:
+ return self.new_entries[key]
+ except KeyError:
+ return self.existing_entries[key]
+
+ def __delitem__(self, key):
+ 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 = (
+ "object ID " + str(key) + " cannot be deleted because it doesn't exist"
+ )
+ raise IndexError(msg)
+
+ def __contains__(self, key):
+ return key in self.existing_entries or key in self.new_entries
+
+ def __len__(self):
+ return len(
+ set(self.existing_entries.keys())
+ | set(self.new_entries.keys())
+ | set(self.deleted_entries.keys())
+ )
+
+ def keys(self):
+ return (
+ set(self.existing_entries.keys()) - set(self.deleted_entries.keys())
+ ) | set(self.new_entries.keys())
+
+ def write(self, f):
+ 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 = 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 = None
+ 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:
+ def __init__(self, name):
+ 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):
+ return self.name.decode("us-ascii")
+
+ def __eq__(self, other):
+ return (
+ isinstance(other, PdfName) and other.name == self.name
+ ) or other == self.name
+
+ def __hash__(self):
+ return hash(self.name)
+
+ def __repr__(self):
+ return f"PdfName({repr(self.name)})"
+
+ @classmethod
+ def from_pdf_stream(cls, data):
+ return cls(PdfParser.interpret_name(data))
+
+ allowed_chars = set(range(33, 127)) - {ord(c) for c in "#%/()<>[]{}"}
+
+ def __bytes__(self):
+ 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):
+ def __bytes__(self):
+ return b"[ " + b" ".join(pdf_repr(x) for x in self) + b" ]"
+
+
+class PdfDict(collections.UserDict):
+ def __setattr__(self, key, value):
+ if key == "data":
+ collections.UserDict.__setattr__(self, key, value)
+ else:
+ self[key.encode("us-ascii")] = value
+
+ def __getattr__(self, key):
+ 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):
+ 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):
+ self.data = data
+
+ def __bytes__(self):
+ return b"<%s>" % b"".join(b"%02X" % b for b in self.data)
+
+
+class PdfStream:
+ def __init__(self, dictionary, buf):
+ self.dictionary = dictionary
+ self.buf = buf
+
+ def decode(self):
+ try:
+ filter = self.dictionary.Filter
+ except AttributeError:
+ return self.buf
+ if filter == b"FlateDecode":
+ try:
+ expected_length = self.dictionary.DL
+ except AttributeError:
+ expected_length = self.dictionary.Length
+ return zlib.decompress(self.buf, bufsize=int(expected_length))
+ else:
+ msg = f"stream filter {repr(self.dictionary.Filter)} unknown/unsupported"
+ raise NotImplementedError(msg)
+
+
+def pdf_repr(x):
+ 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=None, f=None, buf=None, start_offset=0, mode="rb"):
+ 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 = 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 = 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 = {}
+ if 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 = {}
+ self.pages = []
+ self.orig_pages = []
+ self.pages_ref = None
+ self.last_xref_section_offset = None
+ self.trailer_dict = {}
+ self.xref_table = XrefTable()
+ self.xref_table.reading_finished = True
+ if f:
+ self.seek_end()
+
+ def __enter__(self):
+ return self
+
+ def __exit__(self, exc_type, exc_value, traceback):
+ self.close()
+ return False # do not suppress exceptions
+
+ def start_writing(self):
+ self.close_buf()
+ self.seek_end()
+
+ def close_buf(self):
+ try:
+ self.buf.close()
+ except AttributeError:
+ pass
+ self.buf = None
+
+ def close(self):
+ 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):
+ self.f.seek(0, os.SEEK_END)
+
+ def write_header(self):
+ self.f.write(b"%PDF-1.4\n")
+
+ def write_comment(self, s):
+ self.f.write(f"% {s}\n".encode())
+
+ def write_catalog(self):
+ 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):
+ 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=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 = {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, *objs, **dict_obj):
+ if isinstance(ref, int):
+ ref = self.pages[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(ref, *objs, **dict_obj)
+
+ def write_obj(self, ref, *objs, **dict_obj):
+ 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):
+ 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):
+ 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):
+ self.file_size_total = len(self.buf)
+ self.file_size_this = self.file_size_total - self.start_offset
+ self.read_trailer()
+ self.root_ref = self.trailer_dict[b"Root"]
+ 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(b"Pages" in self.root, "/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"]
+ 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=None):
+ 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):
+ 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, "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
+ 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):
+ 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, "previous trailer not found")
+ 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):
+ 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 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))
+ value, offset = cls.get_value(trailer_data, m.end())
+ trailer[key] = value
+ 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, as_text=False):
+ 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, offset, expect_indirect=None, max_nesting=-1):
+ 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, offset = cls.get_value(data, m.end(), max_nesting=max_nesting - 1)
+ if offset is None:
+ return object, None
+ m = cls.re_indirect_def_end.match(data, offset)
+ check_format_condition(m, "indirect object definition end not found")
+ 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 = {}
+ m = cls.re_dict_end.match(data, offset)
+ while not m:
+ key, offset = cls.get_value(data, offset, max_nesting=max_nesting - 1)
+ if offset is None:
+ return result, None
+ value, offset = cls.get_value(data, offset, max_nesting=max_nesting - 1)
+ result[key] = value
+ if offset is None:
+ return result, None
+ m = cls.re_dict_end.match(data, offset)
+ offset = m.end()
+ m = cls.re_stream_start.match(data, offset)
+ if m:
+ try:
+ stream_len = int(result[b"Length"])
+ except (TypeError, KeyError, ValueError) as e:
+ msg = "bad or missing Length in stream dict (%r)" % result.get(
+ b"Length", None
+ )
+ raise PdfFormatError(msg) from e
+ stream_data = data[m.end() : m.end() + stream_len]
+ m = cls.re_stream_end.match(data, m.end() + stream_len)
+ check_format_condition(m, "stream end not found")
+ offset = m.end()
+ result = PdfStream(PdfDict(result), stream_data)
+ else:
+ result = PdfDict(result)
+ return result, offset
+ m = cls.re_array_start.match(data, offset)
+ if m:
+ offset = m.end()
+ result = []
+ m = cls.re_array_end.match(data, offset)
+ while not m:
+ value, offset = cls.get_value(data, offset, max_nesting=max_nesting - 1)
+ result.append(value)
+ if offset is None:
+ return result, None
+ m = cls.re_array_end.match(data, offset)
+ return result, 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 = "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, offset):
+ 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):
+ subsection_found = False
+ m = self.re_xref_section_start.match(
+ self.buf, xref_section_offset + self.start_offset
+ )
+ check_format_condition(m, "xref section start not found")
+ 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, "xref entry not found")
+ 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, max_nesting=-1):
+ 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}",
+ )
+ 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=None):
+ if node is None:
+ node = self.page_tree_root
+ check_format_condition(
+ node[b"Type"] == b"Pages", "/Type of page tree node is not /Pages"
+ )
+ pages = []
+ for kid in 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/contrib/python/Pillow/py3/PIL/PixarImagePlugin.py b/contrib/python/Pillow/py3/PIL/PixarImagePlugin.py
new file mode 100644
index 00000000000..850272311de
--- /dev/null
+++ b/contrib/python/Pillow/py3/PIL/PixarImagePlugin.py
@@ -0,0 +1,69 @@
+#
+# 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 <[email protected]>. 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 . import Image, ImageFile
+from ._binary import i16le as i16
+
+#
+# helpers
+
+
+def _accept(prefix):
+ return prefix[:4] == 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):
+ # assuming a 4-byte magic label
+ 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 = [("raw", (0, 0) + self.size, 1024, (self.mode, 0, 1))]
+
+
+#
+# --------------------------------------------------------------------
+
+Image.register_open(PixarImageFile.format, PixarImageFile, _accept)
+
+Image.register_extension(PixarImageFile.format, ".pxr")
diff --git a/contrib/python/Pillow/py3/PIL/PngImagePlugin.py b/contrib/python/Pillow/py3/PIL/PngImagePlugin.py
new file mode 100644
index 00000000000..5e5a8cf6a2d
--- /dev/null
+++ b/contrib/python/Pillow/py3/PIL/PngImagePlugin.py
@@ -0,0 +1,1452 @@
+#
+# 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.
+#
+
+import itertools
+import logging
+import re
+import struct
+import warnings
+import zlib
+from enum import IntEnum
+
+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
+
+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
+ # Greyscale
+ (1, 0): ("1", "1"),
+ (2, 0): ("L", "L;2"),
+ (4, 0): ("L", "L;4"),
+ (8, 0): ("L", "L"),
+ (16, 0): ("I", "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"),
+ # Greyscale 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<png-text>`.
+"""
+MAX_TEXT_MEMORY = 64 * MAX_TEXT_CHUNK
+"""
+Set the maximum total text chunk size.
+See :ref:`Text in PNG File Format<png-text>`.
+"""
+
+
+# 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<apng-saving>`.
+ """
+ OP_BACKGROUND = 1
+ """
+ This frame’s modified region is cleared to fully transparent black before rendering
+ the next frame.
+ See :ref:`Saving APNG sequences<apng-saving>`.
+ """
+ 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-saving>`.
+ """
+
+
+# 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<apng-saving>`.
+ """
+ OP_OVER = 1
+ """
+ This frame should be alpha composited with the previous output image contents.
+ See :ref:`Saving APNG sequences<apng-saving>`.
+ """
+
+
+def _safe_zlib_decompress(s):
+ dobj = zlib.decompressobj()
+ plaintext = dobj.decompress(s, MAX_TEXT_CHUNK)
+ if dobj.unconsumed_tail:
+ msg = "Decompressed Data Too Large"
+ raise ValueError(msg)
+ return plaintext
+
+
+def _crc32(data, seed=0):
+ return zlib.crc32(data, seed) & 0xFFFFFFFF
+
+
+# --------------------------------------------------------------------
+# Support classes. Suitable for PNG and related formats like MNG etc.
+
+
+class ChunkStream:
+ def __init__(self, fp):
+ self.fp = fp
+ self.queue = []
+
+ def read(self):
+ """Fetch a new chunk. Returns header information."""
+ cid = 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):
+ return self
+
+ def __exit__(self, *args):
+ self.close()
+
+ def close(self):
+ self.queue = self.fp = None
+
+ def push(self, cid, pos, length):
+ self.queue.append((cid, pos, length))
+
+ def call(self, cid, pos, length):
+ """Call the appropriate chunk handler"""
+
+ logger.debug("STREAM %r %s %s", cid, pos, length)
+ return getattr(self, "chunk_" + cid.decode("ascii"))(pos, length)
+
+ def crc(self, cid, data):
+ """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
+
+ 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, data):
+ """Read checksum"""
+
+ self.fp.read(4)
+
+ def verify(self, endchunk=b"IEND"):
+ # Simple approach; just calculate checksum for all remaining
+ # blocks. Must be called directly after open.
+
+ cids = []
+
+ 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
+
+ """
+
+ @staticmethod
+ def __new__(cls, text, lang=None, tkey=None):
+ """
+ :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):
+ self.chunks = []
+
+ def add(self, cid, data, after_idat=False):
+ """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
+
+ """
+
+ chunk = [cid, data]
+ if after_idat:
+ chunk.append(True)
+ self.chunks.append(tuple(chunk))
+
+ def add_itxt(self, key, value, lang="", tkey="", zip=False):
+ """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, value, zip=False):
+ """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, value.tkey, 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 PngStream(ChunkStream):
+ def __init__(self, fp):
+ super().__init__(fp)
+
+ # local copies of Image attributes
+ self.im_info = {}
+ self.im_text = {}
+ self.im_size = (0, 0)
+ self.im_mode = None
+ self.im_tile = None
+ self.im_palette = None
+ self.im_custom_mimetype = None
+ self.im_n_frames = None
+ self._seq_num = None
+ self.rewind_state = None
+
+ self.text_memory = 0
+
+ def check_text_memory(self, chunklen):
+ 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):
+ self.rewind_state = {
+ "info": self.im_info.copy(),
+ "tile": self.im_tile,
+ "seq_num": self._seq_num,
+ }
+
+ def rewind(self):
+ self.im_info = self.rewind_state["info"]
+ self.im_tile = self.rewind_state["tile"]
+ self._seq_num = self.rewind_state["seq_num"]
+
+ def chunk_iCCP(self, pos, length):
+ # ICC profile
+ 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])
+ logger.debug("Compression method %s", s[i])
+ comp_method = s[i]
+ 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, length):
+ # image header
+ 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, length):
+ # image data
+ if "bbox" in self.im_info:
+ 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 = [("zip", (0, 0) + self.im_size, pos, self.im_rawmode)]
+ self.im_tile = tile
+ self.im_idat = length
+ raise EOFError
+
+ def chunk_IEND(self, pos, length):
+ # end of PNG image
+ raise EOFError
+
+ def chunk_PLTE(self, pos, length):
+ # palette
+ s = ImageFile._safe_read(self.fp, length)
+ if self.im_mode == "P":
+ self.im_palette = "RGB", s
+ return s
+
+ def chunk_tRNS(self, pos, length):
+ # transparency
+ 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"):
+ 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, length):
+ # gamma setting
+ s = ImageFile._safe_read(self.fp, length)
+ self.im_info["gamma"] = i32(s) / 100000.0
+ return s
+
+ def chunk_cHRM(self, pos, length):
+ # chromaticity, 8 unsigned ints, actual value is scaled by 100,000
+ # WP x,y, Red x,y, Green x,y Blue x,y
+
+ s = ImageFile._safe_read(self.fp, length)
+ raw_vals = struct.unpack(">%dI" % (len(s) // 4), s)
+ self.im_info["chromaticity"] = tuple(elt / 100000.0 for elt in raw_vals)
+ return s
+
+ def chunk_sRGB(self, pos, length):
+ # srgb rendering intent, 1 byte
+ # 0 perceptual
+ # 1 relative colorimetric
+ # 2 saturation
+ # 3 absolute colorimetric
+
+ 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, length):
+ # pixels per unit
+ 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, length):
+ # text
+ 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 = k.decode("latin-1", "strict")
+ v_str = v.decode("latin-1", "replace")
+
+ self.im_info[k] = v if k == "exif" else v_str
+ self.im_text[k] = v_str
+ self.check_text_memory(len(v_str))
+
+ return s
+
+ def chunk_zTXt(self, pos, length):
+ # compressed text
+ 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 = k.decode("latin-1", "strict")
+ v = v.decode("latin-1", "replace")
+
+ self.im_info[k] = self.im_text[k] = v
+ self.check_text_memory(len(v))
+
+ return s
+
+ def chunk_iTXt(self, pos, length):
+ # international text
+ 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
+ try:
+ k = k.decode("latin-1", "strict")
+ lang = lang.decode("utf-8", "strict")
+ tk = tk.decode("utf-8", "strict")
+ v = v.decode("utf-8", "strict")
+ except UnicodeError:
+ return s
+
+ self.im_info[k] = self.im_text[k] = iTXt(v, lang, tk)
+ self.check_text_memory(len(v))
+
+ return s
+
+ def chunk_eXIf(self, pos, length):
+ 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, length):
+ 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, length):
+ 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, length):
+ 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):
+ return prefix[:8] == _MAGIC
+
+
+##
+# Image plugin for PNG images.
+
+
+class PngImageFile(ImageFile.ImageFile):
+ format = "PNG"
+ format_description = "Portable network graphics"
+
+ def _open(self):
+ 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 = []
+ self.png = 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 = 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):
+ # 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)
+ return self._text
+
+ def verify(self):
+ """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)
+
+ self.png.verify()
+ self.png.close()
+
+ if self._exclusive_fp:
+ self.fp.close()
+ self.fp = None
+
+ def seek(self, frame):
+ 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, rewind=False):
+ if frame == 0:
+ if rewind:
+ self._fp.seek(self.__rewind)
+ self.png.rewind()
+ self.__prepare_idat = self.__rewind_idat
+ self.im = None
+ if self.pyaccess:
+ self.pyaccess = 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")
+ self.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")
+ self.dispose_extent = self.info.get("bbox")
+
+ if not self.tile:
+ raise EOFError
+
+ # 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
+
+ if self.dispose_op == Disposal.OP_PREVIOUS:
+ 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)
+ else:
+ self.dispose = None
+
+ def tell(self):
+ return self.__frame
+
+ def load_prepare(self):
+ """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):
+ """internal: read more image data"""
+
+ 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):
+ """internal: finished reading image data"""
+ 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
+ ImageFile._safe_read(self.fp, length)
+ 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:
+ mask = updated.convert("RGBA")
+ self._prev_im.paste(updated, self.dispose_extent, mask)
+ self.im = self._prev_im
+ if self.pyaccess:
+ self.pyaccess = None
+
+ def _getexif(self):
+ 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):
+ if "exif" not in self.info:
+ self.load()
+
+ return super().getexif()
+
+ def getxmp(self):
+ """
+ Returns a dictionary containing the XMP tags.
+ Requires defusedxml to be installed.
+
+ :returns: XMP tags in a dictionary.
+ """
+ return (
+ self._getxmp(self.info["XML:com.adobe.xmp"])
+ if "XML:com.adobe.xmp" in self.info
+ else {}
+ )
+
+
+# --------------------------------------------------------------------
+# PNG writer
+
+_OUTMODES = {
+ # supported PIL modes, and corresponding rawmodes/bits/color combinations
+ "1": ("1", b"\x01\x00"),
+ "L;1": ("L;1", b"\x01\x00"),
+ "L;2": ("L;2", b"\x02\x00"),
+ "L;4": ("L;4", b"\x04\x00"),
+ "L": ("L", b"\x08\x00"),
+ "LA": ("LA", b"\x08\x04"),
+ "I": ("I;16B", b"\x10\x00"),
+ "I;16": ("I;16B", b"\x10\x00"),
+ "I;16B": ("I;16B", b"\x10\x00"),
+ "P;1": ("P;1", b"\x01\x03"),
+ "P;2": ("P;2", b"\x02\x03"),
+ "P;4": ("P;4", b"\x04\x03"),
+ "P": ("P", b"\x08\x03"),
+ "RGB": ("RGB", b"\x08\x02"),
+ "RGBA": ("RGBA", b"\x08\x06"),
+}
+
+
+def putchunk(fp, cid, *data):
+ """Write a PNG chunk (including CRC field)"""
+
+ data = b"".join(data)
+
+ fp.write(o32(len(data)) + cid)
+ fp.write(data)
+ crc = _crc32(data, _crc32(cid))
+ fp.write(o32(crc))
+
+
+class _idat:
+ # wrap output from the encoder in IDAT chunks
+
+ def __init__(self, fp, chunk):
+ self.fp = fp
+ self.chunk = chunk
+
+ def write(self, data):
+ self.chunk(self.fp, b"IDAT", data)
+
+
+class _fdat:
+ # wrap encoder output in fdAT chunks
+
+ def __init__(self, fp, chunk, seq_num):
+ self.fp = fp
+ self.chunk = chunk
+ self.seq_num = seq_num
+
+ def write(self, data):
+ self.chunk(self.fp, b"fdAT", o32(self.seq_num), data)
+ self.seq_num += 1
+
+
+def _write_multiple_frames(im, fp, chunk, rawmode, default_image, append_images):
+ duration = im.encoderinfo.get("duration", im.info.get("duration", 0))
+ 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 = []
+ frame_count = 0
+ for im_seq in chain:
+ for im_frame in ImageSequence.Iterator(im_seq):
+ if im_frame.mode == rawmode:
+ im_frame = im_frame.copy()
+ else:
+ im_frame = im_frame.convert(rawmode)
+ encoderinfo = im.encoderinfo.copy()
+ if isinstance(duration, (list, tuple)):
+ encoderinfo["duration"] = duration[frame_count]
+ 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")
+ ):
+ previous["encoderinfo"]["duration"] += encoderinfo.get(
+ "duration", duration
+ )
+ continue
+ else:
+ bbox = None
+ if "duration" not in encoderinfo:
+ encoderinfo["duration"] = duration
+ im_frames.append({"im": im_frame, "bbox": bbox, "encoderinfo": encoderinfo})
+
+ # 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 != rawmode:
+ im = im.convert(rawmode)
+ ImageFile._save(im, _idat(fp, chunk), [("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["duration"]))
+ 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,
+ _idat(fp, chunk),
+ [("zip", (0, 0) + im_frame.size, 0, rawmode)],
+ )
+ else:
+ fdat_chunks = _fdat(fp, chunk, seq_num)
+ ImageFile._save(
+ im_frame,
+ fdat_chunks,
+ [("zip", (0, 0) + im_frame.size, 0, rawmode)],
+ )
+ seq_num = fdat_chunks.seq_num
+
+
+def _save_all(im, fp, filename):
+ _save(im, fp, filename, save_all=True)
+
+
+def _save(im, fp, filename, chunk=putchunk, save_all=False):
+ # 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()
+ 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)
+ for mode in ("RGBA", "RGB", "P"):
+ if mode in modes:
+ break
+ else:
+ mode = modes.pop()
+ else:
+ mode = im.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
+ mode = f"{mode};{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, mode = _OUTMODES[mode]
+ except KeyError as e:
+ msg = f"cannot write mode {mode} as PNG"
+ raise OSError(msg) from e
+
+ #
+ # write minimal PNG file
+
+ fp.write(_MAGIC)
+
+ chunk(
+ fp,
+ b"IHDR",
+ o32(im.size[0]), # 0: size
+ o32(im.size[1]),
+ mode, # 8: depth/type
+ b"\0", # 10: compression
+ b"\0", # 11: filter category
+ b"\0", # 12: interlace flag
+ )
+
+ chunks = [b"cHRM", 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 = info_chunk[2:3]
+ 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"):
+ 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)
+
+ if save_all:
+ _write_multiple_frames(im, fp, chunk, rawmode, default_image, append_images)
+ else:
+ ImageFile._save(im, _idat(fp, chunk), [("zip", (0, 0) + 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 = info_chunk[2:3]
+ if after_idat:
+ chunk(fp, cid, data)
+
+ chunk(fp, b"IEND", b"")
+
+ if hasattr(fp, "flush"):
+ fp.flush()
+
+
+# --------------------------------------------------------------------
+# PNG chunk converter
+
+
+def getchunks(im, **params):
+ """Return a list of PNG chunks representing this image."""
+
+ class collector:
+ data = []
+
+ def write(self, data):
+ pass
+
+ def append(self, chunk):
+ self.data.append(chunk)
+
+ def append(fp, cid, *data):
+ data = b"".join(data)
+ crc = o32(_crc32(data, _crc32(cid)))
+ fp.append((cid, data, crc))
+
+ fp = collector()
+
+ try:
+ im.encoderinfo = params
+ _save(im, fp, None, append)
+ finally:
+ del im.encoderinfo
+
+ return fp.data
+
+
+# --------------------------------------------------------------------
+# 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/contrib/python/Pillow/py3/PIL/PpmImagePlugin.py b/contrib/python/Pillow/py3/PIL/PpmImagePlugin.py
new file mode 100644
index 00000000000..e480ab05581
--- /dev/null
+++ b/contrib/python/Pillow/py3/PIL/PpmImagePlugin.py
@@ -0,0 +1,347 @@
+#
+# 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 . 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",
+ # PIL extensions (for test purposes only)
+ b"PyP": "P",
+ b"PyRGBA": "RGBA",
+ b"PyCMYK": "CMYK",
+}
+
+
+def _accept(prefix):
+ return prefix[0:1] == b"P" and prefix[1] in b"0123456y"
+
+
+##
+# Image plugin for PBM, PGM, and PPM images.
+
+
+class PpmImageFile(ImageFile.ImageFile):
+ format = "PPM"
+ format_description = "Pbmplus image"
+
+ def _read_magic(self):
+ 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):
+ 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 = f"Token too long in file header: {token.decode()}"
+ raise ValueError(msg)
+ return token
+
+ def _open(self):
+ magic_number = self._read_magic()
+ try:
+ mode = MODES[magic_number]
+ except KeyError:
+ msg = "not a PPM file"
+ raise SyntaxError(msg)
+
+ 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"
+
+ maxval = None
+ decoder_name = "raw"
+ if magic_number in (b"P1", b"P2", b"P3"):
+ decoder_name = "ppm_plain"
+ for ix in range(3):
+ token = int(self._read_token())
+ if ix == 0: # token is the x size
+ xsize = token
+ elif ix == 1: # token is the y size
+ ysize = token
+ if mode == "1":
+ self._mode = "1"
+ rawmode = "1;I"
+ break
+ else:
+ self._mode = rawmode = mode
+ elif ix == 2: # token is maxval
+ maxval = 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"
+
+ 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, 0, 1) if decoder_name == "raw" else (rawmode, maxval)
+ self._size = xsize, ysize
+ self.tile = [(decoder_name, (0, 0, xsize, ysize), self.fp.tell(), args)]
+
+
+#
+# --------------------------------------------------------------------
+
+
+class PpmPlainDecoder(ImageFile.PyDecoder):
+ _pulls_fd = True
+
+ def _read_block(self):
+ return self.fd.read(ImageFile.SAFEBLOCK)
+
+ def _find_comment_end(self, block, start=0):
+ 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):
+ 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):
+ """
+ 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):
+ 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 = False
+ 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 = False
+
+ 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 > maxval:
+ msg = f"Channel value too large for this mode: {value}"
+ raise ValueError(msg)
+ 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):
+ 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):
+ 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)
+ while len(data) < self.state.xsize * self.state.ysize * bands * out_byte_count:
+ 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, fp, filename):
+ if im.mode == "1":
+ rawmode, head = "1;I", b"P4"
+ elif im.mode == "L":
+ rawmode, head = "L", b"P5"
+ elif im.mode == "I":
+ rawmode, head = "I;16B", b"P5"
+ elif im.mode in ("RGB", "RGBA"):
+ rawmode, head = "RGB", b"P6"
+ 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")
+ ImageFile._save(im, fp, [("raw", (0, 0) + im.size, 0, (rawmode, 0, 1))])
+
+ # ALTERNATIVE: save via builtin debug function
+ # im._dump(filename)
+
+
+#
+# --------------------------------------------------------------------
+
+
+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"])
+
+Image.register_mime(PpmImageFile.format, "image/x-portable-anymap")
diff --git a/contrib/python/Pillow/py3/PIL/PsdImagePlugin.py b/contrib/python/Pillow/py3/PIL/PsdImagePlugin.py
new file mode 100644
index 00000000000..2f019bb8c34
--- /dev/null
+++ b/contrib/python/Pillow/py3/PIL/PsdImagePlugin.py
@@ -0,0 +1,303 @@
+#
+# 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.
+#
+
+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
+
+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):
+ return prefix[:4] == 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):
+ 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 = []
+
+ size = i32(read(4))
+ if size:
+ end = self.fp.tell() + size
+ size = i32(read(4))
+ if size:
+ _layer_data = io.BytesIO(ImageFile._safe_read(self.fp, size))
+ self.layers = _layerinfo(_layer_data, size)
+ self.fp.seek(end)
+ self.n_frames = len(self.layers)
+ self.is_animated = self.n_frames > 1
+
+ #
+ # 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
+
+ def seek(self, layer):
+ if not self._seek_check(layer):
+ return
+
+ # seek to given layer (1..max)
+ try:
+ name, mode, bbox, tile = self.layers[layer - 1]
+ self._mode = mode
+ self.tile = tile
+ self.frame = layer
+ self.fp = self._fp
+ return name, bbox
+ except IndexError as e:
+ msg = "no such layer"
+ raise EOFError(msg) from e
+
+ def tell(self):
+ # return layer number (0=image, 1..max=layers)
+ return self.frame
+
+
+def _layerinfo(fp, ct_bytes):
+ # read layerinfo block
+ layers = []
+
+ def read(size):
+ 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 = i32(read(4))
+ x0 = i32(read(4))
+ y1 = i32(read(4))
+ x1 = i32(read(4))
+
+ # image info
+ mode = []
+ ct_types = i16(read(2))
+ types = list(range(ct_types))
+ if len(types) > 4:
+ continue
+
+ for _ in types:
+ type = i16(read(2))
+
+ if type == 65535:
+ m = "A"
+ else:
+ m = "RGBA"[type]
+
+ mode.append(m)
+ read(4) # size
+
+ # figure out the image mode
+ mode.sort()
+ if mode == ["R"]:
+ mode = "L"
+ elif mode == ["B", "G", "R"]:
+ mode = "RGB"
+ elif mode == ["A", "B", "G", "R"]:
+ mode = "RGBA"
+ else:
+ mode = None # 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
+ for i, (name, mode, bbox) in enumerate(layers):
+ tile = []
+ for m in mode:
+ t = _maketile(fp, m, bbox, 1)
+ if t:
+ tile.extend(t)
+ layers[i] = name, mode, bbox, tile
+
+ return layers
+
+
+def _maketile(file, mode, bbox, channels):
+ tile = None
+ 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
+ tile = []
+ for channel in range(channels):
+ layer = mode[channel]
+ if mode == "CMYK":
+ layer += ";I"
+ tile.append(("raw", bbox, offset, layer))
+ offset = offset + xsize * ysize
+
+ elif compression == 1:
+ #
+ # packbits compression
+ i = 0
+ tile = []
+ bytecount = read(channels * ysize * 2)
+ offset = file.tell()
+ for channel in range(channels):
+ layer = mode[channel]
+ if mode == "CMYK":
+ layer += ";I"
+ tile.append(("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 tile
+
+
+# --------------------------------------------------------------------
+# 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/contrib/python/Pillow/py3/PIL/PyAccess.py b/contrib/python/Pillow/py3/PIL/PyAccess.py
new file mode 100644
index 00000000000..99b46a4a66c
--- /dev/null
+++ b/contrib/python/Pillow/py3/PIL/PyAccess.py
@@ -0,0 +1,363 @@
+#
+# The Python Imaging Library
+# Pillow fork
+#
+# Python implementation of the PixelAccess Object
+#
+# Copyright (c) 1997-2009 by Secret Labs AB. All rights reserved.
+# Copyright (c) 1995-2009 by Fredrik Lundh.
+# Copyright (c) 2013 Eric Soroos
+#
+# See the README file for information on usage and redistribution
+#
+
+# Notes:
+#
+# * Implements the pixel access object following Access.c
+# * Taking only the tuple form, which is used from python.
+# * Fill.c uses the integer form, but it's still going to use the old
+# Access.c implementation.
+#
+
+import logging
+import sys
+
+from ._deprecate import deprecate
+
+try:
+ from cffi import FFI
+
+ defs = """
+ struct Pixel_RGBA {
+ unsigned char r,g,b,a;
+ };
+ struct Pixel_I16 {
+ unsigned char l,r;
+ };
+ """
+ ffi = FFI()
+ ffi.cdef(defs)
+except ImportError as ex:
+ # Allow error import for doc purposes, but error out when accessing
+ # anything in core.
+ from ._util import DeferredError
+
+ FFI = ffi = DeferredError(ex)
+
+logger = logging.getLogger(__name__)
+
+
+class PyAccess:
+ def __init__(self, img, readonly=False):
+ deprecate("PyAccess", 11)
+ vals = dict(img.im.unsafe_ptrs)
+ self.readonly = readonly
+ self.image8 = ffi.cast("unsigned char **", vals["image8"])
+ self.image32 = ffi.cast("int **", vals["image32"])
+ self.image = ffi.cast("unsigned char **", vals["image"])
+ self.xsize, self.ysize = img.im.size
+ self._img = img
+
+ # Keep pointer to im object to prevent dereferencing.
+ self._im = img.im
+ if self._im.mode in ("P", "PA"):
+ self._palette = img.palette
+
+ # Debugging is polluting test traces, only useful here
+ # when hacking on PyAccess
+ # logger.debug("%s", vals)
+ self._post_init()
+
+ def _post_init(self):
+ pass
+
+ def __setitem__(self, xy, color):
+ """
+ Modifies the pixel at x,y. The color is given as a single
+ numerical value for single band images, and a tuple for
+ multi-band images
+
+ :param xy: The pixel coordinate, given as (x, y). See
+ :ref:`coordinate-system`.
+ :param color: The pixel value.
+ """
+ if self.readonly:
+ msg = "Attempt to putpixel a read only image"
+ raise ValueError(msg)
+ (x, y) = xy
+ if x < 0:
+ x = self.xsize + x
+ if y < 0:
+ y = self.ysize + y
+ (x, y) = self.check_xy((x, y))
+
+ if (
+ self._im.mode in ("P", "PA")
+ and isinstance(color, (list, tuple))
+ and len(color) in [3, 4]
+ ):
+ # RGB or RGBA value for a P or PA image
+ if self._im.mode == "PA":
+ alpha = color[3] if len(color) == 4 else 255
+ color = color[:3]
+ color = self._palette.getcolor(color, self._img)
+ if self._im.mode == "PA":
+ color = (color, alpha)
+
+ return self.set_pixel(x, y, color)
+
+ def __getitem__(self, xy):
+ """
+ Returns the pixel at x,y. The pixel is returned as a single
+ value for single band images or a tuple for multiple band
+ images
+
+ :param xy: The pixel coordinate, given as (x, y). See
+ :ref:`coordinate-system`.
+ :returns: a pixel value for single band images, a tuple of
+ pixel values for multiband images.
+ """
+ (x, y) = xy
+ if x < 0:
+ x = self.xsize + x
+ if y < 0:
+ y = self.ysize + y
+ (x, y) = self.check_xy((x, y))
+ return self.get_pixel(x, y)
+
+ putpixel = __setitem__
+ getpixel = __getitem__
+
+ def check_xy(self, xy):
+ (x, y) = xy
+ if not (0 <= x < self.xsize and 0 <= y < self.ysize):
+ msg = "pixel location out of range"
+ raise ValueError(msg)
+ return xy
+
+
+class _PyAccess32_2(PyAccess):
+ """PA, LA, stored in first and last bytes of a 32 bit word"""
+
+ def _post_init(self, *args, **kwargs):
+ self.pixels = ffi.cast("struct Pixel_RGBA **", self.image32)
+
+ def get_pixel(self, x, y):
+ pixel = self.pixels[y][x]
+ return pixel.r, pixel.a
+
+ def set_pixel(self, x, y, color):
+ pixel = self.pixels[y][x]
+ # tuple
+ pixel.r = min(color[0], 255)
+ pixel.a = min(color[1], 255)
+
+
+class _PyAccess32_3(PyAccess):
+ """RGB and friends, stored in the first three bytes of a 32 bit word"""
+
+ def _post_init(self, *args, **kwargs):
+ self.pixels = ffi.cast("struct Pixel_RGBA **", self.image32)
+
+ def get_pixel(self, x, y):
+ pixel = self.pixels[y][x]
+ return pixel.r, pixel.g, pixel.b
+
+ def set_pixel(self, x, y, color):
+ pixel = self.pixels[y][x]
+ # tuple
+ pixel.r = min(color[0], 255)
+ pixel.g = min(color[1], 255)
+ pixel.b = min(color[2], 255)
+ pixel.a = 255
+
+
+class _PyAccess32_4(PyAccess):
+ """RGBA etc, all 4 bytes of a 32 bit word"""
+
+ def _post_init(self, *args, **kwargs):
+ self.pixels = ffi.cast("struct Pixel_RGBA **", self.image32)
+
+ def get_pixel(self, x, y):
+ pixel = self.pixels[y][x]
+ return pixel.r, pixel.g, pixel.b, pixel.a
+
+ def set_pixel(self, x, y, color):
+ pixel = self.pixels[y][x]
+ # tuple
+ pixel.r = min(color[0], 255)
+ pixel.g = min(color[1], 255)
+ pixel.b = min(color[2], 255)
+ pixel.a = min(color[3], 255)
+
+
+class _PyAccess8(PyAccess):
+ """1, L, P, 8 bit images stored as uint8"""
+
+ def _post_init(self, *args, **kwargs):
+ self.pixels = self.image8
+
+ def get_pixel(self, x, y):
+ return self.pixels[y][x]
+
+ def set_pixel(self, x, y, color):
+ try:
+ # integer
+ self.pixels[y][x] = min(color, 255)
+ except TypeError:
+ # tuple
+ self.pixels[y][x] = min(color[0], 255)
+
+
+class _PyAccessI16_N(PyAccess):
+ """I;16 access, native bitendian without conversion"""
+
+ def _post_init(self, *args, **kwargs):
+ self.pixels = ffi.cast("unsigned short **", self.image)
+
+ def get_pixel(self, x, y):
+ return self.pixels[y][x]
+
+ def set_pixel(self, x, y, color):
+ try:
+ # integer
+ self.pixels[y][x] = min(color, 65535)
+ except TypeError:
+ # tuple
+ self.pixels[y][x] = min(color[0], 65535)
+
+
+class _PyAccessI16_L(PyAccess):
+ """I;16L access, with conversion"""
+
+ def _post_init(self, *args, **kwargs):
+ self.pixels = ffi.cast("struct Pixel_I16 **", self.image)
+
+ def get_pixel(self, x, y):
+ pixel = self.pixels[y][x]
+ return pixel.l + pixel.r * 256
+
+ def set_pixel(self, x, y, color):
+ pixel = self.pixels[y][x]
+ try:
+ color = min(color, 65535)
+ except TypeError:
+ color = min(color[0], 65535)
+
+ pixel.l = color & 0xFF # noqa: E741
+ pixel.r = color >> 8
+
+
+class _PyAccessI16_B(PyAccess):
+ """I;16B access, with conversion"""
+
+ def _post_init(self, *args, **kwargs):
+ self.pixels = ffi.cast("struct Pixel_I16 **", self.image)
+
+ def get_pixel(self, x, y):
+ pixel = self.pixels[y][x]
+ return pixel.l * 256 + pixel.r
+
+ def set_pixel(self, x, y, color):
+ pixel = self.pixels[y][x]
+ try:
+ color = min(color, 65535)
+ except Exception:
+ color = min(color[0], 65535)
+
+ pixel.l = color >> 8 # noqa: E741
+ pixel.r = color & 0xFF
+
+
+class _PyAccessI32_N(PyAccess):
+ """Signed Int32 access, native endian"""
+
+ def _post_init(self, *args, **kwargs):
+ self.pixels = self.image32
+
+ def get_pixel(self, x, y):
+ return self.pixels[y][x]
+
+ def set_pixel(self, x, y, color):
+ self.pixels[y][x] = color
+
+
+class _PyAccessI32_Swap(PyAccess):
+ """I;32L/B access, with byteswapping conversion"""
+
+ def _post_init(self, *args, **kwargs):
+ self.pixels = self.image32
+
+ def reverse(self, i):
+ orig = ffi.new("int *", i)
+ chars = ffi.cast("unsigned char *", orig)
+ chars[0], chars[1], chars[2], chars[3] = chars[3], chars[2], chars[1], chars[0]
+ return ffi.cast("int *", chars)[0]
+
+ def get_pixel(self, x, y):
+ return self.reverse(self.pixels[y][x])
+
+ def set_pixel(self, x, y, color):
+ self.pixels[y][x] = self.reverse(color)
+
+
+class _PyAccessF(PyAccess):
+ """32 bit float access"""
+
+ def _post_init(self, *args, **kwargs):
+ self.pixels = ffi.cast("float **", self.image32)
+
+ def get_pixel(self, x, y):
+ return self.pixels[y][x]
+
+ def set_pixel(self, x, y, color):
+ try:
+ # not a tuple
+ self.pixels[y][x] = color
+ except TypeError:
+ # tuple
+ self.pixels[y][x] = color[0]
+
+
+mode_map = {
+ "1": _PyAccess8,
+ "L": _PyAccess8,
+ "P": _PyAccess8,
+ "I;16N": _PyAccessI16_N,
+ "LA": _PyAccess32_2,
+ "La": _PyAccess32_2,
+ "PA": _PyAccess32_2,
+ "RGB": _PyAccess32_3,
+ "LAB": _PyAccess32_3,
+ "HSV": _PyAccess32_3,
+ "YCbCr": _PyAccess32_3,
+ "RGBA": _PyAccess32_4,
+ "RGBa": _PyAccess32_4,
+ "RGBX": _PyAccess32_4,
+ "CMYK": _PyAccess32_4,
+ "F": _PyAccessF,
+ "I": _PyAccessI32_N,
+}
+
+if sys.byteorder == "little":
+ mode_map["I;16"] = _PyAccessI16_N
+ mode_map["I;16L"] = _PyAccessI16_N
+ mode_map["I;16B"] = _PyAccessI16_B
+
+ mode_map["I;32L"] = _PyAccessI32_N
+ mode_map["I;32B"] = _PyAccessI32_Swap
+else:
+ mode_map["I;16"] = _PyAccessI16_L
+ mode_map["I;16L"] = _PyAccessI16_L
+ mode_map["I;16B"] = _PyAccessI16_N
+
+ mode_map["I;32L"] = _PyAccessI32_Swap
+ mode_map["I;32B"] = _PyAccessI32_N
+
+
+def new(img, readonly=False):
+ access_type = mode_map.get(img.mode, None)
+ if not access_type:
+ logger.debug("PyAccess Not Implemented: %s", img.mode)
+ return None
+ return access_type(img, readonly)
diff --git a/contrib/python/Pillow/py3/PIL/QoiImagePlugin.py b/contrib/python/Pillow/py3/PIL/QoiImagePlugin.py
new file mode 100644
index 00000000000..66344faac58
--- /dev/null
+++ b/contrib/python/Pillow/py3/PIL/QoiImagePlugin.py
@@ -0,0 +1,105 @@
+#
+# The Python Imaging Library.
+#
+# QOI support for PIL
+#
+# See the README file for information on usage and redistribution.
+#
+
+import os
+
+from . import Image, ImageFile
+from ._binary import i32be as i32
+from ._binary import o8
+
+
+def _accept(prefix):
+ return prefix[:4] == b"qoif"
+
+
+class QoiImageFile(ImageFile.ImageFile):
+ format = "QOI"
+ format_description = "Quite OK Image"
+
+ def _open(self):
+ if not _accept(self.fp.read(4)):
+ msg = "not a QOI file"
+ raise SyntaxError(msg)
+
+ self._size = tuple(i32(self.fp.read(4)) for i in range(2))
+
+ channels = self.fp.read(1)[0]
+ self._mode = "RGB" if channels == 3 else "RGBA"
+
+ self.fp.seek(1, os.SEEK_CUR) # colorspace
+ self.tile = [("qoi", (0, 0) + self._size, self.fp.tell(), None)]
+
+
+class QoiDecoder(ImageFile.PyDecoder):
+ _pulls_fd = True
+
+ def _add_to_previous_pixels(self, value):
+ 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):
+ self._previously_seen_pixels = {}
+ self._previous_pixel = None
+ self._add_to_previous_pixels(b"".join(o8(i) for i in (0, 0, 0, 255)))
+
+ data = bytearray()
+ bands = Image.getmodebands(self.mode)
+ while len(data) < self.state.xsize * self.state.ysize * bands:
+ byte = self.fd.read(1)[0]
+ if byte == 0b11111110: # QOI_OP_RGB
+ value = 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, (0, 0, 0, 0))
+ elif op == 1: # QOI_OP_DIFF
+ value = (
+ (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,
+ )
+ value += (self._previous_pixel[3],)
+ elif op == 2: # 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 = 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: # QOI_OP_RUN
+ run_length = (byte & 0b00111111) + 1
+ value = self._previous_pixel
+ if bands == 3:
+ value = value[:3]
+ data += value * run_length
+ continue
+ value = b"".join(o8(i) for i in value)
+ self._add_to_previous_pixels(value)
+
+ if bands == 3:
+ value = value[:3]
+ data += value
+ self.set_as_raw(bytes(data))
+ return -1, 0
+
+
+Image.register_open(QoiImageFile.format, QoiImageFile, _accept)
+Image.register_decoder("qoi", QoiDecoder)
+Image.register_extension(QoiImageFile.format, ".qoi")
diff --git a/contrib/python/Pillow/py3/PIL/SgiImagePlugin.py b/contrib/python/Pillow/py3/PIL/SgiImagePlugin.py
new file mode 100644
index 00000000000..acb9ce5a38c
--- /dev/null
+++ b/contrib/python/Pillow/py3/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.
+# <ftp://ftp.sgi.com/graphics/SGIIMAGESPEC>
+#
+#
+# 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.
+#
+
+
+import os
+import struct
+
+from . import Image, ImageFile
+from ._binary import i16be as i16
+from ._binary import o8
+
+
+def _accept(prefix):
+ 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):
+ # HEAD
+ 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)
+
+ # layout
+ layout = bpc, dimension, zsize
+
+ # determine mode from bits/zsize
+ rawmode = ""
+ try:
+ rawmode = MODES[layout]
+ except KeyError:
+ pass
+
+ if rawmode == "":
+ 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 = [
+ ("SGI16", (0, 0) + self.size, headlen, (self.mode, 0, orientation))
+ ]
+ else:
+ self.tile = []
+ offset = headlen
+ for layer in self.mode:
+ self.tile.append(
+ ("raw", (0, 0) + self.size, offset, (layer, 0, orientation))
+ )
+ offset += pagesize
+ elif compression == 1:
+ self.tile = [
+ ("sgi_rle", (0, 0) + self.size, headlen, (rawmode, orientation, bpc))
+ ]
+
+
+def _save(im, fp, filename):
+ if im.mode != "RGB" and im.mode != "RGBA" and im.mode != "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
+
+ # Number of dimensions (x,y,z)
+ dim = 3
+ # X Dimension = width / Y Dimension = height
+ x, y = im.size
+ if im.mode == "L" and y == 1:
+ dim = 1
+ elif im.mode == "L":
+ dim = 2
+ # Z Dimension: Number of channels
+ z = len(im.mode)
+
+ if dim == 1 or dim == 2:
+ z = 1
+
+ # assert we've got the right number of bands.
+ if len(im.getbands()) != z:
+ msg = f"incorrect number of bands in SGI write: {z} vs {len(im.getbands())}"
+ raise ValueError(msg)
+
+ # 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]
+ 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", dim))
+ 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):
+ 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/contrib/python/Pillow/py3/PIL/SpiderImagePlugin.py b/contrib/python/Pillow/py3/PIL/SpiderImagePlugin.py
new file mode 100644
index 00000000000..408b982b515
--- /dev/null
+++ b/contrib/python/Pillow/py3/PIL/SpiderImagePlugin.py
@@ -0,0 +1,318 @@
+#
+# 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
+#
+import os
+import struct
+import sys
+
+from . import Image, ImageFile
+
+
+def isInt(f):
+ 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):
+ 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):
+ 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):
+ # 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 = [("raw", (0, 0) + self.size, offset, (self.rawmode, 0, 1))]
+ self._fp = self.fp # FIXME: hack
+
+ @property
+ def n_frames(self):
+ return self._nimages
+
+ @property
+ def is_animated(self):
+ return self._nimages > 1
+
+ # 1st image index is zero (although SPIDER imgnumber starts at 1)
+ def tell(self):
+ if self.imgnumber < 1:
+ return 0
+ else:
+ return self.imgnumber - 1
+
+ def seek(self, frame):
+ if self.istack == 0:
+ msg = "attempt to seek in a non-stack file"
+ raise EOFError(msg)
+ if not self._seek_check(frame):
+ return
+ 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=255):
+ (minimum, maximum) = self.getextrema()
+ m = 1
+ if maximum != minimum:
+ m = depth / (maximum - minimum)
+ b = -m * minimum
+ return self.point(lambda i, m=m, b=b: i * m + b).convert("L")
+
+ # returns a ImageTk.PhotoImage object, after rescaling to 0..255
+ def tkPhotoImage(self):
+ 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=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
+
+ imglist = []
+ for img in filelist:
+ if not os.path.exists(img):
+ print(f"unable to find {img}")
+ continue
+ try:
+ with Image.open(img) as im:
+ im = im.convert2byte()
+ except Exception:
+ if not isSpiderImage(img):
+ print(img + " is not a Spider image file")
+ continue
+ im.info["filename"] = img
+ imglist.append(im)
+ return imglist
+
+
+# --------------------------------------------------------------------
+# For saving images in Spider format
+
+
+def makeSpiderHeader(im):
+ 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 = []
+ for i in range(nvalues):
+ hdr.append(0.0)
+
+ # 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, fp, filename):
+ if im.mode[0] != "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, [("raw", (0, 0) + im.size, 0, (rawmode, 0, 1))])
+
+
+def _save_spider(im, fp, filename):
+ # get the filename extension and register it with Image
+ ext = os.path.splitext(filename)[1]
+ 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("image: " + str(im))
+ print("format: " + str(im.format))
+ print("size: " + str(im.size))
+ print("mode: " + str(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/contrib/python/Pillow/py3/PIL/SunImagePlugin.py b/contrib/python/Pillow/py3/PIL/SunImagePlugin.py
new file mode 100644
index 00000000000..6a8d5d86b73
--- /dev/null
+++ b/contrib/python/Pillow/py3/PIL/SunImagePlugin.py
@@ -0,0 +1,139 @@
+#
+# 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 . import Image, ImageFile, ImagePalette
+from ._binary import i32be as i32
+
+
+def _accept(prefix):
+ 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):
+ # 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;
+
+ # 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 = [("raw", (0, 0) + self.size, offset, (rawmode, stride))]
+ elif file_type == 2:
+ self.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/contrib/python/Pillow/py3/PIL/TarIO.py b/contrib/python/Pillow/py3/PIL/TarIO.py
new file mode 100644
index 00000000000..32928f6af30
--- /dev/null
+++ b/contrib/python/Pillow/py3/PIL/TarIO.py
@@ -0,0 +1,66 @@
+#
+# 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.
+#
+
+import io
+
+from . import ContainerIO
+
+
+class TarIO(ContainerIO.ContainerIO):
+ """A file object that provides read access to a given member of a TAR file."""
+
+ def __init__(self, tarfile, file):
+ """
+ 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:
+ msg = "unexpected end of tar file"
+ raise OSError(msg)
+
+ name = s[:100].decode("utf-8")
+ i = name.find("\0")
+ if i == 0:
+ 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)
+
+ # Context manager support
+ def __enter__(self):
+ return self
+
+ def __exit__(self, *args):
+ self.close()
+
+ def close(self):
+ self.fh.close()
diff --git a/contrib/python/Pillow/py3/PIL/TgaImagePlugin.py b/contrib/python/Pillow/py3/PIL/TgaImagePlugin.py
new file mode 100644
index 00000000000..f24ee4f5c31
--- /dev/null
+++ b/contrib/python/Pillow/py3/PIL/TgaImagePlugin.py
@@ -0,0 +1,255 @@
+#
+# 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.
+#
+
+
+import warnings
+
+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): "BGR;5",
+ (2, 24): "BGR",
+ (2, 32): "BGRA",
+}
+
+
+##
+# Image plugin for Targa files.
+
+
+class TgaImageFile(ImageFile.ImageFile):
+ format = "TGA"
+ format_description = "Targa"
+
+ def _open(self):
+ # process header
+ 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"
+ elif imagetype in (2, 10):
+ self._mode = "RGB"
+ if depth == 32:
+ self._mode = "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(
+ "BGR;15", b"\0" * 2 * start + self.fp.read(2 * size)
+ )
+ elif mapdepth == 24:
+ self.palette = ImagePalette.raw(
+ "BGR", b"\0" * 3 * start + self.fp.read(3 * size)
+ )
+ elif mapdepth == 32:
+ self.palette = ImagePalette.raw(
+ "BGRA", b"\0" * 4 * start + self.fp.read(4 * size)
+ )
+
+ # setup tile descriptor
+ try:
+ rawmode = MODES[(imagetype & 7, depth)]
+ if imagetype & 8:
+ # compressed
+ self.tile = [
+ (
+ "tga_rle",
+ (0, 0) + self.size,
+ self.fp.tell(),
+ (rawmode, orientation, depth),
+ )
+ ]
+ else:
+ self.tile = [
+ (
+ "raw",
+ (0, 0) + self.size,
+ self.fp.tell(),
+ (rawmode, 0, orientation),
+ )
+ ]
+ except KeyError:
+ pass # cannot decode
+
+ def load_end(self):
+ 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, fp, filename):
+ 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, [("tga_rle", (0, 0) + im.size, 0, (rawmode, orientation))]
+ )
+ else:
+ ImageFile._save(
+ im, fp, [("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/contrib/python/Pillow/py3/PIL/TiffImagePlugin.py b/contrib/python/Pillow/py3/PIL/TiffImagePlugin.py
new file mode 100644
index 00000000000..dabf8dbfb5f
--- /dev/null
+++ b/contrib/python/Pillow/py3/PIL/TiffImagePlugin.py
@@ -0,0 +1,2156 @@
+#
+# 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.
+#
+import io
+import itertools
+import logging
+import math
+import os
+import struct
+import warnings
+from collections.abc import MutableMapping
+from fractions import Fraction
+from numbers import Number, Rational
+
+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 .TiffTags import TYPES
+
+logger = logging.getLogger(__name__)
+
+# Set these to true to force use of libtiff for reading or writing.
+READ_LIBTIFF = False
+WRITE_LIBTIFF = False
+IFD_LEGACY_API = True
+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
+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,)): ("RGBX", "RGBX"),
+ (MM, 2, (1,), 1, (8, 8, 8, 8), (0,)): ("RGBX", "RGBX"),
+ (II, 2, (1,), 1, (8, 8, 8, 8, 8), (0, 0)): ("RGBX", "RGBXX"),
+ (MM, 2, (1,), 1, (8, 8, 8, 8, 8), (0, 0)): ("RGBX", "RGBXX"),
+ (II, 2, (1,), 1, (8, 8, 8, 8, 8, 8), (0, 0, 0)): ("RGBX", "RGBXXX"),
+ (MM, 2, (1,), 1, (8, 8, 8, 8, 8, 8), (0, 0, 0)): ("RGBX", "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,)): ("RGBX", "RGBX;16L"),
+ (MM, 2, (1,), 1, (16, 16, 16, 16), (0,)): ("RGBX", "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), (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"),
+ (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
+]
+
+
+def _accept(prefix):
+ return prefix[:4] in PREFIXES
+
+
+def _limit_rational(val, max_val):
+ 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, max_val, min_val):
+ frac = Fraction(val)
+ n_d = frac.numerator, frac.denominator
+
+ if min(n_d) < min_val:
+ n_d = _limit_rational(val, abs(min_val))
+
+ if max(n_d) > max_val:
+ val = Fraction(*n_d)
+ n_d = _limit_rational(val, max_val)
+
+ return n_d
+
+
+##
+# Wrapper for TIFF IFDs.
+
+_load_dispatch = {}
+_write_dispatch = {}
+
+
+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, denominator=1):
+ """
+ :param value: either an integer numerator, a
+ float/rational/other number, or an IFDRational
+ :param denominator: Optional integer denominator
+ """
+ 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:
+ self._numerator = value
+ self._denominator = denominator
+
+ if denominator == 0:
+ self._val = float("nan")
+ elif denominator == 1:
+ self._val = Fraction(value)
+ else:
+ self._val = Fraction(value, denominator)
+
+ @property
+ def numerator(self):
+ return self._numerator
+
+ @property
+ def denominator(self):
+ return self._denominator
+
+ def limit_rational(self, max_denominator):
+ """
+
+ :param max_denominator: Integer, the maximum denominator value
+ :returns: Tuple of (numerator, denominator)
+ """
+
+ if self.denominator == 0:
+ return self.numerator, self.denominator
+
+ f = self._val.limit_denominator(max_denominator)
+ return f.numerator, f.denominator
+
+ def __repr__(self):
+ return str(float(self._val))
+
+ def __hash__(self):
+ return self._val.__hash__()
+
+ def __eq__(self, other):
+ val = self._val
+ if isinstance(other, IFDRational):
+ other = other._val
+ if isinstance(other, float):
+ val = float(val)
+ return val == other
+
+ def __getstate__(self):
+ return [self._val, self._numerator, self._denominator]
+
+ def __setstate__(self, state):
+ IFDRational.__init__(self, 0)
+ _val, _numerator, _denominator = state
+ self._val = _val
+ self._numerator = _numerator
+ self._denominator = _denominator
+
+ def _delegate(op):
+ def delegate(self, *args):
+ return getattr(self._val, op)(*args)
+
+ return delegate
+
+ """ 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__")
+
+
+class ImageFileDirectory_v2(MutableMapping):
+ """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``.
+
+ """
+
+ def __init__(self, ifh=b"II\052\0\0\0\0\0", prefix=None, group=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 = {}
+ """ Dictionary of tag types """
+ self.reset()
+ (self.next,) = (
+ self._unpack("Q", ifh[8:]) if self._bigtiff else self._unpack("L", ifh[4:])
+ )
+ self._legacy_api = False
+
+ prefix = property(lambda self: self._prefix)
+ offset = property(lambda self: self._offset)
+ legacy_api = property(lambda self: self._legacy_api)
+
+ @legacy_api.setter
+ def legacy_api(self, value):
+ msg = "Not allowing setting of legacy api"
+ raise Exception(msg)
+
+ def reset(self):
+ self._tags_v1 = {} # will remain empty if legacy_api is false
+ self._tags_v2 = {} # main tag storage
+ self._tagdata = {}
+ self.tagtype = {} # added 2008-06-05 by Florian Hoech
+ self._next = None
+ self._offset = None
+
+ def __str__(self):
+ return str(dict(self))
+
+ def named(self):
+ """
+ :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):
+ return len(set(self._tagdata) | set(self._tags_v2))
+
+ def __getitem__(self, tag):
+ 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):
+ return tag in self._tags_v2 or tag in self._tagdata
+
+ def __setitem__(self, tag, value):
+ self._setitem(tag, value, self.legacy_api)
+
+ def _setitem(self, tag, value, legacy_api):
+ 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):
+ self.tagtype[tag] = (
+ TiffTags.RATIONAL
+ if all(v >= 0 for v in values)
+ else TiffTags.SIGNED_RATIONAL
+ )
+ elif all(isinstance(v, int) for v in values):
+ if all(0 <= v < 2**16 for v in values):
+ self.tagtype[tag] = TiffTags.SHORT
+ elif all(-(2**15) < v < 2**15 for v in values):
+ self.tagtype[tag] = TiffTags.SIGNED_SHORT
+ else:
+ self.tagtype[tag] = (
+ TiffTags.LONG
+ if all(v >= 0 for v in values)
+ else 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) 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):
+ self._tags_v2.pop(tag, None)
+ self._tags_v1.pop(tag, None)
+ self._tagdata.pop(tag, None)
+
+ def __iter__(self):
+ return iter(set(self._tagdata) | set(self._tags_v2))
+
+ def _unpack(self, fmt, data):
+ return struct.unpack(self._endian + fmt, data)
+
+ def _pack(self, fmt, *values):
+ return struct.pack(self._endian + fmt, *values)
+
+ def _register_loader(idx, size):
+ def decorator(func):
+ 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):
+ def decorator(func):
+ _write_dispatch[idx] = func # noqa: F821
+ return func
+
+ return decorator
+
+ def _register_basic(idx_fmt_name):
+ from .TiffTags import TYPES
+
+ idx, fmt, name = idx_fmt_name
+ TYPES[idx] = name
+ size = struct.calcsize("=" + fmt)
+ _load_dispatch[idx] = ( # noqa: F821
+ size,
+ lambda self, data, legacy_api=True: (
+ self._unpack(f"{len(data) // size}{fmt}", data)
+ ),
+ )
+ _write_dispatch[idx] = lambda self, *values: ( # noqa: F821
+ b"".join(self._pack(fmt, value) for value in 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, legacy_api=True):
+ return data
+
+ @_register_writer(1) # Basic type, except for the legacy API.
+ def write_byte(self, data):
+ 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, legacy_api=True):
+ if data.endswith(b"\0"):
+ data = data[:-1]
+ return data.decode("latin-1", "replace")
+
+ @_register_writer(2)
+ def write_string(self, value):
+ # 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, legacy_api=True):
+ vals = self._unpack(f"{len(data) // 4}L", data)
+
+ def combine(a, b):
+ 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):
+ 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, legacy_api=True):
+ return data
+
+ @_register_writer(7)
+ def write_undefined(self, value):
+ if isinstance(value, int):
+ value = str(value).encode("ascii", "replace")
+ return value
+
+ @_register_loader(10, 8)
+ def load_signed_rational(self, data, legacy_api=True):
+ vals = self._unpack(f"{len(data) // 4}l", data)
+
+ def combine(a, b):
+ 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):
+ return b"".join(
+ self._pack("2l", *_limit_signed_rational(frac, 2**31 - 1, -(2**31)))
+ for frac in values
+ )
+
+ def _ensure_read(self, fp, size):
+ 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):
+ 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: " + (
+ "<table: %d bytes>" % size 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 tobytes(self, offset=0):
+ # FIXME What about tagdata?
+ result = self._pack("H", len(self._tags_v2))
+
+ entries = []
+ offset = offset + len(result) + len(self._tags_v2) * 12 + 4
+ 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.get(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:
+ if self._endian == "<":
+ ifh = b"II\x2A\x00\x08\x00\x00\x00"
+ else:
+ ifh = b"MM\x00\x2A\x00\x00\x00\x08"
+ ifd = ImageFileDirectory_v2(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})"
+ msg += " - value: " + (
+ "<table: %d bytes>" % len(data) 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) <= 4:
+ entries.append((tag, typ, count, data.ljust(4, b"\0"), b""))
+ else:
+ entries.append((tag, typ, count, self._pack("L", 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:
+ msg = "multistrip support not yet implemented"
+ raise NotImplementedError(msg)
+ value = self._pack("L", self._unpack("L", 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("HHL4s", tag, typ, count, value)
+
+ # -- overwrite here for multi-page --
+ result += b"\0\0\0\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):
+ if fp.tell() == 0: # skip TIFF header on subsequent pages
+ # tiff header -- PIL always starts the first IFD at offset 8
+ fp.write(self._prefix + self._pack("HL", 42, 8))
+
+ 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, "load_" + name, _load_dispatch[idx][1])
+ setattr(ImageFileDirectory_v2, "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, **kwargs):
+ 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
+ """Dictionary of tag types"""
+
+ @classmethod
+ def from_v2(cls, original):
+ """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):
+ """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):
+ return tag in self._tags_v1 or tag in self._tagdata
+
+ def __len__(self):
+ return len(set(self._tagdata) | set(self._tags_v1))
+
+ def __iter__(self):
+ return iter(set(self._tagdata) | set(self._tags_v1))
+
+ def __setitem__(self, tag, value):
+ for legacy_api in (False, True):
+ self._setitem(tag, value, legacy_api)
+
+ def __getitem__(self, tag):
+ 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 when IFD_LEGACY_API == False
+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=None, filename=None):
+ self.tag_v2 = None
+ """ Image file directory (tag dictionary) """
+
+ self.tag = None
+ """ Legacy tag entries """
+
+ super().__init__(fp, filename)
+
+ def _open(self):
+ """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)
+
+ # legacy IFD entries will be filled in later
+ self.ifd = None
+
+ # setup frame pointers
+ self.__first = self.__next = self.tag_v2.next
+ self.__frame = -1
+ self._fp = self.fp
+ self._frame_pos = []
+ self._n_frames = 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):
+ if self._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)
+ return self._n_frames
+
+ def seek(self, frame):
+ """Select a given frame as current image"""
+ if not self._seek_check(frame):
+ return
+ self._seek(frame)
+ # Create a new core image object on second and
+ # subsequent frames in the image. Image may be
+ # different size/mode.
+ Image._decompression_bomb_check(self.size)
+ self.im = Image.core.new(self.mode, self.size)
+
+ def _seek(self, frame):
+ self.fp = self._fp
+
+ # reset buffered io handle in case fp
+ # was passed to libtiff, invalidating the buffer
+ self.fp.tell()
+
+ 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(),
+ )
+ 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)
+ 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):
+ """Return the current frame number"""
+ return self.__frame
+
+ def getxmp(self):
+ """
+ Returns a dictionary containing the XMP tags.
+ Requires defusedxml to be installed.
+
+ :returns: XMP tags in a dictionary.
+ """
+ return self._getxmp(self.tag_v2[XMP]) if XMP in self.tag_v2 else {}
+
+ def get_photoshop_blocks(self):
+ """
+ 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[:4] == 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):
+ if self.tile and self.use_load_libtiff:
+ return self._load_libtiff()
+ return super().load()
+
+ def load_end(self):
+ # 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
+
+ # reset buffered io handle in case fp
+ # was passed to libtiff, invalidating the buffer
+ self.fp.tell()
+
+ # 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):
+ """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 = list(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:
+ args[2] = fp
+
+ decoder = Image._getdecoder(
+ self.mode, "libtiff", tuple(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)
+ # 4 bytes, otherwise the trace might error out
+ n, err = decoder.decode(b"fpfp")
+ 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:
+ raise OSError(err)
+
+ return Image.Image.load(self)
+
+ def _setup(self):
+ """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.get(YCBCRSUBSAMPLING))
+
+ # size
+ xsize = int(self.tag_v2.get(IMAGEWIDTH))
+ ysize = int(self.tag_v2.get(IMAGELENGTH))
+ 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]
+ # 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.
+ if rawmode == "I;16":
+ rawmode = "I;16N"
+ if ";16B" in rawmode:
+ rawmode = rawmode.replace(";16B", ";16N")
+ if ";16L" in rawmode:
+ rawmode = rawmode.replace(";16L", ";16N")
+
+ # 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"
+
+ # 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(("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 = self.size[0]
+ else:
+ # tiled image
+ offsets = self.tag_v2[TILEOFFSETS]
+ w = self.tag_v2.get(TILEWIDTH)
+ h = self.tag_v2.get(TILELENGTH)
+
+ 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
+
+ a = (tile_rawmode, int(stride), 1)
+ self.tile.append(
+ (
+ self._compression,
+ (x, y, min(x + w, xsize), min(y + h, ysize)),
+ offset,
+ a,
+ )
+ )
+ x = x + w
+ if x >= self.size[0]:
+ x, y = 0, y + h
+ if y >= self.size[1]:
+ x = 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;16S": ("I;16S", II, 1, 2, (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;32BS": ("I;32BS", MM, 1, 2, (32,), None),
+ "I;16B": ("I;16B", MM, 1, 1, (16,), None),
+ "I;16BS": ("I;16BS", MM, 1, 2, (16,), None),
+ "F;32BF": ("F;32BF", MM, 1, 3, (32,), None),
+}
+
+
+def _save(im, fp, filename):
+ 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
+
+ ifd = ImageFileDirectory_v2(prefix=prefix)
+
+ encoderinfo = im.encoderinfo
+ encoderconfig = im.encoderconfig
+ 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
+
+ # additions written by Greg Couch, [email protected]
+ # inspired by image-sig posting from Kevin Cazabon, [email protected]
+ 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:
+ 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()
+ 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
+ stride = len(bits) * ((im.size[0] * bits[0] + 7) // 8)
+ # 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, im.size[1])
+ # JPEG encoder expects multiple of 8 rows
+ if compression == "jpeg":
+ rows_per_strip = min(((rows_per_strip + 7) // 8) * 8, im.size[1])
+ else:
+ rows_per_strip = im.size[1]
+ if rows_per_strip == 0:
+ rows_per_strip = 1
+ strip_byte_counts = 1 if stride == 0 else stride * rows_per_strip
+ strips_per_image = (im.size[1] + rows_per_strip - 1) // rows_per_strip
+ ifd[ROWSPERSTRIP] = rows_per_strip
+ if strip_byte_counts >= 2**16:
+ ifd.tagtype[STRIPBYTECOUNTS] = TiffTags.LONG
+ ifd[STRIPBYTECOUNTS] = (strip_byte_counts,) * (strips_per_image - 1) + (
+ stride * im.size[1] - 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, value in {
+ YCBCRSUBSAMPLING: (1, 1),
+ REFERENCEBLACKWHITE: (0, 255, 128, 255, 128, 255),
+ }.items():
+ ifd.setdefault(tag, 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 = os.dup(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.
+ # 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 += [
+ REFERENCEBLACKWHITE,
+ STRIPBYTECOUNTS,
+ STRIPOFFSETS,
+ TRANSFERFUNCTION,
+ SUBIFD,
+ ]
+
+ # bits per sample is a single short in the tiff directory, not a list.
+ atts = {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.
+ legacy_ifd = {}
+ if hasattr(im, "tag"):
+ legacy_ifd = im.tag.to_v2()
+
+ # SAMPLEFORMAT is determined by the image format and should not be copied
+ # from legacy_ifd.
+ supplied_tags = {**getattr(im, "tag_v2", {}), **legacy_ifd}
+ if SAMPLEFORMAT in supplied_tags:
+ del supplied_tags[SAMPLEFORMAT]
+
+ 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 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;16B", "I;16"):
+ 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)
+ e = Image._getencoder(im.mode, "libtiff", a, encoderconfig)
+ e.setimage(im.im, (0, 0) + im.size)
+ while True:
+ # undone, change to self.decodermaxblock:
+ errcode, data = e.encode(16 * 1024)[1:]
+ if not _fp:
+ fp.write(data)
+ if errcode:
+ break
+ if _fp:
+ try:
+ os.close(_fp)
+ except OSError:
+ pass
+ 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, [("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)
+ im._debug_multipage = ifd
+
+
+class AppendingTiffWriter:
+ 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
+ ]
+
+ # StripOffsets = 273
+ # FreeOffsets = 288
+ # TileOffsets = 324
+ # JPEGQTables = 519
+ # JPEGDCTables = 520
+ # JPEGACTables = 521
+ Tags = {273, 288, 324, 519, 520, 521}
+
+ def __init__(self, fn, new=False):
+ if hasattr(fn, "read"):
+ self.f = fn
+ self.close_fp = False
+ else:
+ 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")
+ self.beginning = self.f.tell()
+ self.setup()
+
+ def setup(self):
+ # Reset everything.
+ self.f.seek(self.beginning, os.SEEK_SET)
+
+ self.whereToWriteNewIFDOffset = None
+ self.offsetOfNewPage = 0
+
+ self.IIMM = iimm = self.f.read(4)
+ if not iimm:
+ # empty file - first page
+ self.isFirst = True
+ return
+
+ self.isFirst = False
+ if iimm == b"II\x2a\x00":
+ self.setEndian("<")
+ elif iimm == b"MM\x00\x2a":
+ self.setEndian(">")
+ else:
+ msg = "Invalid TIFF file header"
+ raise RuntimeError(msg)
+
+ self.skipIFDs()
+ self.goToEnd()
+
+ def finalize(self):
+ if self.isFirst:
+ return
+
+ # fix offsets
+ self.f.seek(self.offsetOfNewPage)
+
+ iimm = self.f.read(4)
+ if not iimm:
+ # msg = "nothing written into new page"
+ # raise RuntimeError(msg)
+ # 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)
+
+ ifd_offset = self.readLong()
+ ifd_offset += self.offsetOfNewPage
+ self.f.seek(self.whereToWriteNewIFDOffset)
+ self.writeLong(ifd_offset)
+ self.f.seek(ifd_offset)
+ self.fixIFD()
+
+ def newFrame(self):
+ # Call this to finish a frame.
+ self.finalize()
+ self.setup()
+
+ def __enter__(self):
+ return self
+
+ def __exit__(self, exc_type, exc_value, traceback):
+ if self.close_fp:
+ self.close()
+ return False
+
+ def tell(self):
+ return self.f.tell() - self.offsetOfNewPage
+
+ def seek(self, offset, whence=io.SEEK_SET):
+ if whence == os.SEEK_SET:
+ offset += self.offsetOfNewPage
+
+ self.f.seek(offset, whence)
+ return self.tell()
+
+ def goToEnd(self):
+ 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):
+ self.endian = endian
+ self.longFmt = self.endian + "L"
+ self.shortFmt = self.endian + "H"
+ self.tagFormat = self.endian + "HHL"
+
+ def skipIFDs(self):
+ while True:
+ ifd_offset = self.readLong()
+ if ifd_offset == 0:
+ self.whereToWriteNewIFDOffset = self.f.tell() - 4
+ break
+
+ self.f.seek(ifd_offset)
+ num_tags = self.readShort()
+ self.f.seek(num_tags * 12, os.SEEK_CUR)
+
+ def write(self, data):
+ return self.f.write(data)
+
+ def readShort(self):
+ (value,) = struct.unpack(self.shortFmt, self.f.read(2))
+ return value
+
+ def readLong(self):
+ (value,) = struct.unpack(self.longFmt, self.f.read(4))
+ return value
+
+ def rewriteLastShortToLong(self, value):
+ self.f.seek(-2, os.SEEK_CUR)
+ bytes_written = self.f.write(struct.pack(self.longFmt, value))
+ if bytes_written is not None and bytes_written != 4:
+ msg = f"wrote only {bytes_written} bytes but wanted 4"
+ raise RuntimeError(msg)
+
+ def rewriteLastShort(self, value):
+ self.f.seek(-2, os.SEEK_CUR)
+ bytes_written = self.f.write(struct.pack(self.shortFmt, value))
+ if bytes_written is not None and bytes_written != 2:
+ msg = f"wrote only {bytes_written} bytes but wanted 2"
+ raise RuntimeError(msg)
+
+ def rewriteLastLong(self, value):
+ self.f.seek(-4, os.SEEK_CUR)
+ bytes_written = self.f.write(struct.pack(self.longFmt, value))
+ if bytes_written is not None and bytes_written != 4:
+ msg = f"wrote only {bytes_written} bytes but wanted 4"
+ raise RuntimeError(msg)
+
+ def writeShort(self, value):
+ bytes_written = self.f.write(struct.pack(self.shortFmt, value))
+ if bytes_written is not None and bytes_written != 2:
+ msg = f"wrote only {bytes_written} bytes but wanted 2"
+ raise RuntimeError(msg)
+
+ def writeLong(self, value):
+ bytes_written = self.f.write(struct.pack(self.longFmt, value))
+ if bytes_written is not None and bytes_written != 4:
+ msg = f"wrote only {bytes_written} bytes but wanted 4"
+ raise RuntimeError(msg)
+
+ def close(self):
+ self.finalize()
+ self.f.close()
+
+ def fixIFD(self):
+ num_tags = self.readShort()
+
+ for i in range(num_tags):
+ tag, field_type, count = struct.unpack(self.tagFormat, self.f.read(8))
+
+ field_size = self.fieldSizes[field_type]
+ total_size = field_size * count
+ is_local = total_size <= 4
+ if not is_local:
+ offset = self.readLong()
+ offset += self.offsetOfNewPage
+ self.rewriteLastLong(offset)
+
+ if tag in self.Tags:
+ cur_pos = self.f.tell()
+
+ if is_local:
+ self.fixOffsets(
+ count, isShort=(field_size == 2), isLong=(field_size == 4)
+ )
+ self.f.seek(cur_pos + 4)
+ else:
+ self.f.seek(offset)
+ self.fixOffsets(
+ count, isShort=(field_size == 2), isLong=(field_size == 4)
+ )
+ self.f.seek(cur_pos)
+
+ offset = cur_pos = None
+
+ elif is_local:
+ # skip the locally stored value that is not an offset
+ self.f.seek(4, os.SEEK_CUR)
+
+ def fixOffsets(self, count, isShort=False, isLong=False):
+ if not isShort and not isLong:
+ msg = "offset is neither short nor long"
+ raise RuntimeError(msg)
+
+ for i in range(count):
+ offset = self.readShort() if isShort else self.readLong()
+ offset += self.offsetOfNewPage
+ if isShort and offset >= 65536:
+ # offset is now too large - we must convert shorts to longs
+ 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.rewriteLastShortToLong(offset)
+ self.f.seek(-10, os.SEEK_CUR)
+ self.writeShort(TiffTags.LONG) # rewrite the type to LONG
+ self.f.seek(8, os.SEEK_CUR)
+ elif isShort:
+ self.rewriteLastShort(offset)
+ else:
+ self.rewriteLastLong(offset)
+
+
+def _save_all(im, fp, filename):
+ encoderinfo = im.encoderinfo.copy()
+ encoderconfig = im.encoderconfig
+ append_images = list(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:
+ ims.encoderinfo = encoderinfo
+ ims.encoderconfig = encoderconfig
+ if not hasattr(ims, "n_frames"):
+ nfr = 1
+ else:
+ nfr = ims.n_frames
+
+ for idx in range(nfr):
+ ims.seek(idx)
+ ims.load()
+ _save(ims, tf, filename)
+ tf.newFrame()
+ 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/contrib/python/Pillow/py3/PIL/TiffTags.py b/contrib/python/Pillow/py3/PIL/TiffTags.py
new file mode 100644
index 00000000000..30b05e4e1d4
--- /dev/null
+++ b/contrib/python/Pillow/py3/PIL/TiffTags.py
@@ -0,0 +1,560 @@
+#
+# 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 collections import namedtuple
+
+
+class TagInfo(namedtuple("_TagInfo", "value name type length enum")):
+ __slots__ = []
+
+ def __new__(cls, value=None, name="unknown", type=None, length=None, enum=None):
+ return super().__new__(cls, value, name, type, length, enum or {})
+
+ def cvt_enum(self, value):
+ # 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, group=None):
+ """
+ :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 = {
+ 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 = {
+ 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",
+}
+
+
+def _populate():
+ 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():
+ for k, v in tags.items():
+ tags[k] = TagInfo(k, *v)
+
+
+_populate()
+##
+# Map type numbers to type names -- defined in ImageFileDirectory.
+
+TYPES = {}
+
+# was:
+# TYPES = {
+# 1: "byte",
+# 2: "ascii",
+# 3: "short",
+# 4: "long",
+# 5: "rational",
+# 6: "signed byte",
+# 7: "undefined",
+# 8: "signed short",
+# 9: "signed long",
+# 10: "signed rational",
+# 11: "float",
+# 12: "double",
+# }
+
+#
+# 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/contrib/python/Pillow/py3/PIL/WalImageFile.py b/contrib/python/Pillow/py3/PIL/WalImageFile.py
new file mode 100644
index 00000000000..3d9f97f8485
--- /dev/null
+++ b/contrib/python/Pillow/py3/PIL/WalImageFile.py
@@ -0,0 +1,123 @@
+#
+# 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 . import Image, ImageFile
+from ._binary import i32le as i32
+
+
+class WalImageFile(ImageFile.ImageFile):
+ format = "WAL"
+ format_description = "Quake2 Texture"
+
+ def _open(self):
+ 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):
+ if not self.im:
+ 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):
+ """
+ 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/contrib/python/Pillow/py3/PIL/WebPImagePlugin.py b/contrib/python/Pillow/py3/PIL/WebPImagePlugin.py
new file mode 100644
index 00000000000..612fc09467a
--- /dev/null
+++ b/contrib/python/Pillow/py3/PIL/WebPImagePlugin.py
@@ -0,0 +1,361 @@
+from io import BytesIO
+
+from . import Image, ImageFile
+
+try:
+ from . import _webp
+
+ SUPPORTED = True
+except ImportError:
+ SUPPORTED = False
+
+
+_VALID_WEBP_MODES = {"RGBX": True, "RGBA": True, "RGB": True}
+
+_VALID_WEBP_LEGACY_MODES = {"RGB": True, "RGBA": True}
+
+_VP8_MODES_BY_IDENTIFIER = {
+ b"VP8 ": "RGB",
+ b"VP8X": "RGBA",
+ b"VP8L": "RGBA", # lossless
+}
+
+
+def _accept(prefix):
+ is_riff_file_format = prefix[:4] == 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
+
+
+class WebPImageFile(ImageFile.ImageFile):
+ format = "WEBP"
+ format_description = "WebP image"
+ __loaded = 0
+ __logical_frame = 0
+
+ def _open(self):
+ if not _webp.HAVE_WEBPANIM:
+ # Legacy mode
+ data, width, height, self._mode, icc_profile, exif = _webp.WebPDecode(
+ self.fp.read()
+ )
+ if icc_profile:
+ self.info["icc_profile"] = icc_profile
+ if exif:
+ self.info["exif"] = exif
+ self._size = width, height
+ self.fp = BytesIO(data)
+ self.tile = [("raw", (0, 0) + self.size, 0, self.mode)]
+ self.n_frames = 1
+ self.is_animated = False
+ return
+
+ # 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
+ width, height, loop_count, bgcolor, frame_count, mode = self._decoder.get_info()
+ self._size = width, height
+ 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
+ self.tile = []
+
+ # 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):
+ if "exif" not in self.info:
+ return None
+ return self.getexif()._get_merged_dict()
+
+ def getxmp(self):
+ """
+ Returns a dictionary containing the XMP tags.
+ Requires defusedxml to be installed.
+
+ :returns: XMP tags in a dictionary.
+ """
+ return self._getxmp(self.info["xmp"]) if "xmp" in self.info else {}
+
+ def seek(self, frame):
+ if not self._seek_check(frame):
+ return
+
+ # Set logical frame to requested position
+ self.__logical_frame = frame
+
+ def _reset(self, reset=True):
+ if reset:
+ self._decoder.reset()
+ self.__physical_frame = 0
+ self.__loaded = -1
+ self.__timestamp = 0
+
+ def _get_next(self):
+ # 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):
+ 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):
+ if _webp.HAVE_WEBPANIM:
+ 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 = [("raw", (0, 0) + self.size, 0, self.rawmode)]
+
+ return super().load()
+
+ def tell(self):
+ if not _webp.HAVE_WEBPANIM:
+ return super().tell()
+
+ return self.__logical_frame
+
+
+def _save_all(im, fp, filename):
+ 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 = (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)
+ 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[0],
+ im.size[1],
+ 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 # of frames in this image
+ nfr = getattr(ims, "n_frames", 1)
+
+ for idx in range(nfr):
+ ims.seek(idx)
+ ims.load()
+
+ # Make sure image mode is supported
+ frame = ims
+ rawmode = ims.mode
+ if ims.mode not in _VALID_WEBP_MODES:
+ alpha = (
+ "A" in ims.mode
+ or "a" in ims.mode
+ or (ims.mode == "P" and "A" in ims.im.getpalettemode())
+ )
+ rawmode = "RGBA" if alpha else "RGB"
+ frame = ims.convert(rawmode)
+
+ if rawmode == "RGB":
+ # For faster conversion, use RGBX
+ rawmode = "RGBX"
+
+ # Append the frame to the animation encoder
+ enc.add(
+ frame.tobytes("raw", rawmode),
+ round(timestamp),
+ frame.size[0],
+ frame.size[1],
+ rawmode,
+ lossless,
+ 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), 0, 0, "", lossless, 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, fp, filename):
+ lossless = im.encoderinfo.get("lossless", False)
+ quality = im.encoderinfo.get("quality", 80)
+ 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
+
+ if im.mode not in _VALID_WEBP_LEGACY_MODES:
+ im = im.convert("RGBA" if im.has_transparency_data else "RGB")
+
+ data = _webp.WebPEncode(
+ im.tobytes(),
+ im.size[0],
+ im.size[1],
+ lossless,
+ float(quality),
+ im.mode,
+ 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)
+ if _webp.HAVE_WEBPANIM:
+ Image.register_save_all(WebPImageFile.format, _save_all)
+ Image.register_extension(WebPImageFile.format, ".webp")
+ Image.register_mime(WebPImageFile.format, "image/webp")
diff --git a/contrib/python/Pillow/py3/PIL/WmfImagePlugin.py b/contrib/python/Pillow/py3/PIL/WmfImagePlugin.py
new file mode 100644
index 00000000000..3e5fb01512d
--- /dev/null
+++ b/contrib/python/Pillow/py3/PIL/WmfImagePlugin.py
@@ -0,0 +1,178 @@
+#
+# 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 . 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):
+ """
+ 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:
+ def open(self, im):
+ im._mode = "RGB"
+ self.bbox = im.info["wmf_bbox"]
+
+ def load(self, im):
+ 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):
+ return (
+ prefix[:6] == b"\xd7\xcd\xc6\x9a\x00\x00" or prefix[:4] == b"\x01\x00\x00\x00"
+ )
+
+
+##
+# Image plugin for Windows metafiles.
+
+
+class WmfStubImageFile(ImageFile.StubImageFile):
+ format = "WMF"
+ format_description = "Windows Metafile"
+
+ def _open(self):
+ self._inch = None
+
+ # check placable header
+ s = self.fp.read(80)
+
+ if s[:6] == b"\xd7\xcd\xc6\x9a\x00\x00":
+ # placeable windows metafile
+
+ # get units per inch
+ self._inch = word(s, 14)
+
+ # 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"] // self._inch,
+ (y1 - y0) * self.info["dpi"] // self._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[:4] == 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 - y0) / (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
+
+ 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):
+ return _handler
+
+ def load(self, dpi=None):
+ if dpi is not None and self._inch is not None:
+ self.info["dpi"] = dpi
+ x0, y0, x1, y1 = self.info["wmf_bbox"]
+ self._size = (
+ (x1 - x0) * self.info["dpi"] // self._inch,
+ (y1 - y0) * self.info["dpi"] // self._inch,
+ )
+ return super().load()
+
+
+def _save(im, fp, filename):
+ 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/contrib/python/Pillow/py3/PIL/XVThumbImagePlugin.py b/contrib/python/Pillow/py3/PIL/XVThumbImagePlugin.py
new file mode 100644
index 00000000000..eda60c5c5ca
--- /dev/null
+++ b/contrib/python/Pillow/py3/PIL/XVThumbImagePlugin.py
@@ -0,0 +1,78 @@
+#
+# The Python Imaging Library.
+# $Id$
+#
+# XV Thumbnail file handler by Charles E. "Gene" Cash
+#
+# 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 . 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):
+ return prefix[:6] == _MAGIC
+
+
+##
+# Image plugin for XV thumbnail images.
+
+
+class XVThumbImageFile(ImageFile.ImageFile):
+ format = "XVThumb"
+ format_description = "XV thumbnail image"
+
+ def _open(self):
+ # check magic
+ 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 = [("raw", (0, 0) + self.size, self.fp.tell(), (self.mode, 0, 1))]
+
+
+# --------------------------------------------------------------------
+
+Image.register_open(XVThumbImageFile.format, XVThumbImageFile, _accept)
diff --git a/contrib/python/Pillow/py3/PIL/XbmImagePlugin.py b/contrib/python/Pillow/py3/PIL/XbmImagePlugin.py
new file mode 100644
index 00000000000..71cd57d74da
--- /dev/null
+++ b/contrib/python/Pillow/py3/PIL/XbmImagePlugin.py
@@ -0,0 +1,94 @@
+#
+# 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.
+#
+
+import re
+
+from . import Image, ImageFile
+
+# XBM header
+xbm_head = re.compile(
+ rb"\s*#define[ \t]+.*_width[ \t]+(?P<width>[0-9]+)[\r\n]+"
+ b"#define[ \t]+.*_height[ \t]+(?P<height>[0-9]+)[\r\n]+"
+ b"(?P<hotspot>"
+ b"#define[ \t]+[^_]*_x_hot[ \t]+(?P<xhot>[0-9]+)[\r\n]+"
+ b"#define[ \t]+[^_]*_y_hot[ \t]+(?P<yhot>[0-9]+)[\r\n]+"
+ b")?"
+ rb"[\000-\377]*_bits\[]"
+)
+
+
+def _accept(prefix):
+ return prefix.lstrip()[:7] == b"#define"
+
+
+##
+# Image plugin for X11 bitmaps.
+
+
+class XbmImageFile(ImageFile.ImageFile):
+ format = "XBM"
+ format_description = "X11 Bitmap"
+
+ def _open(self):
+ 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 = [("xbm", (0, 0) + self.size, m.end(), None)]
+
+
+def _save(im, fp, filename):
+ 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, [("xbm", (0, 0) + im.size, 0, None)])
+
+ 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/contrib/python/Pillow/py3/PIL/XpmImagePlugin.py b/contrib/python/Pillow/py3/PIL/XpmImagePlugin.py
new file mode 100644
index 00000000000..8491d3b7e92
--- /dev/null
+++ b/contrib/python/Pillow/py3/PIL/XpmImagePlugin.py
@@ -0,0 +1,128 @@
+#
+# 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.
+#
+
+
+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):
+ return prefix[:9] == b"/* XPM */"
+
+
+##
+# Image plugin for X11 pixel maps.
+
+
+class XpmImageFile(ImageFile.ImageFile):
+ format = "XPM"
+ format_description = "X11 Pixel Map"
+
+ def _open(self):
+ if not _accept(self.fp.read(9)):
+ msg = "not an XPM file"
+ raise SyntaxError(msg)
+
+ # skip forward to next string
+ while True:
+ s = self.fp.readline()
+ if not s:
+ msg = "broken XPM file"
+ raise SyntaxError(msg)
+ m = xpm_head.match(s)
+ if m:
+ break
+
+ self._size = int(m.group(1)), int(m.group(2))
+
+ pal = int(m.group(3))
+ bpp = int(m.group(4))
+
+ if pal > 256 or bpp != 1:
+ msg = "cannot read this XPM file"
+ raise ValueError(msg)
+
+ #
+ # load palette description
+
+ palette = [b"\0\0\0"] * 256
+
+ for _ in range(pal):
+ s = self.fp.readline()
+ if s[-2:] == b"\r\n":
+ s = s[:-2]
+ elif s[-1:] in b"\r\n":
+ s = s[:-1]
+
+ c = s[1]
+ s = s[2:-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[:1] == b"#":
+ # FIXME: handle colour names (see ImagePalette.py)
+ rgb = int(rgb[1:], 16)
+ palette[c] = (
+ o8((rgb >> 16) & 255) + o8((rgb >> 8) & 255) + o8(rgb & 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)
+
+ self._mode = "P"
+ self.palette = ImagePalette.raw("RGB", b"".join(palette))
+
+ self.tile = [("raw", (0, 0) + self.size, self.fp.tell(), ("P", 0, 1))]
+
+ def load_read(self, bytes):
+ #
+ # load all image data in one chunk
+
+ xsize, ysize = self.size
+
+ s = [None] * ysize
+
+ for i in range(ysize):
+ s[i] = self.fp.readline()[1 : xsize + 1].ljust(xsize)
+
+ return b"".join(s)
+
+
+#
+# Registry
+
+
+Image.register_open(XpmImageFile.format, XpmImageFile, _accept)
+
+Image.register_extension(XpmImageFile.format, ".xpm")
+
+Image.register_mime(XpmImageFile.format, "image/xpm")
diff --git a/contrib/python/Pillow/py3/PIL/__init__.py b/contrib/python/Pillow/py3/PIL/__init__.py
new file mode 100644
index 00000000000..2bb8f6d7f10
--- /dev/null
+++ b/contrib/python/Pillow/py3/PIL/__init__.py
@@ -0,0 +1,84 @@
+"""Pillow (Fork of the Python Imaging Library)
+
+Pillow is the friendly PIL fork by Jeffrey A. Clark (Alex) 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 . 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 = [
+ "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/contrib/python/Pillow/py3/PIL/__main__.py b/contrib/python/Pillow/py3/PIL/__main__.py
new file mode 100644
index 00000000000..d249991d053
--- /dev/null
+++ b/contrib/python/Pillow/py3/PIL/__main__.py
@@ -0,0 +1,9 @@
+from .features import pilinfo
+
+
+def main():
+ pilinfo()
+
+
+if __name__ == '__main__':
+ main()
diff --git a/contrib/python/Pillow/py3/PIL/_binary.py b/contrib/python/Pillow/py3/PIL/_binary.py
new file mode 100644
index 00000000000..a74ee9eb6f3
--- /dev/null
+++ b/contrib/python/Pillow/py3/PIL/_binary.py
@@ -0,0 +1,102 @@
+#
+# The Python Imaging Library.
+# $Id$
+#
+# Binary input/output support routines.
+#
+# Copyright (c) 1997-2003 by Secret Labs AB
+# Copyright (c) 1995-2003 by Fredrik Lundh
+# Copyright (c) 2012 by Brian Crowell
+#
+# See the README file for information on usage and redistribution.
+#
+
+
+"""Binary input/output support routines."""
+
+
+from struct import pack, unpack_from
+
+
+def i8(c):
+ return c if c.__class__ is int else c[0]
+
+
+def o8(i):
+ return bytes((i & 255,))
+
+
+# Input, le = little endian, be = big endian
+def i16le(c, o=0):
+ """
+ Converts a 2-bytes (16 bits) string to an unsigned integer.
+
+ :param c: string containing bytes to convert
+ :param o: offset of bytes to convert in string
+ """
+ return unpack_from("<H", c, o)[0]
+
+
+def si16le(c, o=0):
+ """
+ Converts a 2-bytes (16 bits) string to a signed integer.
+
+ :param c: string containing bytes to convert
+ :param o: offset of bytes to convert in string
+ """
+ return unpack_from("<h", c, o)[0]
+
+
+def si16be(c, o=0):
+ """
+ Converts a 2-bytes (16 bits) string to a signed integer, big endian.
+
+ :param c: string containing bytes to convert
+ :param o: offset of bytes to convert in string
+ """
+ return unpack_from(">h", c, o)[0]
+
+
+def i32le(c, o=0):
+ """
+ Converts a 4-bytes (32 bits) string to an unsigned integer.
+
+ :param c: string containing bytes to convert
+ :param o: offset of bytes to convert in string
+ """
+ return unpack_from("<I", c, o)[0]
+
+
+def si32le(c, o=0):
+ """
+ Converts a 4-bytes (32 bits) string to a signed integer.
+
+ :param c: string containing bytes to convert
+ :param o: offset of bytes to convert in string
+ """
+ return unpack_from("<i", c, o)[0]
+
+
+def i16be(c, o=0):
+ return unpack_from(">H", c, o)[0]
+
+
+def i32be(c, o=0):
+ return unpack_from(">I", c, o)[0]
+
+
+# Output, le = little endian, be = big endian
+def o16le(i):
+ return pack("<H", i)
+
+
+def o32le(i):
+ return pack("<I", i)
+
+
+def o16be(i):
+ return pack(">H", i)
+
+
+def o32be(i):
+ return pack(">I", i)
diff --git a/contrib/python/Pillow/py3/PIL/_deprecate.py b/contrib/python/Pillow/py3/PIL/_deprecate.py
new file mode 100644
index 00000000000..2f2a3df13e3
--- /dev/null
+++ b/contrib/python/Pillow/py3/PIL/_deprecate.py
@@ -0,0 +1,69 @@
+from __future__ import annotations
+
+import warnings
+
+from . import __version__
+
+
+def deprecate(
+ deprecated: str,
+ when: int | None,
+ replacement: str | None = None,
+ *,
+ action: str | None = None,
+ plural: bool = False,
+) -> None:
+ """
+ Deprecations helper.
+
+ :param deprecated: Name of thing to be deprecated.
+ :param when: Pillow major version to be removed in.
+ :param replacement: Name of replacement.
+ :param action: Instead of "replacement", give a custom call to action
+ e.g. "Upgrade to new thing".
+ :param plural: if the deprecated thing is plural, needing "are" instead of "is".
+
+ Usually of the form:
+
+ "[deprecated] is deprecated and will be removed in Pillow [when] (yyyy-mm-dd).
+ Use [replacement] instead."
+
+ You can leave out the replacement sentence:
+
+ "[deprecated] is deprecated and will be removed in Pillow [when] (yyyy-mm-dd)"
+
+ Or with another call to action:
+
+ "[deprecated] is deprecated and will be removed in Pillow [when] (yyyy-mm-dd).
+ [action]."
+ """
+
+ is_ = "are" if plural else "is"
+
+ if when is None:
+ removed = "a future version"
+ elif when <= int(__version__.split(".")[0]):
+ msg = f"{deprecated} {is_} deprecated and should be removed."
+ raise RuntimeError(msg)
+ elif when == 11:
+ removed = "Pillow 11 (2024-10-15)"
+ else:
+ msg = f"Unknown removal version: {when}. Update {__name__}?"
+ raise ValueError(msg)
+
+ if replacement and action:
+ msg = "Use only one of 'replacement' and 'action'"
+ raise ValueError(msg)
+
+ if replacement:
+ action = f". Use {replacement} instead."
+ elif action:
+ action = f". {action.rstrip('.')}."
+ else:
+ action = ""
+
+ warnings.warn(
+ f"{deprecated} {is_} deprecated and will be removed in {removed}{action}",
+ DeprecationWarning,
+ stacklevel=3,
+ )
diff --git a/contrib/python/Pillow/py3/PIL/_util.py b/contrib/python/Pillow/py3/PIL/_util.py
new file mode 100644
index 00000000000..ba27b7e49e9
--- /dev/null
+++ b/contrib/python/Pillow/py3/PIL/_util.py
@@ -0,0 +1,19 @@
+import os
+from pathlib import Path
+
+
+def is_path(f):
+ return isinstance(f, (bytes, str, Path))
+
+
+def is_directory(f):
+ """Checks if an object is a string, and that it points to a directory."""
+ return is_path(f) and os.path.isdir(f)
+
+
+class DeferredError:
+ def __init__(self, ex):
+ self.ex = ex
+
+ def __getattr__(self, elt):
+ raise self.ex
diff --git a/contrib/python/Pillow/py3/PIL/_version.py b/contrib/python/Pillow/py3/PIL/_version.py
new file mode 100644
index 00000000000..0936d1a7f35
--- /dev/null
+++ b/contrib/python/Pillow/py3/PIL/_version.py
@@ -0,0 +1,2 @@
+# Master version for Pillow
+__version__ = "10.1.0"
diff --git a/contrib/python/Pillow/py3/PIL/features.py b/contrib/python/Pillow/py3/PIL/features.py
new file mode 100644
index 00000000000..0936499fb8f
--- /dev/null
+++ b/contrib/python/Pillow/py3/PIL/features.py
@@ -0,0 +1,329 @@
+import collections
+import os
+import sys
+import warnings
+
+import PIL
+
+from . import Image
+
+modules = {
+ "pil": ("PIL._imaging", "PILLOW_VERSION"),
+ "tkinter": ("PIL._tkinter_finder", "tk_version"),
+ "freetype2": ("PIL._imagingft", "freetype2_version"),
+ "littlecms2": ("PIL._imagingcms", "littlecms_version"),
+ "webp": ("PIL._webp", "webpdecoder_version"),
+}
+
+
+def check_module(feature):
+ """
+ Checks if a module is available.
+
+ :param feature: The module to check for.
+ :returns: ``True`` if available, ``False`` otherwise.
+ :raises ValueError: If the module is not defined in this version of Pillow.
+ """
+ if feature not in modules:
+ msg = f"Unknown module {feature}"
+ raise ValueError(msg)
+
+ module, ver = modules[feature]
+
+ try:
+ __import__(module)
+ return True
+ except ModuleNotFoundError:
+ return False
+ except ImportError as ex:
+ warnings.warn(str(ex))
+ return False
+
+
+def version_module(feature):
+ """
+ :param feature: The module to check for.
+ :returns:
+ The loaded version number as a string, or ``None`` if unknown or not available.
+ :raises ValueError: If the module is not defined in this version of Pillow.
+ """
+ if not check_module(feature):
+ return None
+
+ module, ver = modules[feature]
+
+ if ver is None:
+ return None
+
+ return getattr(__import__(module, fromlist=[ver]), ver)
+
+
+def get_supported_modules():
+ """
+ :returns: A list of all supported modules.
+ """
+ return [f for f in modules if check_module(f)]
+
+
+codecs = {
+ "jpg": ("jpeg", "jpeglib"),
+ "jpg_2000": ("jpeg2k", "jp2klib"),
+ "zlib": ("zip", "zlib"),
+ "libtiff": ("libtiff", "libtiff"),
+}
+
+
+def check_codec(feature):
+ """
+ Checks if a codec is available.
+
+ :param feature: The codec to check for.
+ :returns: ``True`` if available, ``False`` otherwise.
+ :raises ValueError: If the codec is not defined in this version of Pillow.
+ """
+ if feature not in codecs:
+ msg = f"Unknown codec {feature}"
+ raise ValueError(msg)
+
+ codec, lib = codecs[feature]
+
+ return codec + "_encoder" in dir(Image.core)
+
+
+def version_codec(feature):
+ """
+ :param feature: The codec to check for.
+ :returns:
+ The version number as a string, or ``None`` if not available.
+ Checked at compile time for ``jpg``, run-time otherwise.
+ :raises ValueError: If the codec is not defined in this version of Pillow.
+ """
+ if not check_codec(feature):
+ return None
+
+ codec, lib = codecs[feature]
+
+ version = getattr(Image.core, lib + "_version")
+
+ if feature == "libtiff":
+ return version.split("\n")[0].split("Version ")[1]
+
+ return version
+
+
+def get_supported_codecs():
+ """
+ :returns: A list of all supported codecs.
+ """
+ return [f for f in codecs if check_codec(f)]
+
+
+features = {
+ "webp_anim": ("PIL._webp", "HAVE_WEBPANIM", None),
+ "webp_mux": ("PIL._webp", "HAVE_WEBPMUX", None),
+ "transp_webp": ("PIL._webp", "HAVE_TRANSPARENCY", None),
+ "raqm": ("PIL._imagingft", "HAVE_RAQM", "raqm_version"),
+ "fribidi": ("PIL._imagingft", "HAVE_FRIBIDI", "fribidi_version"),
+ "harfbuzz": ("PIL._imagingft", "HAVE_HARFBUZZ", "harfbuzz_version"),
+ "libjpeg_turbo": ("PIL._imaging", "HAVE_LIBJPEGTURBO", "libjpeg_turbo_version"),
+ "libimagequant": ("PIL._imaging", "HAVE_LIBIMAGEQUANT", "imagequant_version"),
+ "xcb": ("PIL._imaging", "HAVE_XCB", None),
+}
+
+
+def check_feature(feature):
+ """
+ Checks if a feature is available.
+
+ :param feature: The feature to check for.
+ :returns: ``True`` if available, ``False`` if unavailable, ``None`` if unknown.
+ :raises ValueError: If the feature is not defined in this version of Pillow.
+ """
+ if feature not in features:
+ msg = f"Unknown feature {feature}"
+ raise ValueError(msg)
+
+ module, flag, ver = features[feature]
+
+ try:
+ imported_module = __import__(module, fromlist=["PIL"])
+ return getattr(imported_module, flag)
+ except ModuleNotFoundError:
+ return None
+ except ImportError as ex:
+ warnings.warn(str(ex))
+ return None
+
+
+def version_feature(feature):
+ """
+ :param feature: The feature to check for.
+ :returns: The version number as a string, or ``None`` if not available.
+ :raises ValueError: If the feature is not defined in this version of Pillow.
+ """
+ if not check_feature(feature):
+ return None
+
+ module, flag, ver = features[feature]
+
+ if ver is None:
+ return None
+
+ return getattr(__import__(module, fromlist=[ver]), ver)
+
+
+def get_supported_features():
+ """
+ :returns: A list of all supported features.
+ """
+ return [f for f in features if check_feature(f)]
+
+
+def check(feature):
+ """
+ :param feature: A module, codec, or feature name.
+ :returns:
+ ``True`` if the module, codec, or feature is available,
+ ``False`` or ``None`` otherwise.
+ """
+
+ if feature in modules:
+ return check_module(feature)
+ if feature in codecs:
+ return check_codec(feature)
+ if feature in features:
+ return check_feature(feature)
+ warnings.warn(f"Unknown feature '{feature}'.", stacklevel=2)
+ return False
+
+
+def version(feature):
+ """
+ :param feature:
+ The module, codec, or feature to check for.
+ :returns:
+ The version number as a string, or ``None`` if unknown or not available.
+ """
+ if feature in modules:
+ return version_module(feature)
+ if feature in codecs:
+ return version_codec(feature)
+ if feature in features:
+ return version_feature(feature)
+ return None
+
+
+def get_supported():
+ """
+ :returns: A list of all supported modules, features, and codecs.
+ """
+
+ ret = get_supported_modules()
+ ret.extend(get_supported_features())
+ ret.extend(get_supported_codecs())
+ return ret
+
+
+def pilinfo(out=None, supported_formats=True):
+ """
+ Prints information about this installation of Pillow.
+ This function can be called with ``python3 -m PIL``.
+
+ :param out:
+ The output stream to print to. Defaults to ``sys.stdout`` if ``None``.
+ :param supported_formats:
+ If ``True``, a list of all supported image file formats will be printed.
+ """
+
+ if out is None:
+ out = sys.stdout
+
+ Image.init()
+
+ print("-" * 68, file=out)
+ print(f"Pillow {PIL.__version__}", file=out)
+ py_version = sys.version.splitlines()
+ print(f"Python {py_version[0].strip()}", file=out)
+ for py_version in py_version[1:]:
+ print(f" {py_version.strip()}", file=out)
+ print("-" * 68, file=out)
+ print(
+ f"Python modules loaded from {os.path.dirname(Image.__file__)}",
+ file=out,
+ )
+ print(
+ f"Binary modules loaded from {sys.executable}",
+ file=out,
+ )
+ print("-" * 68, file=out)
+
+ for name, feature in [
+ ("pil", "PIL CORE"),
+ ("tkinter", "TKINTER"),
+ ("freetype2", "FREETYPE2"),
+ ("littlecms2", "LITTLECMS2"),
+ ("webp", "WEBP"),
+ ("transp_webp", "WEBP Transparency"),
+ ("webp_mux", "WEBPMUX"),
+ ("webp_anim", "WEBP Animation"),
+ ("jpg", "JPEG"),
+ ("jpg_2000", "OPENJPEG (JPEG2000)"),
+ ("zlib", "ZLIB (PNG/ZIP)"),
+ ("libtiff", "LIBTIFF"),
+ ("raqm", "RAQM (Bidirectional Text)"),
+ ("libimagequant", "LIBIMAGEQUANT (Quantization method)"),
+ ("xcb", "XCB (X protocol)"),
+ ]:
+ if check(name):
+ if name == "jpg" and check_feature("libjpeg_turbo"):
+ v = "libjpeg-turbo " + version_feature("libjpeg_turbo")
+ else:
+ v = version(name)
+ if v is not None:
+ version_static = name in ("pil", "jpg")
+ if name == "littlecms2":
+ # this check is also in src/_imagingcms.c:setup_module()
+ version_static = tuple(int(x) for x in v.split(".")) < (2, 7)
+ t = "compiled for" if version_static else "loaded"
+ if name == "raqm":
+ for f in ("fribidi", "harfbuzz"):
+ v2 = version_feature(f)
+ if v2 is not None:
+ v += f", {f} {v2}"
+ print("---", feature, "support ok,", t, v, file=out)
+ else:
+ print("---", feature, "support ok", file=out)
+ else:
+ print("***", feature, "support not installed", file=out)
+ print("-" * 68, file=out)
+
+ if supported_formats:
+ extensions = collections.defaultdict(list)
+ for ext, i in Image.EXTENSION.items():
+ extensions[i].append(ext)
+
+ for i in sorted(Image.ID):
+ line = f"{i}"
+ if i in Image.MIME:
+ line = f"{line} {Image.MIME[i]}"
+ print(line, file=out)
+
+ if i in extensions:
+ print(
+ "Extensions: {}".format(", ".join(sorted(extensions[i]))), file=out
+ )
+
+ features = []
+ if i in Image.OPEN:
+ features.append("open")
+ if i in Image.SAVE:
+ features.append("save")
+ if i in Image.SAVE_ALL:
+ features.append("save_all")
+ if i in Image.DECODERS:
+ features.append("decode")
+ if i in Image.ENCODERS:
+ features.append("encode")
+
+ print("Features: {}".format(", ".join(features)), file=out)
+ print("-" * 68, file=out)
diff --git a/contrib/python/Pillow/py3/README.md b/contrib/python/Pillow/py3/README.md
new file mode 100644
index 00000000000..e11bd2faa1d
--- /dev/null
+++ b/contrib/python/Pillow/py3/README.md
@@ -0,0 +1,121 @@
+<p align="center">
+ <img width="248" height="250" src="https://raw.githubusercontent.com/python-pillow/pillow-logo/main/pillow-logo-248x250.png" alt="Pillow logo">
+</p>
+
+# Pillow
+
+## Python Imaging Library (Fork)
+
+Pillow is the friendly PIL fork by [Jeffrey A. Clark (Alex) and
+contributors](https://github.com/python-pillow/Pillow/graphs/contributors).
+PIL is the Python Imaging Library by Fredrik Lundh and Contributors.
+As of 2019, Pillow development is
+[supported by Tidelift](https://tidelift.com/subscription/pkg/pypi-pillow?utm_source=pypi-pillow&utm_medium=readme&utm_campaign=enterprise).
+
+<table>
+ <tr>
+ <th>docs</th>
+ <td>
+ <a href="https://pillow.readthedocs.io/?badge=latest"><img
+ alt="Documentation Status"
+ src="https://readthedocs.org/projects/pillow/badge/?version=latest"></a>
+ </td>
+ </tr>
+ <tr>
+ <th>tests</th>
+ <td>
+ <a href="https://github.com/python-pillow/Pillow/actions/workflows/lint.yml"><img
+ alt="GitHub Actions build status (Lint)"
+ src="https://github.com/python-pillow/Pillow/workflows/Lint/badge.svg"></a>
+ <a href="https://github.com/python-pillow/Pillow/actions/workflows/test.yml"><img
+ alt="GitHub Actions build status (Test Linux and macOS)"
+ src="https://github.com/python-pillow/Pillow/workflows/Test/badge.svg"></a>
+ <a href="https://github.com/python-pillow/Pillow/actions/workflows/test-windows.yml"><img
+ alt="GitHub Actions build status (Test Windows)"
+ src="https://github.com/python-pillow/Pillow/workflows/Test%20Windows/badge.svg"></a>
+ <a href="https://github.com/python-pillow/Pillow/actions/workflows/test-mingw.yml"><img
+ alt="GitHub Actions build status (Test MinGW)"
+ src="https://github.com/python-pillow/Pillow/workflows/Test%20MinGW/badge.svg"></a>
+ <a href="https://github.com/python-pillow/Pillow/actions/workflows/test-cygwin.yml"><img
+ alt="GitHub Actions build status (Test Cygwin)"
+ src="https://github.com/python-pillow/Pillow/workflows/Test%20Cygwin/badge.svg"></a>
+ <a href="https://github.com/python-pillow/Pillow/actions/workflows/test-docker.yml"><img
+ alt="GitHub Actions build status (Test Docker)"
+ src="https://github.com/python-pillow/Pillow/workflows/Test%20Docker/badge.svg"></a>
+ <a href="https://ci.appveyor.com/project/python-pillow/Pillow"><img
+ alt="AppVeyor CI build status (Windows)"
+ src="https://img.shields.io/appveyor/build/python-pillow/Pillow/main.svg?label=Windows%20build"></a>
+ <a href="https://github.com/python-pillow/Pillow/actions/workflows/wheels.yml"><img
+ alt="GitHub Actions build status (Wheels)"
+ src="https://github.com/python-pillow/Pillow/workflows/Wheels/badge.svg"></a>
+ <a href="https://app.travis-ci.com/github/python-pillow/Pillow"><img
+ alt="Travis CI wheels build status (aarch64)"
+ src="https://img.shields.io/travis/com/python-pillow/Pillow/main.svg?label=aarch64%20wheels"></a>
+ <a href="https://app.codecov.io/gh/python-pillow/Pillow"><img
+ alt="Code coverage"
+ src="https://codecov.io/gh/python-pillow/Pillow/branch/main/graph/badge.svg"></a>
+ <a href="https://bugs.chromium.org/p/oss-fuzz/issues/list?sort=-opened&can=1&q=proj:pillow"><img
+ alt="Fuzzing Status"
+ src="https://oss-fuzz-build-logs.storage.googleapis.com/badges/pillow.svg"></a>
+ </td>
+ </tr>
+ <tr>
+ <th>package</th>
+ <td>
+ <a href="https://zenodo.org/badge/latestdoi/17549/python-pillow/Pillow"><img
+ alt="Zenodo"
+ src="https://zenodo.org/badge/17549/python-pillow/Pillow.svg"></a>
+ <a href="https://tidelift.com/subscription/pkg/pypi-pillow?utm_source=pypi-pillow&utm_medium=badge"><img
+ alt="Tidelift"
+ src="https://tidelift.com/badges/package/pypi/Pillow?style=flat"></a>
+ <a href="https://pypi.org/project/Pillow/"><img
+ alt="Newest PyPI version"
+ src="https://img.shields.io/pypi/v/pillow.svg"></a>
+ <a href="https://pypi.org/project/Pillow/"><img
+ alt="Number of PyPI downloads"
+ src="https://img.shields.io/pypi/dm/pillow.svg"></a>
+ <a href="https://www.bestpractices.dev/projects/6331"><img
+ alt="OpenSSF Best Practices"
+ src="https://www.bestpractices.dev/projects/6331/badge"></a>
+ </td>
+ </tr>
+ <tr>
+ <th>social</th>
+ <td>
+ <a href="https://gitter.im/python-pillow/Pillow?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge"><img
+ alt="Join the chat at https://gitter.im/python-pillow/Pillow"
+ src="https://badges.gitter.im/python-pillow/Pillow.svg"></a>
+ <a href="https://twitter.com/PythonPillow"><img
+ alt="Follow on https://twitter.com/PythonPillow"
+ src="https://img.shields.io/badge/tweet-on%20Twitter-00aced.svg"></a>
+ <a href="https://fosstodon.org/@pillow"><img
+ alt="Follow on https://fosstodon.org/@pillow"
+ src="https://img.shields.io/badge/publish-on%20Mastodon-595aff.svg"
+ rel="me"></a>
+ </td>
+ </tr>
+</table>
+
+## Overview
+
+The Python Imaging Library adds image processing capabilities to your Python interpreter.
+
+This library provides extensive file format support, an efficient internal representation, and fairly powerful image processing capabilities.
+
+The core image library is designed for fast access to data stored in a few basic pixel formats. It should provide a solid foundation for a general image processing tool.
+
+## More Information
+
+- [Documentation](https://pillow.readthedocs.io/)
+ - [Installation](https://pillow.readthedocs.io/en/latest/installation.html)
+ - [Handbook](https://pillow.readthedocs.io/en/latest/handbook/index.html)
+- [Contribute](https://github.com/python-pillow/Pillow/blob/main/.github/CONTRIBUTING.md)
+ - [Issues](https://github.com/python-pillow/Pillow/issues)
+ - [Pull requests](https://github.com/python-pillow/Pillow/pulls)
+- [Release notes](https://pillow.readthedocs.io/en/stable/releasenotes/index.html)
+- [Changelog](https://github.com/python-pillow/Pillow/blob/main/CHANGES.rst)
+ - [Pre-fork](https://github.com/python-pillow/Pillow/blob/main/CHANGES.rst#pre-fork)
+
+## Report a Vulnerability
+
+To report a security vulnerability, please follow the procedure described in the [Tidelift security policy](https://tidelift.com/docs/security).
diff --git a/contrib/python/Pillow/py3/RELEASING.md b/contrib/python/Pillow/py3/RELEASING.md
new file mode 100644
index 00000000000..0229dbbc1cc
--- /dev/null
+++ b/contrib/python/Pillow/py3/RELEASING.md
@@ -0,0 +1,134 @@
+# Release Checklist
+
+See https://pillow.readthedocs.io/en/stable/releasenotes/versioning.html for
+information about how the version numbers line up with releases.
+
+## Main Release
+
+Released quarterly on January 2nd, April 1st, July 1st and October 15th.
+
+* [ ] Open a release ticket e.g. https://github.com/python-pillow/Pillow/issues/3154
+* [ ] Develop and prepare release in `main` branch.
+* [ ] Check [GitHub Actions](https://github.com/python-pillow/Pillow/actions) and [AppVeyor](https://ci.appveyor.com/project/python-pillow/Pillow) to confirm passing tests in `main` branch.
+* [ ] Check that all of the wheel builds pass the tests in the [GitHub Actions "Wheels" workflow](https://github.com/python-pillow/Pillow/actions/workflows/wheels.yml) and [Travis CI](https://app.travis-ci.com/github/python-pillow/pillow) jobs by manually triggering them.
+* [ ] In compliance with [PEP 440](https://peps.python.org/pep-0440/), update version identifier in `src/PIL/_version.py`
+* [ ] Update `CHANGES.rst`.
+* [ ] Run pre-release check via `make release-test` in a freshly cloned repo.
+* [ ] Create branch and tag for release e.g.:
+ ```bash
+ git branch 5.2.x
+ git tag 5.2.0
+ git push --tags
+ ```
+* [ ] Create and check source distribution:
+ ```bash
+ make sdist
+ ```
+* [ ] Create [binary distributions](https://github.com/python-pillow/Pillow/blob/main/RELEASING.md#binary-distributions)
+* [ ] Check and upload all binaries and source distributions e.g.:
+ ```bash
+ python3 -m twine check --strict dist/*
+ python3 -m twine upload dist/Pillow-5.2.0*
+ ```
+* [ ] Publish the [release on GitHub](https://github.com/python-pillow/Pillow/releases)
+* [ ] In compliance with [PEP 440](https://peps.python.org/pep-0440/),
+ increment and append `.dev0` to version identifier in `src/PIL/_version.py` and then:
+ ```bash
+ git push --all
+ ```
+## Point Release
+
+Released as needed for security, installation or critical bug fixes.
+
+* [ ] Make necessary changes in `main` branch.
+* [ ] Update `CHANGES.rst`.
+* [ ] Check out release branch e.g.:
+ ```bash
+ git checkout -t remotes/origin/5.2.x
+ ```
+* [ ] Cherry pick individual commits from `main` branch to release branch e.g. `5.2.x`, then `git push`.
+* [ ] Check [GitHub Actions](https://github.com/python-pillow/Pillow/actions) and [AppVeyor](https://ci.appveyor.com/project/python-pillow/Pillow) to confirm passing tests in release branch e.g. `5.2.x`.
+* [ ] In compliance with [PEP 440](https://peps.python.org/pep-0440/), update version identifier in `src/PIL/_version.py`
+* [ ] Run pre-release check via `make release-test`.
+* [ ] Create tag for release e.g.:
+ ```bash
+ git tag 5.2.1
+ git push --tags
+ ```
+* [ ] Create and check source distribution:
+ ```bash
+ make sdist
+ ```
+* [ ] Create [binary distributions](https://github.com/python-pillow/Pillow/blob/main/RELEASING.md#binary-distributions)
+* [ ] Check and upload all binaries and source distributions e.g.:
+ ```bash
+ python3 -m twine check --strict dist/*
+ python3 -m twine upload dist/Pillow-5.2.1*
+ ```
+* [ ] Publish the [release on GitHub](https://github.com/python-pillow/Pillow/releases) and then:
+ ```bash
+ git push
+ ```
+
+## Embargoed Release
+
+Released as needed privately to individual vendors for critical security-related bug fixes.
+
+* [ ] Prepare patch for all versions that will get a fix. Test against local installations.
+* [ ] Commit against `main`, cherry pick to affected release branches.
+* [ ] Run local test matrix on each release & Python version.
+* [ ] Privately send to distros.
+* [ ] Run pre-release check via `make release-test`
+* [ ] Amend any commits with the CVE #
+* [ ] On release date, tag and push to GitHub.
+ ```bash
+ git checkout 2.5.x
+ git tag 2.5.3
+ git push origin --tags
+ ```
+* [ ] Create and check source distribution:
+ ```bash
+ make sdist
+ ```
+* [ ] Create [binary distributions](https://github.com/python-pillow/Pillow/blob/main/RELEASING.md#binary-distributions)
+* [ ] Publish the [release on GitHub](https://github.com/python-pillow/Pillow/releases) and then:
+ ```bash
+ git push origin 2.5.x
+ ```
+
+## Binary Distributions
+
+### macOS and Linux
+* [ ] Download wheels from the [GitHub Actions "Wheels" workflow](https://github.com/python-pillow/Pillow/actions/workflows/wheels.yml)
+ and copy into `dist/`. For example using [GitHub CLI](https://github.com/cli/cli):
+ ```bash
+ gh run download --dir dist
+ # select dist-x.y.z
+ ```
+* [ ] Download the Linux aarch64 wheels created by Travis CI from [GitHub releases](https://github.com/python-pillow/Pillow/releases)
+ and copy into `dist`.
+
+### Windows
+* [ ] Download the artifacts from the [GitHub Actions "Test Windows" workflow](https://github.com/python-pillow/Pillow/actions/workflows/test-windows.yml)
+ and copy into `dist/`. For example using [GitHub CLI](https://github.com/cli/cli):
+ ```bash
+ gh run download --dir dist
+ # select dist-x.y.z
+ ```
+
+## Publicize Release
+
+* [ ] Announce release availability via [Twitter](https://twitter.com/pythonpillow) and [Mastodon](https://fosstodon.org/@pillow) e.g. https://twitter.com/PythonPillow/status/1013789184354603010
+
+## Documentation
+
+* [ ] Make sure the [default version for Read the Docs](https://pillow.readthedocs.io/en/stable/) is up-to-date with the release changes
+
+## Docker Images
+
+* [ ] Update Pillow in the Docker Images repository
+ ```bash
+ git clone https://github.com/python-pillow/docker-images
+ cd docker-images
+ ./update-pillow-tag.sh [[release tag]]
+ ```
diff --git a/contrib/python/Pillow/py3/_imaging.c b/contrib/python/Pillow/py3/_imaging.c
new file mode 100644
index 00000000000..2270c77fe7e
--- /dev/null
+++ b/contrib/python/Pillow/py3/_imaging.c
@@ -0,0 +1,4406 @@
+/*
+ * The Python Imaging Library.
+ *
+ * the imaging library bindings
+ *
+ * history:
+ * 1995-09-24 fl Created
+ * 1996-03-24 fl Ready for first public release (release 0.0)
+ * 1996-03-25 fl Added fromstring (for Jack's "img" library)
+ * 1996-03-28 fl Added channel operations
+ * 1996-03-31 fl Added point operation
+ * 1996-04-08 fl Added new/new_block/new_array factories
+ * 1996-04-13 fl Added decoders
+ * 1996-05-04 fl Added palette hack
+ * 1996-05-12 fl Compile cleanly as C++
+ * 1996-05-19 fl Added matrix conversions, gradient fills
+ * 1996-05-27 fl Added display_mode
+ * 1996-07-22 fl Added getbbox, offset
+ * 1996-07-23 fl Added sequence semantics
+ * 1996-08-13 fl Added logical operators, point mode
+ * 1996-08-16 fl Modified paste interface
+ * 1996-09-06 fl Added putdata methods, use abstract interface
+ * 1996-11-01 fl Added xbm encoder
+ * 1996-11-04 fl Added experimental path stuff, draw_lines, etc
+ * 1996-12-10 fl Added zip decoder, crc32 interface
+ * 1996-12-14 fl Added modulo arithmetics
+ * 1996-12-29 fl Added zip encoder
+ * 1997-01-03 fl Added fli and msp decoders
+ * 1997-01-04 fl Added experimental sun_rle and tga_rle decoders
+ * 1997-01-05 fl Added gif encoder, getpalette hack
+ * 1997-02-23 fl Added histogram mask
+ * 1997-05-12 fl Minor tweaks to match the IFUNC95 interface
+ * 1997-05-21 fl Added noise generator, spread effect
+ * 1997-06-05 fl Added mandelbrot generator
+ * 1997-08-02 fl Modified putpalette to coerce image mode if necessary
+ * 1998-01-11 fl Added INT32 support
+ * 1998-01-22 fl Fixed draw_points to draw the last point too
+ * 1998-06-28 fl Added getpixel, getink, draw_ink
+ * 1998-07-12 fl Added getextrema
+ * 1998-07-17 fl Added point conversion to arbitrary formats
+ * 1998-09-21 fl Added support for resampling filters
+ * 1998-09-22 fl Added support for quad transform
+ * 1998-12-29 fl Added support for arcs, chords, and pieslices
+ * 1999-01-10 fl Added some experimental arrow graphics stuff
+ * 1999-02-06 fl Added draw_bitmap, font acceleration stuff
+ * 2001-04-17 fl Fixed some egcs compiler nits
+ * 2001-09-17 fl Added screen grab primitives (win32)
+ * 2002-03-09 fl Added stretch primitive
+ * 2002-03-10 fl Fixed filter handling in rotate
+ * 2002-06-06 fl Added I, F, and RGB support to putdata
+ * 2002-06-08 fl Added rankfilter
+ * 2002-06-09 fl Added support for user-defined filter kernels
+ * 2002-11-19 fl Added clipboard grab primitives (win32)
+ * 2002-12-11 fl Added draw context
+ * 2003-04-26 fl Tweaks for Python 2.3 beta 1
+ * 2003-05-21 fl Added createwindow primitive (win32)
+ * 2003-09-13 fl Added thread section hooks
+ * 2003-09-15 fl Added expand helper
+ * 2003-09-26 fl Added experimental LA support
+ * 2004-02-21 fl Handle zero-size images in quantize
+ * 2004-06-05 fl Added ptr attribute (used to access Imaging objects)
+ * 2004-06-05 fl Don't crash when fetching pixels from zero-wide images
+ * 2004-09-17 fl Added getcolors
+ * 2004-10-04 fl Added modefilter
+ * 2005-10-02 fl Added access proxy
+ * 2006-06-18 fl Always draw last point in polyline
+ *
+ * Copyright (c) 1997-2006 by Secret Labs AB
+ * Copyright (c) 1995-2006 by Fredrik Lundh
+ *
+ * See the README file for information on usage and redistribution.
+ */
+
+#define PY_SSIZE_T_CLEAN
+#include "Python.h"
+
+#ifdef HAVE_LIBJPEG
+#include "jconfig.h"
+#endif
+
+#ifdef HAVE_LIBZ
+#include "zlib.h"
+#endif
+
+#ifdef HAVE_LIBTIFF
+#ifndef _TIFFIO_
+#include <tiffio.h>
+#endif
+#endif
+
+#include "libImaging/Imaging.h"
+
+#define _USE_MATH_DEFINES
+#include <math.h>
+
+/* Configuration stuff. Feel free to undef things you don't need. */
+#define WITH_IMAGECHOPS /* ImageChops support */
+#define WITH_IMAGEDRAW /* ImageDraw support */
+#define WITH_MAPPING /* use memory mapping to read some file formats */
+#define WITH_IMAGEPATH /* ImagePath stuff */
+#define WITH_ARROW /* arrow graphics stuff (experimental) */
+#define WITH_EFFECTS /* special effects */
+#define WITH_QUANTIZE /* quantization support */
+#define WITH_RANKFILTER /* rank filter */
+#define WITH_MODEFILTER /* mode filter */
+#define WITH_THREADING /* "friendly" threading support */
+#define WITH_UNSHARPMASK /* Kevin Cazabon's unsharpmask module */
+
+#undef VERBOSE
+
+#define B16(p, i) ((((int)p[(i)]) << 8) + p[(i) + 1])
+#define L16(p, i) ((((int)p[(i) + 1]) << 8) + p[(i)])
+#define S16(v) ((v) < 32768 ? (v) : ((v)-65536))
+
+/* -------------------------------------------------------------------- */
+/* OBJECT ADMINISTRATION */
+/* -------------------------------------------------------------------- */
+
+typedef struct {
+ PyObject_HEAD Imaging image;
+ ImagingAccess access;
+} ImagingObject;
+
+static PyTypeObject Imaging_Type;
+
+#ifdef WITH_IMAGEDRAW
+
+typedef struct {
+ /* to write a character, cut out sxy from glyph data, place
+ at current position plus dxy, and advance by (dx, dy) */
+ int dx, dy;
+ int dx0, dy0, dx1, dy1;
+ int sx0, sy0, sx1, sy1;
+} Glyph;
+
+typedef struct {
+ PyObject_HEAD ImagingObject *ref;
+ Imaging bitmap;
+ int ysize;
+ int baseline;
+ Glyph glyphs[256];
+} ImagingFontObject;
+
+static PyTypeObject ImagingFont_Type;
+
+typedef struct {
+ PyObject_HEAD ImagingObject *image;
+ UINT8 ink[4];
+ int blend;
+} ImagingDrawObject;
+
+static PyTypeObject ImagingDraw_Type;
+
+#endif
+
+typedef struct {
+ PyObject_HEAD ImagingObject *image;
+ int readonly;
+} PixelAccessObject;
+
+static PyTypeObject PixelAccess_Type;
+
+PyObject *
+PyImagingNew(Imaging imOut) {
+ ImagingObject *imagep;
+
+ if (!imOut) {
+ return NULL;
+ }
+
+ imagep = PyObject_New(ImagingObject, &Imaging_Type);
+ if (imagep == NULL) {
+ ImagingDelete(imOut);
+ return NULL;
+ }
+
+#ifdef VERBOSE
+ printf("imaging %p allocated\n", imagep);
+#endif
+
+ imagep->image = imOut;
+ imagep->access = ImagingAccessNew(imOut);
+
+ return (PyObject *)imagep;
+}
+
+static void
+_dealloc(ImagingObject *imagep) {
+#ifdef VERBOSE
+ printf("imaging %p deleted\n", imagep);
+#endif
+
+ if (imagep->access) {
+ ImagingAccessDelete(imagep->image, imagep->access);
+ }
+ ImagingDelete(imagep->image);
+ PyObject_Del(imagep);
+}
+
+#define PyImaging_Check(op) (Py_TYPE(op) == &Imaging_Type)
+
+Imaging
+PyImaging_AsImaging(PyObject *op) {
+ if (!PyImaging_Check(op)) {
+ PyErr_BadInternalCall();
+ return NULL;
+ }
+
+ return ((ImagingObject *)op)->image;
+}
+
+/* -------------------------------------------------------------------- */
+/* THREAD HANDLING */
+/* -------------------------------------------------------------------- */
+
+void
+ImagingSectionEnter(ImagingSectionCookie *cookie) {
+#ifdef WITH_THREADING
+ *cookie = (PyThreadState *)PyEval_SaveThread();
+#endif
+}
+
+void
+ImagingSectionLeave(ImagingSectionCookie *cookie) {
+#ifdef WITH_THREADING
+ PyEval_RestoreThread((PyThreadState *)*cookie);
+#endif
+}
+
+/* -------------------------------------------------------------------- */
+/* BUFFER HANDLING */
+/* -------------------------------------------------------------------- */
+/* Python compatibility API */
+
+int
+PyImaging_CheckBuffer(PyObject *buffer) {
+ return PyObject_CheckBuffer(buffer);
+}
+
+int
+PyImaging_GetBuffer(PyObject *buffer, Py_buffer *view) {
+ /* must call check_buffer first! */
+ return PyObject_GetBuffer(buffer, view, PyBUF_SIMPLE);
+}
+
+/* -------------------------------------------------------------------- */
+/* EXCEPTION REROUTING */
+/* -------------------------------------------------------------------- */
+
+/* error messages */
+static const char *must_be_sequence = "argument must be a sequence";
+static const char *must_be_two_coordinates =
+ "coordinate list must contain exactly 2 coordinates";
+static const char *incorrectly_ordered_x_coordinate =
+ "x1 must be greater than or equal to x0";
+static const char *incorrectly_ordered_y_coordinate =
+ "y1 must be greater than or equal to y0";
+static const char *wrong_mode = "unrecognized image mode";
+static const char *wrong_raw_mode = "unrecognized raw mode";
+static const char *outside_image = "image index out of range";
+static const char *outside_palette = "palette index out of range";
+static const char *wrong_palette_size = "invalid palette size";
+static const char *no_palette = "image has no palette";
+static const char *readonly = "image is readonly";
+/* static const char* no_content = "image has no content"; */
+
+void *
+ImagingError_OSError(void) {
+ PyErr_SetString(PyExc_OSError, "error when accessing file");
+ return NULL;
+}
+
+void *
+ImagingError_MemoryError(void) {
+ return PyErr_NoMemory();
+}
+
+void *
+ImagingError_Mismatch(void) {
+ PyErr_SetString(PyExc_ValueError, "images do not match");
+ return NULL;
+}
+
+void *
+ImagingError_ModeError(void) {
+ PyErr_SetString(PyExc_ValueError, "image has wrong mode");
+ return NULL;
+}
+
+void *
+ImagingError_ValueError(const char *message) {
+ PyErr_SetString(
+ PyExc_ValueError, (message) ? (char *)message : "unrecognized argument value");
+ return NULL;
+}
+
+void
+ImagingError_Clear(void) {
+ PyErr_Clear();
+}
+
+/* -------------------------------------------------------------------- */
+/* HELPERS */
+/* -------------------------------------------------------------------- */
+
+static int
+getbands(const char *mode) {
+ Imaging im;
+ int bands;
+
+ /* FIXME: add primitive to libImaging to avoid extra allocation */
+ im = ImagingNew(mode, 0, 0);
+ if (!im) {
+ return -1;
+ }
+
+ bands = im->bands;
+
+ ImagingDelete(im);
+
+ return bands;
+}
+
+#define TYPE_UINT8 (0x100 | sizeof(UINT8))
+#define TYPE_INT32 (0x200 | sizeof(INT32))
+#define TYPE_FLOAT16 (0x500 | sizeof(FLOAT16))
+#define TYPE_FLOAT32 (0x300 | sizeof(FLOAT32))
+#define TYPE_DOUBLE (0x400 | sizeof(double))
+
+static void *
+getlist(PyObject *arg, Py_ssize_t *length, const char *wrong_length, int type) {
+ /* - allocates and returns a c array of the items in the
+ python sequence arg.
+ - the size of the returned array is in length
+ - all of the arg items must be numeric items of the type
+ specified in type
+ - sequence length is checked against the length parameter IF
+ an error parameter is passed in wrong_length
+ - caller is responsible for freeing the memory
+ */
+
+ Py_ssize_t i, n;
+ int itemp;
+ double dtemp;
+ FLOAT32 ftemp;
+ UINT8 *list;
+ PyObject *seq;
+ PyObject *op;
+
+ if (!PySequence_Check(arg)) {
+ PyErr_SetString(PyExc_TypeError, must_be_sequence);
+ return NULL;
+ }
+
+ n = PySequence_Size(arg);
+ if (length && wrong_length && n != *length) {
+ PyErr_SetString(PyExc_ValueError, wrong_length);
+ return NULL;
+ }
+
+ /* malloc check ok, type & ff is just a sizeof(something)
+ calloc checks for overflow */
+ list = calloc(n, type & 0xff);
+ if (!list) {
+ return ImagingError_MemoryError();
+ }
+
+ seq = PySequence_Fast(arg, must_be_sequence);
+ if (!seq) {
+ free(list);
+ return NULL;
+ }
+
+ for (i = 0; i < n; i++) {
+ op = PySequence_Fast_GET_ITEM(seq, i);
+ // DRY, branch prediction is going to work _really_ well
+ // on this switch. And 3 fewer loops to copy/paste.
+ switch (type) {
+ case TYPE_UINT8:
+ itemp = PyLong_AsLong(op);
+ list[i] = CLIP8(itemp);
+ break;
+ case TYPE_INT32:
+ itemp = PyLong_AsLong(op);
+ memcpy(list + i * sizeof(INT32), &itemp, sizeof(itemp));
+ break;
+ case TYPE_FLOAT32:
+ ftemp = (FLOAT32)PyFloat_AsDouble(op);
+ memcpy(list + i * sizeof(ftemp), &ftemp, sizeof(ftemp));
+ break;
+ case TYPE_DOUBLE:
+ dtemp = PyFloat_AsDouble(op);
+ memcpy(list + i * sizeof(dtemp), &dtemp, sizeof(dtemp));
+ break;
+ }
+ }
+
+ Py_DECREF(seq);
+
+ if (PyErr_Occurred()) {
+ free(list);
+ return NULL;
+ }
+
+ if (length) {
+ *length = n;
+ }
+
+ return list;
+}
+
+FLOAT32
+float16tofloat32(const FLOAT16 in) {
+ UINT32 t1;
+ UINT32 t2;
+ UINT32 t3;
+ FLOAT32 out[1] = {0};
+
+ t1 = in & 0x7fff; // Non-sign bits
+ t2 = in & 0x8000; // Sign bit
+ t3 = in & 0x7c00; // Exponent
+
+ t1 <<= 13; // Align mantissa on MSB
+ t2 <<= 16; // Shift sign bit into position
+
+ t1 += 0x38000000; // Adjust bias
+
+ t1 = (t3 == 0 ? 0 : t1); // Denormals-as-zero
+
+ t1 |= t2; // Re-insert sign bit
+
+ memcpy(out, &t1, 4);
+ return out[0];
+}
+
+static inline PyObject *
+getpixel(Imaging im, ImagingAccess access, int x, int y) {
+ union {
+ UINT8 b[4];
+ UINT16 h;
+ INT32 i;
+ FLOAT32 f;
+ } pixel;
+
+ if (x < 0) {
+ x = im->xsize + x;
+ }
+ if (y < 0) {
+ y = im->ysize + y;
+ }
+
+ if (x < 0 || x >= im->xsize || y < 0 || y >= im->ysize) {
+ PyErr_SetString(PyExc_IndexError, outside_image);
+ return NULL;
+ }
+
+ access->get_pixel(im, x, y, &pixel);
+
+ switch (im->type) {
+ case IMAGING_TYPE_UINT8:
+ switch (im->bands) {
+ case 1:
+ return PyLong_FromLong(pixel.b[0]);
+ case 2:
+ return Py_BuildValue("BB", pixel.b[0], pixel.b[1]);
+ case 3:
+ return Py_BuildValue("BBB", pixel.b[0], pixel.b[1], pixel.b[2]);
+ case 4:
+ return Py_BuildValue(
+ "BBBB", pixel.b[0], pixel.b[1], pixel.b[2], pixel.b[3]);
+ }
+ break;
+ case IMAGING_TYPE_INT32:
+ return PyLong_FromLong(pixel.i);
+ case IMAGING_TYPE_FLOAT32:
+ return PyFloat_FromDouble(pixel.f);
+ case IMAGING_TYPE_SPECIAL:
+ if (im->bands == 1) {
+ return PyLong_FromLong(pixel.h);
+ } else {
+ return Py_BuildValue("BBB", pixel.b[0], pixel.b[1], pixel.b[2]);
+ }
+ break;
+ }
+
+ /* unknown type */
+ Py_INCREF(Py_None);
+ return Py_None;
+}
+
+static char *
+getink(PyObject *color, Imaging im, char *ink) {
+ int g = 0, b = 0, a = 0;
+ double f = 0;
+ /* Windows 64 bit longs are 32 bits, and 0xFFFFFFFF (white) is a
+ Python long (not int) that raises an overflow error when trying
+ to return it into a 32 bit C long
+ */
+ PY_LONG_LONG r = 0;
+ FLOAT32 ftmp;
+ INT32 itmp;
+
+ /* fill ink buffer (four bytes) with something that can
+ be cast to either UINT8 or INT32 */
+
+ int rIsInt = 0;
+ int tupleSize = PyTuple_Check(color) ? PyTuple_GET_SIZE(color) : -1;
+ if (tupleSize == 1) {
+ color = PyTuple_GetItem(color, 0);
+ }
+ if (im->type == IMAGING_TYPE_UINT8 || im->type == IMAGING_TYPE_INT32 ||
+ im->type == IMAGING_TYPE_SPECIAL) {
+ if (PyLong_Check(color)) {
+ r = PyLong_AsLongLong(color);
+ if (r == -1 && PyErr_Occurred()) {
+ return NULL;
+ }
+ rIsInt = 1;
+ } else if (im->bands == 1) {
+ PyErr_SetString(
+ PyExc_TypeError, "color must be int or single-element tuple");
+ return NULL;
+ } else if (tupleSize == -1) {
+ PyErr_SetString(PyExc_TypeError, "color must be int or tuple");
+ return NULL;
+ }
+ }
+
+ switch (im->type) {
+ case IMAGING_TYPE_UINT8:
+ /* unsigned integer */
+ if (im->bands == 1) {
+ /* unsigned integer, single layer */
+ if (rIsInt != 1) {
+ if (tupleSize != 1) {
+ PyErr_SetString(PyExc_TypeError, "color must be int or single-element tuple");
+ return NULL;
+ } else if (!PyArg_ParseTuple(color, "L", &r)) {
+ return NULL;
+ }
+ }
+ ink[0] = (char)CLIP8(r);
+ ink[1] = ink[2] = ink[3] = 0;
+ } else {
+ if (rIsInt) {
+ /* compatibility: ABGR */
+ a = (UINT8)(r >> 24);
+ b = (UINT8)(r >> 16);
+ g = (UINT8)(r >> 8);
+ r = (UINT8)r;
+ } else {
+ a = 255;
+ if (im->bands == 2) {
+ if (tupleSize != 1 && tupleSize != 2) {
+ PyErr_SetString(PyExc_TypeError, "color must be int, or tuple of one or two elements");
+ return NULL;
+ } else if (!PyArg_ParseTuple(color, "L|i", &r, &a)) {
+ return NULL;
+ }
+ g = b = r;
+ } else {
+ if (tupleSize != 3 && tupleSize != 4) {
+ PyErr_SetString(PyExc_TypeError, "color must be int, or tuple of one, three or four elements");
+ return NULL;
+ } else if (!PyArg_ParseTuple(color, "Lii|i", &r, &g, &b, &a)) {
+ return NULL;
+ }
+ }
+ }
+ ink[0] = (char)CLIP8(r);
+ ink[1] = (char)CLIP8(g);
+ ink[2] = (char)CLIP8(b);
+ ink[3] = (char)CLIP8(a);
+ }
+ return ink;
+ case IMAGING_TYPE_INT32:
+ /* signed integer */
+ itmp = r;
+ memcpy(ink, &itmp, sizeof(itmp));
+ return ink;
+ case IMAGING_TYPE_FLOAT32:
+ /* floating point */
+ f = PyFloat_AsDouble(color);
+ if (f == -1.0 && PyErr_Occurred()) {
+ return NULL;
+ }
+ ftmp = f;
+ memcpy(ink, &ftmp, sizeof(ftmp));
+ return ink;
+ case IMAGING_TYPE_SPECIAL:
+ if (strncmp(im->mode, "I;16", 4) == 0) {
+ ink[0] = (UINT8)r;
+ ink[1] = (UINT8)(r >> 8);
+ ink[2] = ink[3] = 0;
+ return ink;
+ } else {
+ if (rIsInt) {
+ b = (UINT8)(r >> 16);
+ g = (UINT8)(r >> 8);
+ r = (UINT8)r;
+ } else if (tupleSize != 3) {
+ PyErr_SetString(PyExc_TypeError, "color must be int, or tuple of one or three elements");
+ return NULL;
+ } else if (!PyArg_ParseTuple(color, "iiL", &b, &g, &r)) {
+ return NULL;
+ }
+ if (!strcmp(im->mode, "BGR;15")) {
+ UINT16 v = ((((UINT16)r) << 7) & 0x7c00) +
+ ((((UINT16)g) << 2) & 0x03e0) +
+ ((((UINT16)b) >> 3) & 0x001f);
+
+ ink[0] = (UINT8)v;
+ ink[1] = (UINT8)(v >> 8);
+ ink[2] = ink[3] = 0;
+ return ink;
+ } else if (!strcmp(im->mode, "BGR;16")) {
+ UINT16 v = ((((UINT16)r) << 8) & 0xf800) +
+ ((((UINT16)g) << 3) & 0x07e0) +
+ ((((UINT16)b) >> 3) & 0x001f);
+ ink[0] = (UINT8)v;
+ ink[1] = (UINT8)(v >> 8);
+ ink[2] = ink[3] = 0;
+ return ink;
+ } else if (!strcmp(im->mode, "BGR;24")) {
+ ink[0] = (UINT8)b;
+ ink[1] = (UINT8)g;
+ ink[2] = (UINT8)r;
+ ink[3] = 0;
+ return ink;
+ }
+ }
+ }
+
+ PyErr_SetString(PyExc_ValueError, wrong_mode);
+ return NULL;
+}
+
+/* -------------------------------------------------------------------- */
+/* FACTORIES */
+/* -------------------------------------------------------------------- */
+
+static PyObject *
+_fill(PyObject *self, PyObject *args) {
+ char *mode;
+ int xsize, ysize;
+ PyObject *color;
+ char buffer[4];
+ Imaging im;
+
+ xsize = ysize = 256;
+ color = NULL;
+
+ if (!PyArg_ParseTuple(args, "s|(ii)O", &mode, &xsize, &ysize, &color)) {
+ return NULL;
+ }
+
+ im = ImagingNewDirty(mode, xsize, ysize);
+ if (!im) {
+ return NULL;
+ }
+
+ buffer[0] = buffer[1] = buffer[2] = buffer[3] = 0;
+ if (color) {
+ if (!getink(color, im, buffer)) {
+ ImagingDelete(im);
+ return NULL;
+ }
+ }
+
+ (void)ImagingFill(im, buffer);
+
+ return PyImagingNew(im);
+}
+
+static PyObject *
+_new(PyObject *self, PyObject *args) {
+ char *mode;
+ int xsize, ysize;
+
+ if (!PyArg_ParseTuple(args, "s(ii)", &mode, &xsize, &ysize)) {
+ return NULL;
+ }
+
+ return PyImagingNew(ImagingNew(mode, xsize, ysize));
+}
+
+static PyObject *
+_new_block(PyObject *self, PyObject *args) {
+ char *mode;
+ int xsize, ysize;
+
+ if (!PyArg_ParseTuple(args, "s(ii)", &mode, &xsize, &ysize)) {
+ return NULL;
+ }
+
+ return PyImagingNew(ImagingNewBlock(mode, xsize, ysize));
+}
+
+static PyObject *
+_linear_gradient(PyObject *self, PyObject *args) {
+ char *mode;
+
+ if (!PyArg_ParseTuple(args, "s", &mode)) {
+ return NULL;
+ }
+
+ return PyImagingNew(ImagingFillLinearGradient(mode));
+}
+
+static PyObject *
+_radial_gradient(PyObject *self, PyObject *args) {
+ char *mode;
+
+ if (!PyArg_ParseTuple(args, "s", &mode)) {
+ return NULL;
+ }
+
+ return PyImagingNew(ImagingFillRadialGradient(mode));
+}
+
+static PyObject *
+_alpha_composite(ImagingObject *self, PyObject *args) {
+ ImagingObject *imagep1;
+ ImagingObject *imagep2;
+
+ if (!PyArg_ParseTuple(
+ args, "O!O!", &Imaging_Type, &imagep1, &Imaging_Type, &imagep2)) {
+ return NULL;
+ }
+
+ return PyImagingNew(ImagingAlphaComposite(imagep1->image, imagep2->image));
+}
+
+static PyObject *
+_blend(ImagingObject *self, PyObject *args) {
+ ImagingObject *imagep1;
+ ImagingObject *imagep2;
+ double alpha;
+
+ alpha = 0.5;
+ if (!PyArg_ParseTuple(
+ args, "O!O!|d", &Imaging_Type, &imagep1, &Imaging_Type, &imagep2, &alpha)) {
+ return NULL;
+ }
+
+ return PyImagingNew(ImagingBlend(imagep1->image, imagep2->image, (float)alpha));
+}
+
+/* -------------------------------------------------------------------- */
+/* METHODS */
+/* -------------------------------------------------------------------- */
+
+static INT16 *
+_prepare_lut_table(PyObject *table, Py_ssize_t table_size) {
+ int i;
+ Py_buffer buffer_info;
+ INT32 data_type = TYPE_FLOAT32;
+ float item = 0;
+ void *table_data = NULL;
+ int free_table_data = 0;
+ INT16 *prepared;
+
+/* NOTE: This value should be the same as in ColorLUT.c */
+#define PRECISION_BITS (16 - 8 - 2)
+
+ const char *wrong_size =
+ ("The table should have table_channels * "
+ "size1D * size2D * size3D float items.");
+
+ if (PyObject_CheckBuffer(table)) {
+ if (!PyObject_GetBuffer(table, &buffer_info, PyBUF_CONTIG_RO | PyBUF_FORMAT)) {
+ if (buffer_info.ndim == 1 && buffer_info.shape[0] == table_size) {
+ if (strlen(buffer_info.format) == 1) {
+ switch (buffer_info.format[0]) {
+ case 'e':
+ data_type = TYPE_FLOAT16;
+ table_data = buffer_info.buf;
+ break;
+ case 'f':
+ data_type = TYPE_FLOAT32;
+ table_data = buffer_info.buf;
+ break;
+ case 'd':
+ data_type = TYPE_DOUBLE;
+ table_data = buffer_info.buf;
+ break;
+ }
+ }
+ }
+ PyBuffer_Release(&buffer_info);
+ }
+ }
+
+ if (!table_data) {
+ free_table_data = 1;
+ table_data = getlist(table, &table_size, wrong_size, TYPE_FLOAT32);
+ if (!table_data) {
+ return NULL;
+ }
+ }
+
+ /* malloc check ok, max is 2 * 4 * 65**3 = 2197000 */
+ prepared = (INT16 *)malloc(sizeof(INT16) * table_size);
+ if (!prepared) {
+ if (free_table_data) {
+ free(table_data);
+ }
+ return (INT16 *)ImagingError_MemoryError();
+ }
+
+ for (i = 0; i < table_size; i++) {
+ FLOAT16 htmp;
+ double dtmp;
+ switch (data_type) {
+ case TYPE_FLOAT16:
+ memcpy(&htmp, ((char *)table_data) + i * sizeof(htmp), sizeof(htmp));
+ item = float16tofloat32(htmp);
+ break;
+ case TYPE_FLOAT32:
+ memcpy(
+ &item, ((char *)table_data) + i * sizeof(FLOAT32), sizeof(FLOAT32));
+ break;
+ case TYPE_DOUBLE:
+ memcpy(&dtmp, ((char *)table_data) + i * sizeof(dtmp), sizeof(dtmp));
+ item = (FLOAT32)dtmp;
+ break;
+ }
+ /* Max value for INT16 */
+ if (item >= (0x7fff - 0.5) / (255 << PRECISION_BITS)) {
+ prepared[i] = 0x7fff;
+ continue;
+ }
+ /* Min value for INT16 */
+ if (item <= (-0x8000 + 0.5) / (255 << PRECISION_BITS)) {
+ prepared[i] = -0x8000;
+ continue;
+ }
+ if (item < 0) {
+ prepared[i] = item * (255 << PRECISION_BITS) - 0.5;
+ } else {
+ prepared[i] = item * (255 << PRECISION_BITS) + 0.5;
+ }
+ }
+
+#undef PRECISION_BITS
+ if (free_table_data) {
+ free(table_data);
+ }
+ return prepared;
+}
+
+static PyObject *
+_color_lut_3d(ImagingObject *self, PyObject *args) {
+ char *mode;
+ int filter;
+ int table_channels;
+ int size1D, size2D, size3D;
+ PyObject *table;
+
+ INT16 *prepared_table;
+ Imaging imOut;
+
+ if (!PyArg_ParseTuple(
+ args,
+ "siiiiiO:color_lut_3d",
+ &mode,
+ &filter,
+ &table_channels,
+ &size1D,
+ &size2D,
+ &size3D,
+ &table)) {
+ return NULL;
+ }
+
+ /* actually, it is trilinear */
+ if (filter != IMAGING_TRANSFORM_BILINEAR) {
+ PyErr_SetString(PyExc_ValueError, "Only LINEAR filter is supported.");
+ return NULL;
+ }
+
+ if (1 > table_channels || table_channels > 4) {
+ PyErr_SetString(PyExc_ValueError, "table_channels should be from 1 to 4");
+ return NULL;
+ }
+
+ if (2 > size1D || size1D > 65 || 2 > size2D || size2D > 65 || 2 > size3D ||
+ size3D > 65) {
+ PyErr_SetString(
+ PyExc_ValueError, "Table size in any dimension should be from 2 to 65");
+ return NULL;
+ }
+
+ prepared_table =
+ _prepare_lut_table(table, table_channels * size1D * size2D * size3D);
+ if (!prepared_table) {
+ return NULL;
+ }
+
+ imOut = ImagingNewDirty(mode, self->image->xsize, self->image->ysize);
+ if (!imOut) {
+ free(prepared_table);
+ return NULL;
+ }
+
+ if (!ImagingColorLUT3D_linear(
+ imOut,
+ self->image,
+ table_channels,
+ size1D,
+ size2D,
+ size3D,
+ prepared_table)) {
+ free(prepared_table);
+ ImagingDelete(imOut);
+ return NULL;
+ }
+
+ free(prepared_table);
+
+ return PyImagingNew(imOut);
+}
+
+static PyObject *
+_convert(ImagingObject *self, PyObject *args) {
+ char *mode;
+ int dither = 0;
+ ImagingObject *paletteimage = NULL;
+
+ if (!PyArg_ParseTuple(args, "s|iO", &mode, &dither, &paletteimage)) {
+ return NULL;
+ }
+ if (paletteimage != NULL) {
+ if (!PyImaging_Check(paletteimage)) {
+ PyObject_Print((PyObject *)paletteimage, stderr, 0);
+ PyErr_SetString(
+ PyExc_ValueError, "palette argument must be image with mode 'P'");
+ return NULL;
+ }
+ if (paletteimage->image->palette == NULL) {
+ PyErr_SetString(PyExc_ValueError, "null palette");
+ return NULL;
+ }
+ }
+
+ return PyImagingNew(ImagingConvert(
+ self->image, mode, paletteimage ? paletteimage->image->palette : NULL, dither));
+}
+
+static PyObject *
+_convert2(ImagingObject *self, PyObject *args) {
+ ImagingObject *imagep1;
+ ImagingObject *imagep2;
+ if (!PyArg_ParseTuple(
+ args, "O!O!", &Imaging_Type, &imagep1, &Imaging_Type, &imagep2)) {
+ return NULL;
+ }
+
+ if (!ImagingConvert2(imagep1->image, imagep2->image)) {
+ return NULL;
+ }
+
+ Py_INCREF(Py_None);
+ return Py_None;
+}
+
+static PyObject *
+_convert_matrix(ImagingObject *self, PyObject *args) {
+ char *mode;
+ float m[12];
+ if (!PyArg_ParseTuple(args, "s(ffff)", &mode, m + 0, m + 1, m + 2, m + 3)) {
+ PyErr_Clear();
+ if (!PyArg_ParseTuple(
+ args,
+ "s(ffffffffffff)",
+ &mode,
+ m + 0,
+ m + 1,
+ m + 2,
+ m + 3,
+ m + 4,
+ m + 5,
+ m + 6,
+ m + 7,
+ m + 8,
+ m + 9,
+ m + 10,
+ m + 11)) {
+ return NULL;
+ }
+ }
+
+ return PyImagingNew(ImagingConvertMatrix(self->image, mode, m));
+}
+
+static PyObject *
+_convert_transparent(ImagingObject *self, PyObject *args) {
+ char *mode;
+ int r, g, b;
+ if (PyArg_ParseTuple(args, "s(iii)", &mode, &r, &g, &b)) {
+ return PyImagingNew(ImagingConvertTransparent(self->image, mode, r, g, b));
+ }
+ PyErr_Clear();
+ if (PyArg_ParseTuple(args, "si", &mode, &r)) {
+ return PyImagingNew(ImagingConvertTransparent(self->image, mode, r, 0, 0));
+ }
+ return NULL;
+}
+
+static PyObject *
+_copy(ImagingObject *self, PyObject *args) {
+ if (!PyArg_ParseTuple(args, "")) {
+ return NULL;
+ }
+
+ return PyImagingNew(ImagingCopy(self->image));
+}
+
+static PyObject *
+_crop(ImagingObject *self, PyObject *args) {
+ int x0, y0, x1, y1;
+ if (!PyArg_ParseTuple(args, "(iiii)", &x0, &y0, &x1, &y1)) {
+ return NULL;
+ }
+
+ return PyImagingNew(ImagingCrop(self->image, x0, y0, x1, y1));
+}
+
+static PyObject *
+_expand_image(ImagingObject *self, PyObject *args) {
+ int x, y;
+ if (!PyArg_ParseTuple(args, "ii", &x, &y)) {
+ return NULL;
+ }
+
+ return PyImagingNew(ImagingExpand(self->image, x, y));
+}
+
+static PyObject *
+_filter(ImagingObject *self, PyObject *args) {
+ PyObject *imOut;
+ Py_ssize_t kernelsize;
+ FLOAT32 *kerneldata;
+
+ int xsize, ysize, i;
+ float divisor, offset;
+ PyObject *kernel = NULL;
+ if (!PyArg_ParseTuple(
+ args, "(ii)ffO", &xsize, &ysize, &divisor, &offset, &kernel)) {
+ return NULL;
+ }
+
+ /* get user-defined kernel */
+ kerneldata = getlist(kernel, &kernelsize, NULL, TYPE_FLOAT32);
+ if (!kerneldata) {
+ return NULL;
+ }
+ if (kernelsize != (Py_ssize_t)xsize * (Py_ssize_t)ysize) {
+ free(kerneldata);
+ return ImagingError_ValueError("bad kernel size");
+ }
+
+ for (i = 0; i < kernelsize; ++i) {
+ kerneldata[i] /= divisor;
+ }
+
+ imOut = PyImagingNew(ImagingFilter(self->image, xsize, ysize, kerneldata, offset));
+
+ free(kerneldata);
+
+ return imOut;
+}
+
+#ifdef WITH_UNSHARPMASK
+static PyObject *
+_gaussian_blur(ImagingObject *self, PyObject *args) {
+ Imaging imIn;
+ Imaging imOut;
+
+ float xradius, yradius;
+ int passes = 3;
+ if (!PyArg_ParseTuple(args, "(ff)|i", &xradius, &yradius, &passes)) {
+ return NULL;
+ }
+
+ imIn = self->image;
+ imOut = ImagingNewDirty(imIn->mode, imIn->xsize, imIn->ysize);
+ if (!imOut) {
+ return NULL;
+ }
+
+ if (!ImagingGaussianBlur(imOut, imIn, xradius, yradius, passes)) {
+ ImagingDelete(imOut);
+ return NULL;
+ }
+
+ return PyImagingNew(imOut);
+}
+#endif
+
+static PyObject *
+_getpalette(ImagingObject *self, PyObject *args) {
+ PyObject *palette;
+ int palettesize;
+ int bits;
+ ImagingShuffler pack;
+
+ char *mode = "RGB";
+ char *rawmode = "RGB";
+ if (!PyArg_ParseTuple(args, "|ss", &mode, &rawmode)) {
+ return NULL;
+ }
+
+ if (!self->image->palette) {
+ PyErr_SetString(PyExc_ValueError, no_palette);
+ return NULL;
+ }
+
+ pack = ImagingFindPacker(mode, rawmode, &bits);
+ if (!pack) {
+ PyErr_SetString(PyExc_ValueError, wrong_raw_mode);
+ return NULL;
+ }
+
+ palettesize = self->image->palette->size;
+ palette = PyBytes_FromStringAndSize(NULL, palettesize * bits / 8);
+ if (!palette) {
+ return NULL;
+ }
+
+ pack(
+ (UINT8 *)PyBytes_AsString(palette), self->image->palette->palette, palettesize);
+
+ return palette;
+}
+
+static PyObject *
+_getpalettemode(ImagingObject *self) {
+ if (!self->image->palette) {
+ PyErr_SetString(PyExc_ValueError, no_palette);
+ return NULL;
+ }
+
+ return PyUnicode_FromString(self->image->palette->mode);
+}
+
+static inline int
+_getxy(PyObject *xy, int *x, int *y) {
+ PyObject *value;
+
+ if (!PyTuple_Check(xy) || PyTuple_GET_SIZE(xy) != 2) {
+ goto badarg;
+ }
+
+ value = PyTuple_GET_ITEM(xy, 0);
+ if (PyLong_Check(value)) {
+ *x = PyLong_AS_LONG(value);
+ } else if (PyFloat_Check(value)) {
+ *x = (int)PyFloat_AS_DOUBLE(value);
+ } else {
+ PyObject *int_value = PyObject_CallMethod(value, "__int__", NULL);
+ if (int_value != NULL && PyLong_Check(int_value)) {
+ *x = PyLong_AS_LONG(int_value);
+ } else {
+ goto badval;
+ }
+ }
+
+ value = PyTuple_GET_ITEM(xy, 1);
+ if (PyLong_Check(value)) {
+ *y = PyLong_AS_LONG(value);
+ } else if (PyFloat_Check(value)) {
+ *y = (int)PyFloat_AS_DOUBLE(value);
+ } else {
+ PyObject *int_value = PyObject_CallMethod(value, "__int__", NULL);
+ if (int_value != NULL && PyLong_Check(int_value)) {
+ *y = PyLong_AS_LONG(int_value);
+ } else {
+ goto badval;
+ }
+ }
+
+ return 0;
+
+badarg:
+ PyErr_SetString(PyExc_TypeError, "argument must be sequence of length 2");
+ return -1;
+
+badval:
+ PyErr_SetString(PyExc_TypeError, "an integer is required");
+ return -1;
+}
+
+static PyObject *
+_getpixel(ImagingObject *self, PyObject *args) {
+ PyObject *xy;
+ int x, y;
+
+ if (PyTuple_GET_SIZE(args) != 1) {
+ PyErr_SetString(PyExc_TypeError, "argument 1 must be sequence of length 2");
+ return NULL;
+ }
+
+ xy = PyTuple_GET_ITEM(args, 0);
+
+ if (_getxy(xy, &x, &y)) {
+ return NULL;
+ }
+
+ if (self->access == NULL) {
+ Py_INCREF(Py_None);
+ return Py_None;
+ }
+
+ return getpixel(self->image, self->access, x, y);
+}
+
+union hist_extrema {
+ UINT8 u[2];
+ INT32 i[2];
+ FLOAT32 f[2];
+};
+
+static union hist_extrema *
+parse_histogram_extremap(
+ ImagingObject *self, PyObject *extremap, union hist_extrema *ep) {
+ int i0, i1;
+ double f0, f1;
+
+ if (extremap) {
+ switch (self->image->type) {
+ case IMAGING_TYPE_UINT8:
+ if (!PyArg_ParseTuple(extremap, "ii", &i0, &i1)) {
+ return NULL;
+ }
+ ep->u[0] = CLIP8(i0);
+ ep->u[1] = CLIP8(i1);
+ break;
+ case IMAGING_TYPE_INT32:
+ if (!PyArg_ParseTuple(extremap, "ii", &i0, &i1)) {
+ return NULL;
+ }
+ ep->i[0] = i0;
+ ep->i[1] = i1;
+ break;
+ case IMAGING_TYPE_FLOAT32:
+ if (!PyArg_ParseTuple(extremap, "dd", &f0, &f1)) {
+ return NULL;
+ }
+ ep->f[0] = (FLOAT32)f0;
+ ep->f[1] = (FLOAT32)f1;
+ break;
+ default:
+ return NULL;
+ }
+ } else {
+ return NULL;
+ }
+ return ep;
+}
+
+static PyObject *
+_histogram(ImagingObject *self, PyObject *args) {
+ ImagingHistogram h;
+ PyObject *list;
+ int i;
+ union hist_extrema extrema;
+ union hist_extrema *ep;
+
+ PyObject *extremap = NULL;
+ ImagingObject *maskp = NULL;
+ if (!PyArg_ParseTuple(args, "|OO!", &extremap, &Imaging_Type, &maskp)) {
+ return NULL;
+ }
+
+ /* Using a var to avoid allocations. */
+ ep = parse_histogram_extremap(self, extremap, &extrema);
+ h = ImagingGetHistogram(self->image, (maskp) ? maskp->image : NULL, ep);
+
+ if (!h) {
+ return NULL;
+ }
+
+ /* Build an integer list containing the histogram */
+ list = PyList_New(h->bands * 256);
+ if (list == NULL) {
+ ImagingHistogramDelete(h);
+ return NULL;
+ }
+ for (i = 0; i < h->bands * 256; i++) {
+ PyObject *item;
+ item = PyLong_FromLong(h->histogram[i]);
+ if (item == NULL) {
+ Py_DECREF(list);
+ list = NULL;
+ break;
+ }
+ PyList_SetItem(list, i, item);
+ }
+
+ /* Destroy the histogram structure */
+ ImagingHistogramDelete(h);
+
+ return list;
+}
+
+static PyObject *
+_entropy(ImagingObject *self, PyObject *args) {
+ ImagingHistogram h;
+ int idx, length;
+ long sum;
+ double entropy, fsum, p;
+ union hist_extrema extrema;
+ union hist_extrema *ep;
+
+ PyObject *extremap = NULL;
+ ImagingObject *maskp = NULL;
+ if (!PyArg_ParseTuple(args, "|OO!", &extremap, &Imaging_Type, &maskp)) {
+ return NULL;
+ }
+
+ /* Using a local var to avoid allocations. */
+ ep = parse_histogram_extremap(self, extremap, &extrema);
+ h = ImagingGetHistogram(self->image, (maskp) ? maskp->image : NULL, ep);
+
+ if (!h) {
+ return NULL;
+ }
+
+ /* Calculate the histogram entropy */
+ /* First, sum the histogram data */
+ length = h->bands * 256;
+ sum = 0;
+ for (idx = 0; idx < length; idx++) {
+ sum += h->histogram[idx];
+ }
+
+ /* Next, normalize the histogram data, */
+ /* using the histogram sum value */
+ fsum = (double)sum;
+ entropy = 0.0;
+ for (idx = 0; idx < length; idx++) {
+ p = (double)h->histogram[idx] / fsum;
+ if (p != 0.0) {
+ entropy += p * log(p) * M_LOG2E;
+ }
+ }
+
+ /* Destroy the histogram structure */
+ ImagingHistogramDelete(h);
+
+ return PyFloat_FromDouble(-entropy);
+}
+
+#ifdef WITH_MODEFILTER
+static PyObject *
+_modefilter(ImagingObject *self, PyObject *args) {
+ int size;
+ if (!PyArg_ParseTuple(args, "i", &size)) {
+ return NULL;
+ }
+
+ return PyImagingNew(ImagingModeFilter(self->image, size));
+}
+#endif
+
+static PyObject *
+_offset(ImagingObject *self, PyObject *args) {
+ int xoffset, yoffset;
+ if (!PyArg_ParseTuple(args, "ii", &xoffset, &yoffset)) {
+ return NULL;
+ }
+
+ return PyImagingNew(ImagingOffset(self->image, xoffset, yoffset));
+}
+
+static PyObject *
+_paste(ImagingObject *self, PyObject *args) {
+ int status;
+ char ink[4];
+
+ PyObject *source;
+ int x0, y0, x1, y1;
+ ImagingObject *maskp = NULL;
+ if (!PyArg_ParseTuple(
+ args, "O(iiii)|O!", &source, &x0, &y0, &x1, &y1, &Imaging_Type, &maskp)) {
+ return NULL;
+ }
+
+ if (PyImaging_Check(source)) {
+ status = ImagingPaste(
+ self->image,
+ PyImaging_AsImaging(source),
+ (maskp) ? maskp->image : NULL,
+ x0,
+ y0,
+ x1,
+ y1);
+
+ } else {
+ if (!getink(source, self->image, ink)) {
+ return NULL;
+ }
+ status = ImagingFill2(
+ self->image, ink, (maskp) ? maskp->image : NULL, x0, y0, x1, y1);
+ }
+
+ if (status < 0) {
+ return NULL;
+ }
+
+ Py_INCREF(Py_None);
+ return Py_None;
+}
+
+static PyObject *
+_point(ImagingObject *self, PyObject *args) {
+ static const char *wrong_number = "wrong number of lut entries";
+
+ Py_ssize_t n;
+ int i, bands;
+ Imaging im;
+
+ PyObject *list;
+ char *mode;
+ if (!PyArg_ParseTuple(args, "Oz", &list, &mode)) {
+ return NULL;
+ }
+
+ if (mode && !strcmp(mode, "F")) {
+ FLOAT32 *data;
+
+ /* map from 8-bit data to floating point */
+ n = 256;
+ data = getlist(list, &n, wrong_number, TYPE_FLOAT32);
+ if (!data) {
+ return NULL;
+ }
+ im = ImagingPoint(self->image, mode, (void *)data);
+ free(data);
+
+ } else if (!strcmp(self->image->mode, "I") && mode && !strcmp(mode, "L")) {
+ UINT8 *data;
+
+ /* map from 16-bit subset of 32-bit data to 8-bit */
+ /* FIXME: support arbitrary number of entries (requires API change) */
+ n = 65536;
+ data = getlist(list, &n, wrong_number, TYPE_UINT8);
+ if (!data) {
+ return NULL;
+ }
+ im = ImagingPoint(self->image, mode, (void *)data);
+ free(data);
+
+ } else {
+ INT32 *data;
+ UINT8 lut[1024];
+
+ if (mode) {
+ bands = getbands(mode);
+ if (bands < 0) {
+ return NULL;
+ }
+ } else {
+ bands = self->image->bands;
+ }
+
+ /* map to integer data */
+ n = 256 * bands;
+ data = getlist(list, &n, wrong_number, TYPE_INT32);
+ if (!data) {
+ return NULL;
+ }
+
+ if (mode && !strcmp(mode, "I")) {
+ im = ImagingPoint(self->image, mode, (void *)data);
+ } else if (mode && bands > 1) {
+ for (i = 0; i < 256; i++) {
+ lut[i * 4] = CLIP8(data[i]);
+ lut[i * 4 + 1] = CLIP8(data[i + 256]);
+ lut[i * 4 + 2] = CLIP8(data[i + 512]);
+ if (n > 768) {
+ lut[i * 4 + 3] = CLIP8(data[i + 768]);
+ }
+ }
+ im = ImagingPoint(self->image, mode, (void *)lut);
+ } else {
+ /* map individual bands */
+ for (i = 0; i < n; i++) {
+ lut[i] = CLIP8(data[i]);
+ }
+ im = ImagingPoint(self->image, mode, (void *)lut);
+ }
+ free(data);
+ }
+
+ return PyImagingNew(im);
+}
+
+static PyObject *
+_point_transform(ImagingObject *self, PyObject *args) {
+ double scale = 1.0;
+ double offset = 0.0;
+ if (!PyArg_ParseTuple(args, "|dd", &scale, &offset)) {
+ return NULL;
+ }
+
+ return PyImagingNew(ImagingPointTransform(self->image, scale, offset));
+}
+
+static PyObject *
+_putdata(ImagingObject *self, PyObject *args) {
+ Imaging image;
+ // i & n are # pixels, require py_ssize_t. x can be as large as n. y, just because.
+ Py_ssize_t n, i, x, y;
+
+ PyObject *data;
+ PyObject *seq = NULL;
+ PyObject *op;
+ double scale = 1.0;
+ double offset = 0.0;
+
+ if (!PyArg_ParseTuple(args, "O|dd", &data, &scale, &offset)) {
+ return NULL;
+ }
+
+ if (!PySequence_Check(data)) {
+ PyErr_SetString(PyExc_TypeError, must_be_sequence);
+ return NULL;
+ }
+
+ image = self->image;
+
+ n = PyObject_Length(data);
+ if (n > (Py_ssize_t)image->xsize * (Py_ssize_t)image->ysize) {
+ PyErr_SetString(PyExc_TypeError, "too many data entries");
+ return NULL;
+ }
+
+#define set_value_to_item(seq, i) \
+op = PySequence_Fast_GET_ITEM(seq, i); \
+if (PySequence_Check(op)) { \
+ PyErr_SetString(PyExc_TypeError, "sequence must be flattened"); \
+ return NULL; \
+} else { \
+ value = PyFloat_AsDouble(op); \
+}
+ if (image->image8) {
+ if (PyBytes_Check(data)) {
+ unsigned char *p;
+ p = (unsigned char *)PyBytes_AS_STRING(data);
+ if (scale == 1.0 && offset == 0.0) {
+ /* Plain string data */
+ for (i = y = 0; i < n; i += image->xsize, y++) {
+ x = n - i;
+ if (x > (int)image->xsize) {
+ x = image->xsize;
+ }
+ memcpy(image->image8[y], p + i, x);
+ }
+ } else {
+ /* Scaled and clipped string data */
+ for (i = x = y = 0; i < n; i++) {
+ image->image8[y][x] = CLIP8((int)(p[i] * scale + offset));
+ if (++x >= (int)image->xsize) {
+ x = 0, y++;
+ }
+ }
+ }
+ } else {
+ seq = PySequence_Fast(data, must_be_sequence);
+ if (!seq) {
+ PyErr_SetString(PyExc_TypeError, must_be_sequence);
+ return NULL;
+ }
+ double value;
+ if (image->bands == 1) {
+ int bigendian = 0;
+ if (image->type == IMAGING_TYPE_SPECIAL) {
+ // I;16*
+ bigendian = strcmp(image->mode, "I;16B") == 0;
+ }
+ for (i = x = y = 0; i < n; i++) {
+ set_value_to_item(seq, i);
+ if (scale != 1.0 || offset != 0.0) {
+ value = value * scale + offset;
+ }
+ if (image->type == IMAGING_TYPE_SPECIAL) {
+ image->image8[y][x * 2 + (bigendian ? 1 : 0)] = CLIP8((int)value % 256);
+ image->image8[y][x * 2 + (bigendian ? 0 : 1)] = CLIP8((int)value >> 8);
+ } else {
+ image->image8[y][x] = (UINT8)CLIP8(value);
+ }
+ if (++x >= (int)image->xsize) {
+ x = 0, y++;
+ }
+ }
+ } else {
+ // BGR;*
+ int b;
+ for (i = x = y = 0; i < n; i++) {
+ char ink[4];
+
+ op = PySequence_Fast_GET_ITEM(seq, i);
+ if (!op || !getink(op, image, ink)) {
+ Py_DECREF(seq);
+ return NULL;
+ }
+ /* FIXME: what about scale and offset? */
+ for (b = 0; b < image->pixelsize; b++) {
+ image->image8[y][x * image->pixelsize + b] = ink[b];
+ }
+ if (++x >= (int)image->xsize) {
+ x = 0, y++;
+ }
+ }
+ }
+ PyErr_Clear(); /* Avoid weird exceptions */
+ }
+ } else {
+ /* 32-bit images */
+ seq = PySequence_Fast(data, must_be_sequence);
+ if (!seq) {
+ PyErr_SetString(PyExc_TypeError, must_be_sequence);
+ return NULL;
+ }
+ switch (image->type) {
+ case IMAGING_TYPE_INT32:
+ for (i = x = y = 0; i < n; i++) {
+ double value;
+ set_value_to_item(seq, i);
+ IMAGING_PIXEL_INT32(image, x, y) =
+ (INT32)(value * scale + offset);
+ if (++x >= (int)image->xsize) {
+ x = 0, y++;
+ }
+ }
+ PyErr_Clear(); /* Avoid weird exceptions */
+ break;
+ case IMAGING_TYPE_FLOAT32:
+ for (i = x = y = 0; i < n; i++) {
+ double value;
+ set_value_to_item(seq, i);
+ IMAGING_PIXEL_FLOAT32(image, x, y) =
+ (FLOAT32)(value * scale + offset);
+ if (++x >= (int)image->xsize) {
+ x = 0, y++;
+ }
+ }
+ PyErr_Clear(); /* Avoid weird exceptions */
+ break;
+ default:
+ for (i = x = y = 0; i < n; i++) {
+ union {
+ char ink[4];
+ INT32 inkint;
+ } u;
+
+ u.inkint = 0;
+
+ op = PySequence_Fast_GET_ITEM(seq, i);
+ if (!op || !getink(op, image, u.ink)) {
+ Py_DECREF(seq);
+ return NULL;
+ }
+ /* FIXME: what about scale and offset? */
+ image->image32[y][x] = u.inkint;
+ if (++x >= (int)image->xsize) {
+ x = 0, y++;
+ }
+ }
+ PyErr_Clear(); /* Avoid weird exceptions */
+ break;
+ }
+ }
+
+ Py_XDECREF(seq);
+
+ Py_INCREF(Py_None);
+ return Py_None;
+}
+
+#ifdef WITH_QUANTIZE
+
+static PyObject *
+_quantize(ImagingObject *self, PyObject *args) {
+ int colours = 256;
+ int method = 0;
+ int kmeans = 0;
+ if (!PyArg_ParseTuple(args, "|iii", &colours, &method, &kmeans)) {
+ return NULL;
+ }
+
+ if (!self->image->xsize || !self->image->ysize) {
+ /* no content; return an empty image */
+ return PyImagingNew(ImagingNew("P", self->image->xsize, self->image->ysize));
+ }
+
+ return PyImagingNew(ImagingQuantize(self->image, colours, method, kmeans));
+}
+#endif
+
+static PyObject *
+_putpalette(ImagingObject *self, PyObject *args) {
+ ImagingShuffler unpack;
+ int bits;
+
+ char *rawmode, *palette_mode;
+ UINT8 *palette;
+ Py_ssize_t palettesize;
+ if (!PyArg_ParseTuple(args, "sy#", &rawmode, &palette, &palettesize)) {
+ return NULL;
+ }
+
+ if (strcmp(self->image->mode, "L") && strcmp(self->image->mode, "LA") &&
+ strcmp(self->image->mode, "P") && strcmp(self->image->mode, "PA")) {
+ PyErr_SetString(PyExc_ValueError, wrong_mode);
+ return NULL;
+ }
+
+ palette_mode = strncmp("RGBA", rawmode, 4) == 0 ? "RGBA" : "RGB";
+ unpack = ImagingFindUnpacker(palette_mode, rawmode, &bits);
+ if (!unpack) {
+ PyErr_SetString(PyExc_ValueError, wrong_raw_mode);
+ return NULL;
+ }
+
+ if (palettesize * 8 / bits > 256) {
+ PyErr_SetString(PyExc_ValueError, wrong_palette_size);
+ return NULL;
+ }
+
+ ImagingPaletteDelete(self->image->palette);
+
+ strcpy(self->image->mode, strlen(self->image->mode) == 2 ? "PA" : "P");
+
+ self->image->palette = ImagingPaletteNew(palette_mode);
+
+ self->image->palette->size = palettesize * 8 / bits;
+ unpack(self->image->palette->palette, palette, self->image->palette->size);
+
+ Py_INCREF(Py_None);
+ return Py_None;
+}
+
+static PyObject *
+_putpalettealpha(ImagingObject *self, PyObject *args) {
+ int index;
+ int alpha = 0;
+ if (!PyArg_ParseTuple(args, "i|i", &index, &alpha)) {
+ return NULL;
+ }
+
+ if (!self->image->palette) {
+ PyErr_SetString(PyExc_ValueError, no_palette);
+ return NULL;
+ }
+
+ if (index < 0 || index >= 256) {
+ PyErr_SetString(PyExc_ValueError, outside_palette);
+ return NULL;
+ }
+
+ strcpy(self->image->palette->mode, "RGBA");
+ self->image->palette->palette[index * 4 + 3] = (UINT8)alpha;
+
+ Py_INCREF(Py_None);
+ return Py_None;
+}
+
+static PyObject *
+_putpalettealphas(ImagingObject *self, PyObject *args) {
+ int i;
+ UINT8 *values;
+ Py_ssize_t length;
+ if (!PyArg_ParseTuple(args, "y#", &values, &length)) {
+ return NULL;
+ }
+
+ if (!self->image->palette) {
+ PyErr_SetString(PyExc_ValueError, no_palette);
+ return NULL;
+ }
+
+ if (length > 256) {
+ PyErr_SetString(PyExc_ValueError, outside_palette);
+ return NULL;
+ }
+
+ strcpy(self->image->palette->mode, "RGBA");
+ for (i = 0; i < length; i++) {
+ self->image->palette->palette[i * 4 + 3] = (UINT8)values[i];
+ }
+
+ Py_INCREF(Py_None);
+ return Py_None;
+}
+
+static PyObject *
+_putpixel(ImagingObject *self, PyObject *args) {
+ Imaging im;
+ char ink[4];
+
+ int x, y;
+ PyObject *color;
+ if (!PyArg_ParseTuple(args, "(ii)O", &x, &y, &color)) {
+ return NULL;
+ }
+
+ im = self->image;
+
+ if (x < 0) {
+ x = im->xsize + x;
+ }
+ if (y < 0) {
+ y = im->ysize + y;
+ }
+
+ if (x < 0 || x >= im->xsize || y < 0 || y >= im->ysize) {
+ PyErr_SetString(PyExc_IndexError, outside_image);
+ return NULL;
+ }
+
+ if (!getink(color, im, ink)) {
+ return NULL;
+ }
+
+ if (self->access) {
+ self->access->put_pixel(im, x, y, ink);
+ }
+
+ Py_INCREF(Py_None);
+ return Py_None;
+}
+
+#ifdef WITH_RANKFILTER
+static PyObject *
+_rankfilter(ImagingObject *self, PyObject *args) {
+ int size, rank;
+ if (!PyArg_ParseTuple(args, "ii", &size, &rank)) {
+ return NULL;
+ }
+
+ return PyImagingNew(ImagingRankFilter(self->image, size, rank));
+}
+#endif
+
+static PyObject *
+_resize(ImagingObject *self, PyObject *args) {
+ Imaging imIn;
+ Imaging imOut;
+
+ int xsize, ysize;
+ int filter = IMAGING_TRANSFORM_NEAREST;
+ float box[4] = {0, 0, 0, 0};
+
+ imIn = self->image;
+ box[2] = imIn->xsize;
+ box[3] = imIn->ysize;
+
+ if (!PyArg_ParseTuple(
+ args,
+ "(ii)|i(ffff)",
+ &xsize,
+ &ysize,
+ &filter,
+ &box[0],
+ &box[1],
+ &box[2],
+ &box[3])) {
+ return NULL;
+ }
+
+ if (xsize < 1 || ysize < 1) {
+ return ImagingError_ValueError("height and width must be > 0");
+ }
+
+ if (box[0] < 0 || box[1] < 0) {
+ return ImagingError_ValueError("box offset can't be negative");
+ }
+
+ if (box[2] > imIn->xsize || box[3] > imIn->ysize) {
+ return ImagingError_ValueError("box can't exceed original image size");
+ }
+
+ if (box[2] - box[0] < 0 || box[3] - box[1] < 0) {
+ return ImagingError_ValueError("box can't be empty");
+ }
+
+ // If box's coordinates are int and box size matches requested size
+ if (box[0] - (int)box[0] == 0 && box[2] - box[0] == xsize &&
+ box[1] - (int)box[1] == 0 && box[3] - box[1] == ysize) {
+ imOut = ImagingCrop(imIn, box[0], box[1], box[2], box[3]);
+ } else if (filter == IMAGING_TRANSFORM_NEAREST) {
+ double a[8];
+
+ memset(a, 0, sizeof a);
+ a[0] = (double)(box[2] - box[0]) / xsize;
+ a[4] = (double)(box[3] - box[1]) / ysize;
+ a[2] = box[0];
+ a[5] = box[1];
+
+ imOut = ImagingNewDirty(imIn->mode, xsize, ysize);
+
+ imOut = ImagingTransform(
+ imOut, imIn, IMAGING_TRANSFORM_AFFINE, 0, 0, xsize, ysize, a, filter, 1);
+ } else {
+ imOut = ImagingResample(imIn, xsize, ysize, filter, box);
+ }
+
+ return PyImagingNew(imOut);
+}
+
+static PyObject *
+_reduce(ImagingObject *self, PyObject *args) {
+ Imaging imIn;
+ Imaging imOut;
+
+ int xscale, yscale;
+ int box[4] = {0, 0, 0, 0};
+
+ imIn = self->image;
+ box[2] = imIn->xsize;
+ box[3] = imIn->ysize;
+
+ if (!PyArg_ParseTuple(
+ args,
+ "(ii)|(iiii)",
+ &xscale,
+ &yscale,
+ &box[0],
+ &box[1],
+ &box[2],
+ &box[3])) {
+ return NULL;
+ }
+
+ if (xscale < 1 || yscale < 1) {
+ return ImagingError_ValueError("scale must be > 0");
+ }
+
+ if (box[0] < 0 || box[1] < 0) {
+ return ImagingError_ValueError("box offset can't be negative");
+ }
+
+ if (box[2] > imIn->xsize || box[3] > imIn->ysize) {
+ return ImagingError_ValueError("box can't exceed original image size");
+ }
+
+ if (box[2] <= box[0] || box[3] <= box[1]) {
+ return ImagingError_ValueError("box can't be empty");
+ }
+
+ if (xscale == 1 && yscale == 1) {
+ imOut = ImagingCrop(imIn, box[0], box[1], box[2], box[3]);
+ } else {
+ // Change box format: (left, top, width, height)
+ box[2] -= box[0];
+ box[3] -= box[1];
+ imOut = ImagingReduce(imIn, xscale, yscale, box);
+ }
+
+ return PyImagingNew(imOut);
+}
+
+#define IS_RGB(mode) \
+ (!strcmp(mode, "RGB") || !strcmp(mode, "RGBA") || !strcmp(mode, "RGBX"))
+
+static PyObject *
+im_setmode(ImagingObject *self, PyObject *args) {
+ /* attempt to modify the mode of an image in place */
+
+ Imaging im;
+
+ char *mode;
+ Py_ssize_t modelen;
+ if (!PyArg_ParseTuple(args, "s#:setmode", &mode, &modelen)) {
+ return NULL;
+ }
+
+ im = self->image;
+
+ /* move all logic in here to the libImaging primitive */
+
+ if (!strcmp(im->mode, mode)) {
+ ; /* same mode; always succeeds */
+ } else if (IS_RGB(im->mode) && IS_RGB(mode)) {
+ /* color to color */
+ strcpy(im->mode, mode);
+ im->bands = modelen;
+ if (!strcmp(mode, "RGBA")) {
+ (void)ImagingFillBand(im, 3, 255);
+ }
+ } else {
+ /* trying doing an in-place conversion */
+ if (!ImagingConvertInPlace(im, mode)) {
+ return NULL;
+ }
+ }
+
+ if (self->access) {
+ ImagingAccessDelete(im, self->access);
+ }
+ self->access = ImagingAccessNew(im);
+
+ Py_INCREF(Py_None);
+ return Py_None;
+}
+
+static PyObject *
+_transform2(ImagingObject *self, PyObject *args) {
+ static const char *wrong_number = "wrong number of matrix entries";
+
+ Imaging imOut;
+ Py_ssize_t n;
+ double *a;
+
+ ImagingObject *imagep;
+ int x0, y0, x1, y1;
+ int method;
+ PyObject *data;
+ int filter = IMAGING_TRANSFORM_NEAREST;
+ int fill = 1;
+ if (!PyArg_ParseTuple(
+ args,
+ "(iiii)O!iO|ii",
+ &x0,
+ &y0,
+ &x1,
+ &y1,
+ &Imaging_Type,
+ &imagep,
+ &method,
+ &data,
+ &filter,
+ &fill)) {
+ return NULL;
+ }
+
+ switch (method) {
+ case IMAGING_TRANSFORM_AFFINE:
+ n = 6;
+ break;
+ case IMAGING_TRANSFORM_PERSPECTIVE:
+ n = 8;
+ break;
+ case IMAGING_TRANSFORM_QUAD:
+ n = 8;
+ break;
+ default:
+ n = -1; /* force error */
+ }
+
+ a = getlist(data, &n, wrong_number, TYPE_DOUBLE);
+ if (!a) {
+ return NULL;
+ }
+
+ imOut = ImagingTransform(
+ self->image, imagep->image, method, x0, y0, x1, y1, a, filter, fill);
+
+ free(a);
+
+ if (!imOut) {
+ return NULL;
+ }
+
+ Py_INCREF(Py_None);
+ return Py_None;
+}
+
+static PyObject *
+_transpose(ImagingObject *self, PyObject *args) {
+ Imaging imIn;
+ Imaging imOut;
+
+ int op;
+ if (!PyArg_ParseTuple(args, "i", &op)) {
+ return NULL;
+ }
+
+ imIn = self->image;
+
+ switch (op) {
+ case 0: /* flip left right */
+ case 1: /* flip top bottom */
+ case 3: /* rotate 180 */
+ imOut = ImagingNewDirty(imIn->mode, imIn->xsize, imIn->ysize);
+ break;
+ case 2: /* rotate 90 */
+ case 4: /* rotate 270 */
+ case 5: /* transpose */
+ case 6: /* transverse */
+ imOut = ImagingNewDirty(imIn->mode, imIn->ysize, imIn->xsize);
+ break;
+ default:
+ PyErr_SetString(PyExc_ValueError, "No such transpose operation");
+ return NULL;
+ }
+
+ if (imOut) {
+ switch (op) {
+ case 0:
+ (void)ImagingFlipLeftRight(imOut, imIn);
+ break;
+ case 1:
+ (void)ImagingFlipTopBottom(imOut, imIn);
+ break;
+ case 2:
+ (void)ImagingRotate90(imOut, imIn);
+ break;
+ case 3:
+ (void)ImagingRotate180(imOut, imIn);
+ break;
+ case 4:
+ (void)ImagingRotate270(imOut, imIn);
+ break;
+ case 5:
+ (void)ImagingTranspose(imOut, imIn);
+ break;
+ case 6:
+ (void)ImagingTransverse(imOut, imIn);
+ break;
+ }
+ }
+
+ return PyImagingNew(imOut);
+}
+
+#ifdef WITH_UNSHARPMASK
+static PyObject *
+_unsharp_mask(ImagingObject *self, PyObject *args) {
+ Imaging imIn;
+ Imaging imOut;
+
+ float radius;
+ int percent, threshold;
+ if (!PyArg_ParseTuple(args, "fii", &radius, &percent, &threshold)) {
+ return NULL;
+ }
+
+ imIn = self->image;
+ imOut = ImagingNewDirty(imIn->mode, imIn->xsize, imIn->ysize);
+ if (!imOut) {
+ return NULL;
+ }
+
+ if (!ImagingUnsharpMask(imOut, imIn, radius, percent, threshold)) {
+ return NULL;
+ }
+
+ return PyImagingNew(imOut);
+}
+#endif
+
+static PyObject *
+_box_blur(ImagingObject *self, PyObject *args) {
+ Imaging imIn;
+ Imaging imOut;
+
+ float xradius, yradius;
+ int n = 1;
+ if (!PyArg_ParseTuple(args, "(ff)|i", &xradius, &yradius, &n)) {
+ return NULL;
+ }
+
+ imIn = self->image;
+ imOut = ImagingNewDirty(imIn->mode, imIn->xsize, imIn->ysize);
+ if (!imOut) {
+ return NULL;
+ }
+
+ if (!ImagingBoxBlur(imOut, imIn, xradius, yradius, n)) {
+ ImagingDelete(imOut);
+ return NULL;
+ }
+
+ return PyImagingNew(imOut);
+}
+
+/* -------------------------------------------------------------------- */
+
+static PyObject *
+_isblock(ImagingObject *self) {
+ return PyBool_FromLong(self->image->block != NULL);
+}
+
+static PyObject *
+_getbbox(ImagingObject *self, PyObject *args) {
+ int bbox[4];
+
+ int alpha_only = 1;
+ if (!PyArg_ParseTuple(args, "|i", &alpha_only)) {
+ return NULL;
+ }
+
+ if (!ImagingGetBBox(self->image, bbox, alpha_only)) {
+ Py_INCREF(Py_None);
+ return Py_None;
+ }
+
+ return Py_BuildValue("iiii", bbox[0], bbox[1], bbox[2], bbox[3]);
+}
+
+static PyObject *
+_getcolors(ImagingObject *self, PyObject *args) {
+ ImagingColorItem *items;
+ int i, colors;
+ PyObject *out;
+
+ int maxcolors = 256;
+ if (!PyArg_ParseTuple(args, "i:getcolors", &maxcolors)) {
+ return NULL;
+ }
+
+ items = ImagingGetColors(self->image, maxcolors, &colors);
+ if (!items) {
+ return NULL;
+ }
+
+ if (colors > maxcolors) {
+ out = Py_None;
+ Py_INCREF(out);
+ } else {
+ out = PyList_New(colors);
+ if (out == NULL) {
+ free(items);
+ return NULL;
+ }
+ for (i = 0; i < colors; i++) {
+ ImagingColorItem *v = &items[i];
+ PyObject *item = Py_BuildValue(
+ "iN", v->count, getpixel(self->image, self->access, v->x, v->y));
+ PyList_SetItem(out, i, item);
+ }
+ }
+
+ free(items);
+
+ return out;
+}
+
+static PyObject *
+_getextrema(ImagingObject *self) {
+ union {
+ UINT8 u[2];
+ INT32 i[2];
+ FLOAT32 f[2];
+ UINT16 s[2];
+ } extrema;
+ int status;
+
+ status = ImagingGetExtrema(self->image, &extrema);
+ if (status < 0) {
+ return NULL;
+ }
+
+ if (status) {
+ switch (self->image->type) {
+ case IMAGING_TYPE_UINT8:
+ return Py_BuildValue("BB", extrema.u[0], extrema.u[1]);
+ case IMAGING_TYPE_INT32:
+ return Py_BuildValue("ii", extrema.i[0], extrema.i[1]);
+ case IMAGING_TYPE_FLOAT32:
+ return Py_BuildValue("dd", extrema.f[0], extrema.f[1]);
+ case IMAGING_TYPE_SPECIAL:
+ if (strcmp(self->image->mode, "I;16") == 0) {
+ return Py_BuildValue("HH", extrema.s[0], extrema.s[1]);
+ }
+ }
+ }
+
+ Py_INCREF(Py_None);
+ return Py_None;
+}
+
+static PyObject *
+_getprojection(ImagingObject *self) {
+ unsigned char *xprofile;
+ unsigned char *yprofile;
+ PyObject *result;
+
+ /* malloc check ok */
+ xprofile = malloc(self->image->xsize);
+ yprofile = malloc(self->image->ysize);
+
+ if (xprofile == NULL || yprofile == NULL) {
+ free(xprofile);
+ free(yprofile);
+ return ImagingError_MemoryError();
+ }
+
+ ImagingGetProjection(
+ self->image, (unsigned char *)xprofile, (unsigned char *)yprofile);
+
+ result = Py_BuildValue(
+ "y#y#",
+ xprofile,
+ (Py_ssize_t)self->image->xsize,
+ yprofile,
+ (Py_ssize_t)self->image->ysize);
+
+ free(xprofile);
+ free(yprofile);
+
+ return result;
+}
+
+/* -------------------------------------------------------------------- */
+
+static PyObject *
+_getband(ImagingObject *self, PyObject *args) {
+ int band;
+
+ if (!PyArg_ParseTuple(args, "i", &band)) {
+ return NULL;
+ }
+
+ return PyImagingNew(ImagingGetBand(self->image, band));
+}
+
+static PyObject *
+_fillband(ImagingObject *self, PyObject *args) {
+ int band;
+ int color;
+
+ if (!PyArg_ParseTuple(args, "ii", &band, &color)) {
+ return NULL;
+ }
+
+ if (!ImagingFillBand(self->image, band, color)) {
+ return NULL;
+ }
+
+ Py_INCREF(Py_None);
+ return Py_None;
+}
+
+static PyObject *
+_putband(ImagingObject *self, PyObject *args) {
+ ImagingObject *imagep;
+ int band;
+ if (!PyArg_ParseTuple(args, "O!i", &Imaging_Type, &imagep, &band)) {
+ return NULL;
+ }
+
+ if (!ImagingPutBand(self->image, imagep->image, band)) {
+ return NULL;
+ }
+
+ Py_INCREF(Py_None);
+ return Py_None;
+}
+
+static PyObject *
+_merge(PyObject *self, PyObject *args) {
+ char *mode;
+ ImagingObject *band0 = NULL;
+ ImagingObject *band1 = NULL;
+ ImagingObject *band2 = NULL;
+ ImagingObject *band3 = NULL;
+ Imaging bands[4] = {NULL, NULL, NULL, NULL};
+
+ if (!PyArg_ParseTuple(
+ args,
+ "sO!|O!O!O!",
+ &mode,
+ &Imaging_Type,
+ &band0,
+ &Imaging_Type,
+ &band1,
+ &Imaging_Type,
+ &band2,
+ &Imaging_Type,
+ &band3)) {
+ return NULL;
+ }
+
+ if (band0) {
+ bands[0] = band0->image;
+ }
+ if (band1) {
+ bands[1] = band1->image;
+ }
+ if (band2) {
+ bands[2] = band2->image;
+ }
+ if (band3) {
+ bands[3] = band3->image;
+ }
+
+ return PyImagingNew(ImagingMerge(mode, bands));
+}
+
+static PyObject *
+_split(ImagingObject *self) {
+ int fails = 0;
+ Py_ssize_t i;
+ PyObject *list;
+ PyObject *imaging_object;
+ Imaging bands[4] = {NULL, NULL, NULL, NULL};
+
+ if (!ImagingSplit(self->image, bands)) {
+ return NULL;
+ }
+
+ list = PyTuple_New(self->image->bands);
+ for (i = 0; i < self->image->bands; i++) {
+ imaging_object = PyImagingNew(bands[i]);
+ if (!imaging_object) {
+ fails += 1;
+ }
+ PyTuple_SET_ITEM(list, i, imaging_object);
+ }
+ if (fails) {
+ Py_DECREF(list);
+ list = NULL;
+ }
+ return list;
+}
+
+/* -------------------------------------------------------------------- */
+
+#ifdef WITH_IMAGECHOPS
+
+static PyObject *
+_chop_invert(ImagingObject *self) {
+ return PyImagingNew(ImagingNegative(self->image));
+}
+
+static PyObject *
+_chop_lighter(ImagingObject *self, PyObject *args) {
+ ImagingObject *imagep;
+
+ if (!PyArg_ParseTuple(args, "O!", &Imaging_Type, &imagep)) {
+ return NULL;
+ }
+
+ return PyImagingNew(ImagingChopLighter(self->image, imagep->image));
+}
+
+static PyObject *
+_chop_darker(ImagingObject *self, PyObject *args) {
+ ImagingObject *imagep;
+
+ if (!PyArg_ParseTuple(args, "O!", &Imaging_Type, &imagep)) {
+ return NULL;
+ }
+
+ return PyImagingNew(ImagingChopDarker(self->image, imagep->image));
+}
+
+static PyObject *
+_chop_difference(ImagingObject *self, PyObject *args) {
+ ImagingObject *imagep;
+
+ if (!PyArg_ParseTuple(args, "O!", &Imaging_Type, &imagep)) {
+ return NULL;
+ }
+
+ return PyImagingNew(ImagingChopDifference(self->image, imagep->image));
+}
+
+static PyObject *
+_chop_multiply(ImagingObject *self, PyObject *args) {
+ ImagingObject *imagep;
+
+ if (!PyArg_ParseTuple(args, "O!", &Imaging_Type, &imagep)) {
+ return NULL;
+ }
+
+ return PyImagingNew(ImagingChopMultiply(self->image, imagep->image));
+}
+
+static PyObject *
+_chop_screen(ImagingObject *self, PyObject *args) {
+ ImagingObject *imagep;
+
+ if (!PyArg_ParseTuple(args, "O!", &Imaging_Type, &imagep)) {
+ return NULL;
+ }
+
+ return PyImagingNew(ImagingChopScreen(self->image, imagep->image));
+}
+
+static PyObject *
+_chop_add(ImagingObject *self, PyObject *args) {
+ ImagingObject *imagep;
+ float scale;
+ int offset;
+
+ scale = 1.0;
+ offset = 0;
+
+ if (!PyArg_ParseTuple(args, "O!|fi", &Imaging_Type, &imagep, &scale, &offset)) {
+ return NULL;
+ }
+
+ return PyImagingNew(ImagingChopAdd(self->image, imagep->image, scale, offset));
+}
+
+static PyObject *
+_chop_subtract(ImagingObject *self, PyObject *args) {
+ ImagingObject *imagep;
+ float scale;
+ int offset;
+
+ scale = 1.0;
+ offset = 0;
+
+ if (!PyArg_ParseTuple(args, "O!|fi", &Imaging_Type, &imagep, &scale, &offset)) {
+ return NULL;
+ }
+
+ return PyImagingNew(ImagingChopSubtract(self->image, imagep->image, scale, offset));
+}
+
+static PyObject *
+_chop_and(ImagingObject *self, PyObject *args) {
+ ImagingObject *imagep;
+
+ if (!PyArg_ParseTuple(args, "O!", &Imaging_Type, &imagep)) {
+ return NULL;
+ }
+
+ return PyImagingNew(ImagingChopAnd(self->image, imagep->image));
+}
+
+static PyObject *
+_chop_or(ImagingObject *self, PyObject *args) {
+ ImagingObject *imagep;
+
+ if (!PyArg_ParseTuple(args, "O!", &Imaging_Type, &imagep)) {
+ return NULL;
+ }
+
+ return PyImagingNew(ImagingChopOr(self->image, imagep->image));
+}
+
+static PyObject *
+_chop_xor(ImagingObject *self, PyObject *args) {
+ ImagingObject *imagep;
+
+ if (!PyArg_ParseTuple(args, "O!", &Imaging_Type, &imagep)) {
+ return NULL;
+ }
+
+ return PyImagingNew(ImagingChopXor(self->image, imagep->image));
+}
+
+static PyObject *
+_chop_add_modulo(ImagingObject *self, PyObject *args) {
+ ImagingObject *imagep;
+
+ if (!PyArg_ParseTuple(args, "O!", &Imaging_Type, &imagep)) {
+ return NULL;
+ }
+
+ return PyImagingNew(ImagingChopAddModulo(self->image, imagep->image));
+}
+
+static PyObject *
+_chop_subtract_modulo(ImagingObject *self, PyObject *args) {
+ ImagingObject *imagep;
+
+ if (!PyArg_ParseTuple(args, "O!", &Imaging_Type, &imagep)) {
+ return NULL;
+ }
+
+ return PyImagingNew(ImagingChopSubtractModulo(self->image, imagep->image));
+}
+
+static PyObject *
+_chop_soft_light(ImagingObject *self, PyObject *args) {
+ ImagingObject *imagep;
+
+ if (!PyArg_ParseTuple(args, "O!", &Imaging_Type, &imagep)) {
+ return NULL;
+ }
+
+ return PyImagingNew(ImagingChopSoftLight(self->image, imagep->image));
+}
+
+static PyObject *
+_chop_hard_light(ImagingObject *self, PyObject *args) {
+ ImagingObject *imagep;
+
+ if (!PyArg_ParseTuple(args, "O!", &Imaging_Type, &imagep)) {
+ return NULL;
+ }
+
+ return PyImagingNew(ImagingChopHardLight(self->image, imagep->image));
+}
+
+static PyObject *
+_chop_overlay(ImagingObject *self, PyObject *args) {
+ ImagingObject *imagep;
+
+ if (!PyArg_ParseTuple(args, "O!", &Imaging_Type, &imagep)) {
+ return NULL;
+ }
+
+ return PyImagingNew(ImagingOverlay(self->image, imagep->image));
+}
+#endif
+
+/* -------------------------------------------------------------------- */
+
+#ifdef WITH_IMAGEDRAW
+
+static PyObject *
+_font_new(PyObject *self_, PyObject *args) {
+ ImagingFontObject *self;
+ int i, y0, y1;
+ static const char *wrong_length = "descriptor table has wrong size";
+
+ ImagingObject *imagep;
+ unsigned char *glyphdata;
+ Py_ssize_t glyphdata_length;
+ if (!PyArg_ParseTuple(
+ args, "O!y#", &Imaging_Type, &imagep, &glyphdata, &glyphdata_length)) {
+ return NULL;
+ }
+
+ if (glyphdata_length != 256 * 20) {
+ PyErr_SetString(PyExc_ValueError, wrong_length);
+ return NULL;
+ }
+
+ self = PyObject_New(ImagingFontObject, &ImagingFont_Type);
+ if (self == NULL) {
+ return NULL;
+ }
+
+ /* glyph bitmap */
+ self->bitmap = imagep->image;
+
+ y0 = y1 = 0;
+
+ /* glyph glyphs */
+ for (i = 0; i < 256; i++) {
+ self->glyphs[i].dx = S16(B16(glyphdata, 0));
+ self->glyphs[i].dy = S16(B16(glyphdata, 2));
+ self->glyphs[i].dx0 = S16(B16(glyphdata, 4));
+ self->glyphs[i].dy0 = S16(B16(glyphdata, 6));
+ self->glyphs[i].dx1 = S16(B16(glyphdata, 8));
+ self->glyphs[i].dy1 = S16(B16(glyphdata, 10));
+ self->glyphs[i].sx0 = S16(B16(glyphdata, 12));
+ self->glyphs[i].sy0 = S16(B16(glyphdata, 14));
+ self->glyphs[i].sx1 = S16(B16(glyphdata, 16));
+ self->glyphs[i].sy1 = S16(B16(glyphdata, 18));
+ if (self->glyphs[i].dy0 < y0) {
+ y0 = self->glyphs[i].dy0;
+ }
+ if (self->glyphs[i].dy1 > y1) {
+ y1 = self->glyphs[i].dy1;
+ }
+ glyphdata += 20;
+ }
+
+ self->baseline = -y0;
+ self->ysize = y1 - y0;
+
+ /* keep a reference to the bitmap object */
+ Py_INCREF(imagep);
+ self->ref = imagep;
+
+ return (PyObject *)self;
+}
+
+static void
+_font_dealloc(ImagingFontObject *self) {
+ Py_XDECREF(self->ref);
+ PyObject_Del(self);
+}
+
+static inline int
+textwidth(ImagingFontObject *self, const unsigned char *text) {
+ int xsize;
+
+ for (xsize = 0; *text; text++) {
+ xsize += self->glyphs[*text].dx;
+ }
+
+ return xsize;
+}
+
+void
+_font_text_asBytes(PyObject *encoded_string, unsigned char **text) {
+ /* Allocates *text, returns a 'new reference'. Caller is required to free */
+
+ PyObject *bytes = NULL;
+ Py_ssize_t len = 0;
+ char *buffer;
+
+ *text = NULL;
+
+ if (PyUnicode_CheckExact(encoded_string)) {
+ bytes = PyUnicode_AsLatin1String(encoded_string);
+ if (!bytes) {
+ return;
+ }
+ PyBytes_AsStringAndSize(bytes, &buffer, &len);
+ } else if (PyBytes_Check(encoded_string)) {
+ PyBytes_AsStringAndSize(encoded_string, &buffer, &len);
+ }
+
+ *text = calloc(len + 1, 1);
+ if (*text) {
+ memcpy(*text, buffer, len);
+ } else {
+ ImagingError_MemoryError();
+ }
+ if (bytes) {
+ Py_DECREF(bytes);
+ }
+
+ return;
+}
+
+static PyObject *
+_font_getmask(ImagingFontObject *self, PyObject *args) {
+ Imaging im;
+ Imaging bitmap;
+ int x, b;
+ int i = 0;
+ int status;
+ Glyph *glyph;
+
+ PyObject *encoded_string;
+
+ unsigned char *text;
+ char *mode = "";
+
+ if (!PyArg_ParseTuple(args, "O|s:getmask", &encoded_string, &mode)) {
+ return NULL;
+ }
+
+ _font_text_asBytes(encoded_string, &text);
+ if (!text) {
+ return NULL;
+ }
+
+ im = ImagingNew(self->bitmap->mode, textwidth(self, text), self->ysize);
+ if (!im) {
+ free(text);
+ return ImagingError_MemoryError();
+ }
+
+ b = 0;
+ (void)ImagingFill(im, &b);
+
+ b = self->baseline;
+ for (x = 0; text[i]; i++) {
+ glyph = &self->glyphs[text[i]];
+ bitmap =
+ ImagingCrop(self->bitmap, glyph->sx0, glyph->sy0, glyph->sx1, glyph->sy1);
+ if (!bitmap) {
+ goto failed;
+ }
+ status = ImagingPaste(
+ im,
+ bitmap,
+ NULL,
+ glyph->dx0 + x,
+ glyph->dy0 + b,
+ glyph->dx1 + x,
+ glyph->dy1 + b);
+ ImagingDelete(bitmap);
+ if (status < 0) {
+ goto failed;
+ }
+ x = x + glyph->dx;
+ b = b + glyph->dy;
+ }
+ free(text);
+ return PyImagingNew(im);
+
+failed:
+ free(text);
+ ImagingDelete(im);
+ Py_RETURN_NONE;
+}
+
+static PyObject *
+_font_getsize(ImagingFontObject *self, PyObject *args) {
+ unsigned char *text;
+ PyObject *encoded_string;
+ PyObject *val;
+
+ if (!PyArg_ParseTuple(args, "O:getsize", &encoded_string)) {
+ return NULL;
+ }
+
+ _font_text_asBytes(encoded_string, &text);
+ if (!text) {
+ return NULL;
+ }
+
+ val = Py_BuildValue("ii", textwidth(self, text), self->ysize);
+ free(text);
+ return val;
+}
+
+static struct PyMethodDef _font_methods[] = {
+ {"getmask", (PyCFunction)_font_getmask, METH_VARARGS},
+ {"getsize", (PyCFunction)_font_getsize, METH_VARARGS},
+ {NULL, NULL} /* sentinel */
+};
+
+/* -------------------------------------------------------------------- */
+
+static PyObject *
+_draw_new(PyObject *self_, PyObject *args) {
+ ImagingDrawObject *self;
+
+ ImagingObject *imagep;
+ int blend = 0;
+ if (!PyArg_ParseTuple(args, "O!|i", &Imaging_Type, &imagep, &blend)) {
+ return NULL;
+ }
+
+ self = PyObject_New(ImagingDrawObject, &ImagingDraw_Type);
+ if (self == NULL) {
+ return NULL;
+ }
+
+ /* keep a reference to the image object */
+ Py_INCREF(imagep);
+ self->image = imagep;
+
+ self->ink[0] = self->ink[1] = self->ink[2] = self->ink[3] = 0;
+
+ self->blend = blend;
+
+ return (PyObject *)self;
+}
+
+static void
+_draw_dealloc(ImagingDrawObject *self) {
+ Py_XDECREF(self->image);
+ PyObject_Del(self);
+}
+
+extern Py_ssize_t
+PyPath_Flatten(PyObject *data, double **xy);
+
+static PyObject *
+_draw_ink(ImagingDrawObject *self, PyObject *args) {
+ INT32 ink = 0;
+ PyObject *color;
+ if (!PyArg_ParseTuple(args, "O", &color)) {
+ return NULL;
+ }
+
+ if (!getink(color, self->image->image, (char *)&ink)) {
+ return NULL;
+ }
+
+ return PyLong_FromLong((int)ink);
+}
+
+static PyObject *
+_draw_arc(ImagingDrawObject *self, PyObject *args) {
+ double *xy;
+ Py_ssize_t n;
+
+ PyObject *data;
+ int ink;
+ int width = 0;
+ float start, end;
+ if (!PyArg_ParseTuple(args, "Offi|i", &data, &start, &end, &ink, &width)) {
+ return NULL;
+ }
+
+ n = PyPath_Flatten(data, &xy);
+ if (n < 0) {
+ return NULL;
+ }
+ if (n != 2) {
+ PyErr_SetString(PyExc_TypeError, must_be_two_coordinates);
+ free(xy);
+ return NULL;
+ }
+ if (xy[2] < xy[0]) {
+ PyErr_SetString(PyExc_ValueError, incorrectly_ordered_x_coordinate);
+ free(xy);
+ return NULL;
+ }
+ if (xy[3] < xy[1]) {
+ PyErr_SetString(PyExc_ValueError, incorrectly_ordered_y_coordinate);
+ free(xy);
+ return NULL;
+ }
+
+ n = ImagingDrawArc(
+ self->image->image,
+ (int)xy[0],
+ (int)xy[1],
+ (int)xy[2],
+ (int)xy[3],
+ start,
+ end,
+ &ink,
+ width,
+ self->blend);
+
+ free(xy);
+
+ if (n < 0) {
+ return NULL;
+ }
+
+ Py_INCREF(Py_None);
+ return Py_None;
+}
+
+static PyObject *
+_draw_bitmap(ImagingDrawObject *self, PyObject *args) {
+ double *xy;
+ Py_ssize_t n;
+
+ PyObject *data;
+ ImagingObject *bitmap;
+ int ink;
+ if (!PyArg_ParseTuple(args, "OO!i", &data, &Imaging_Type, &bitmap, &ink)) {
+ return NULL;
+ }
+
+ n = PyPath_Flatten(data, &xy);
+ if (n < 0) {
+ return NULL;
+ }
+ if (n != 1) {
+ PyErr_SetString(
+ PyExc_TypeError, "coordinate list must contain exactly 1 coordinate");
+ free(xy);
+ return NULL;
+ }
+
+ n = ImagingDrawBitmap(
+ self->image->image, (int)xy[0], (int)xy[1], bitmap->image, &ink, self->blend);
+
+ free(xy);
+
+ if (n < 0) {
+ return NULL;
+ }
+
+ Py_INCREF(Py_None);
+ return Py_None;
+}
+
+static PyObject *
+_draw_chord(ImagingDrawObject *self, PyObject *args) {
+ double *xy;
+ Py_ssize_t n;
+
+ PyObject *data;
+ int ink, fill;
+ int width = 0;
+ float start, end;
+ if (!PyArg_ParseTuple(args, "Offii|i", &data, &start, &end, &ink, &fill, &width)) {
+ return NULL;
+ }
+
+ n = PyPath_Flatten(data, &xy);
+ if (n < 0) {
+ return NULL;
+ }
+ if (n != 2) {
+ PyErr_SetString(PyExc_TypeError, must_be_two_coordinates);
+ free(xy);
+ return NULL;
+ }
+ if (xy[2] < xy[0]) {
+ PyErr_SetString(PyExc_ValueError, incorrectly_ordered_x_coordinate);
+ free(xy);
+ return NULL;
+ }
+ if (xy[3] < xy[1]) {
+ PyErr_SetString(PyExc_ValueError, incorrectly_ordered_y_coordinate);
+ free(xy);
+ return NULL;
+ }
+
+ n = ImagingDrawChord(
+ self->image->image,
+ (int)xy[0],
+ (int)xy[1],
+ (int)xy[2],
+ (int)xy[3],
+ start,
+ end,
+ &ink,
+ fill,
+ width,
+ self->blend);
+
+ free(xy);
+
+ if (n < 0) {
+ return NULL;
+ }
+
+ Py_INCREF(Py_None);
+ return Py_None;
+}
+
+static PyObject *
+_draw_ellipse(ImagingDrawObject *self, PyObject *args) {
+ double *xy;
+ Py_ssize_t n;
+
+ PyObject *data;
+ int ink;
+ int fill = 0;
+ int width = 0;
+ if (!PyArg_ParseTuple(args, "Oi|ii", &data, &ink, &fill, &width)) {
+ return NULL;
+ }
+
+ n = PyPath_Flatten(data, &xy);
+ if (n < 0) {
+ return NULL;
+ }
+ if (n != 2) {
+ PyErr_SetString(PyExc_TypeError, must_be_two_coordinates);
+ free(xy);
+ return NULL;
+ }
+ if (xy[2] < xy[0]) {
+ PyErr_SetString(PyExc_ValueError, incorrectly_ordered_x_coordinate);
+ free(xy);
+ return NULL;
+ }
+ if (xy[3] < xy[1]) {
+ PyErr_SetString(PyExc_ValueError, incorrectly_ordered_y_coordinate);
+ free(xy);
+ return NULL;
+ }
+
+ n = ImagingDrawEllipse(
+ self->image->image,
+ (int)xy[0],
+ (int)xy[1],
+ (int)xy[2],
+ (int)xy[3],
+ &ink,
+ fill,
+ width,
+ self->blend);
+
+ free(xy);
+
+ if (n < 0) {
+ return NULL;
+ }
+
+ Py_INCREF(Py_None);
+ return Py_None;
+}
+
+static PyObject *
+_draw_lines(ImagingDrawObject *self, PyObject *args) {
+ double *xy;
+ Py_ssize_t i, n;
+
+ PyObject *data;
+ int ink;
+ int width = 0;
+ if (!PyArg_ParseTuple(args, "Oi|i", &data, &ink, &width)) {
+ return NULL;
+ }
+
+ n = PyPath_Flatten(data, &xy);
+ if (n < 0) {
+ return NULL;
+ }
+
+ if (width <= 1) {
+ double *p = NULL;
+ for (i = 0; i < n - 1; i++) {
+ p = &xy[i + i];
+ if (ImagingDrawLine(
+ self->image->image,
+ (int)p[0],
+ (int)p[1],
+ (int)p[2],
+ (int)p[3],
+ &ink,
+ self->blend) < 0) {
+ free(xy);
+ return NULL;
+ }
+ }
+ if (p) { /* draw last point */
+ ImagingDrawPoint(
+ self->image->image, (int)p[2], (int)p[3], &ink, self->blend);
+ }
+ } else {
+ for (i = 0; i < n - 1; i++) {
+ double *p = &xy[i + i];
+ if (ImagingDrawWideLine(
+ self->image->image,
+ (int)p[0],
+ (int)p[1],
+ (int)p[2],
+ (int)p[3],
+ &ink,
+ width,
+ self->blend) < 0) {
+ free(xy);
+ return NULL;
+ }
+ }
+ }
+
+ free(xy);
+
+ Py_INCREF(Py_None);
+ return Py_None;
+}
+
+static PyObject *
+_draw_points(ImagingDrawObject *self, PyObject *args) {
+ double *xy;
+ Py_ssize_t i, n;
+
+ PyObject *data;
+ int ink;
+ if (!PyArg_ParseTuple(args, "Oi", &data, &ink)) {
+ return NULL;
+ }
+
+ n = PyPath_Flatten(data, &xy);
+ if (n < 0) {
+ return NULL;
+ }
+
+ for (i = 0; i < n; i++) {
+ double *p = &xy[i + i];
+ if (ImagingDrawPoint(
+ self->image->image, (int)p[0], (int)p[1], &ink, self->blend) < 0) {
+ free(xy);
+ return NULL;
+ }
+ }
+
+ free(xy);
+
+ Py_INCREF(Py_None);
+ return Py_None;
+}
+
+#ifdef WITH_ARROW
+
+/* from outline.c */
+extern ImagingOutline
+PyOutline_AsOutline(PyObject *outline);
+
+static PyObject *
+_draw_outline(ImagingDrawObject *self, PyObject *args) {
+ ImagingOutline outline;
+
+ PyObject *outline_;
+ int ink;
+ int fill = 0;
+ if (!PyArg_ParseTuple(args, "Oi|i", &outline_, &ink, &fill)) {
+ return NULL;
+ }
+
+ outline = PyOutline_AsOutline(outline_);
+ if (!outline) {
+ PyErr_SetString(PyExc_TypeError, "expected outline object");
+ return NULL;
+ }
+
+ if (ImagingDrawOutline(self->image->image, outline, &ink, fill, self->blend) < 0) {
+ return NULL;
+ }
+
+ Py_INCREF(Py_None);
+ return Py_None;
+}
+
+#endif
+
+static PyObject *
+_draw_pieslice(ImagingDrawObject *self, PyObject *args) {
+ double *xy;
+ Py_ssize_t n;
+
+ PyObject *data;
+ int ink, fill;
+ int width = 0;
+ float start, end;
+ if (!PyArg_ParseTuple(args, "Offii|i", &data, &start, &end, &ink, &fill, &width)) {
+ return NULL;
+ }
+
+ n = PyPath_Flatten(data, &xy);
+ if (n < 0) {
+ return NULL;
+ }
+ if (n != 2) {
+ PyErr_SetString(PyExc_TypeError, must_be_two_coordinates);
+ free(xy);
+ return NULL;
+ }
+ if (xy[2] < xy[0]) {
+ PyErr_SetString(PyExc_ValueError, incorrectly_ordered_x_coordinate);
+ free(xy);
+ return NULL;
+ }
+ if (xy[3] < xy[1]) {
+ PyErr_SetString(PyExc_ValueError, incorrectly_ordered_y_coordinate);
+ free(xy);
+ return NULL;
+ }
+
+ n = ImagingDrawPieslice(
+ self->image->image,
+ (int)xy[0],
+ (int)xy[1],
+ (int)xy[2],
+ (int)xy[3],
+ start,
+ end,
+ &ink,
+ fill,
+ width,
+ self->blend);
+
+ free(xy);
+
+ if (n < 0) {
+ return NULL;
+ }
+
+ Py_INCREF(Py_None);
+ return Py_None;
+}
+
+static PyObject *
+_draw_polygon(ImagingDrawObject *self, PyObject *args) {
+ double *xy;
+ int *ixy;
+ Py_ssize_t n, i;
+
+ PyObject *data;
+ int ink;
+ int fill = 0;
+ int width = 0;
+ if (!PyArg_ParseTuple(args, "Oi|ii", &data, &ink, &fill, &width)) {
+ return NULL;
+ }
+
+ n = PyPath_Flatten(data, &xy);
+ if (n < 0) {
+ return NULL;
+ }
+ if (n < 2) {
+ PyErr_SetString(
+ PyExc_TypeError, "coordinate list must contain at least 2 coordinates");
+ free(xy);
+ return NULL;
+ }
+
+ /* Copy list of vertices to array */
+ ixy = (int *)calloc(n, 2 * sizeof(int));
+ if (ixy == NULL) {
+ free(xy);
+ return ImagingError_MemoryError();
+ }
+
+ for (i = 0; i < n; i++) {
+ ixy[i + i] = (int)xy[i + i];
+ ixy[i + i + 1] = (int)xy[i + i + 1];
+ }
+
+ free(xy);
+
+ if (ImagingDrawPolygon(self->image->image, n, ixy, &ink, fill, width, self->blend) < 0) {
+ free(ixy);
+ return NULL;
+ }
+
+ free(ixy);
+
+ Py_INCREF(Py_None);
+ return Py_None;
+}
+
+static PyObject *
+_draw_rectangle(ImagingDrawObject *self, PyObject *args) {
+ double *xy;
+ Py_ssize_t n;
+
+ PyObject *data;
+ int ink;
+ int fill = 0;
+ int width = 0;
+ if (!PyArg_ParseTuple(args, "Oi|ii", &data, &ink, &fill, &width)) {
+ return NULL;
+ }
+
+ n = PyPath_Flatten(data, &xy);
+ if (n < 0) {
+ return NULL;
+ }
+ if (n != 2) {
+ PyErr_SetString(PyExc_TypeError, must_be_two_coordinates);
+ free(xy);
+ return NULL;
+ }
+ if (xy[2] < xy[0]) {
+ PyErr_SetString(PyExc_ValueError, incorrectly_ordered_x_coordinate);
+ free(xy);
+ return NULL;
+ }
+ if (xy[3] < xy[1]) {
+ PyErr_SetString(PyExc_ValueError, incorrectly_ordered_y_coordinate);
+ free(xy);
+ return NULL;
+ }
+
+ n = ImagingDrawRectangle(
+ self->image->image,
+ (int)xy[0],
+ (int)xy[1],
+ (int)xy[2],
+ (int)xy[3],
+ &ink,
+ fill,
+ width,
+ self->blend);
+
+ free(xy);
+
+ if (n < 0) {
+ return NULL;
+ }
+
+ Py_INCREF(Py_None);
+ return Py_None;
+}
+
+static struct PyMethodDef _draw_methods[] = {
+#ifdef WITH_IMAGEDRAW
+ /* Graphics (ImageDraw) */
+ {"draw_lines", (PyCFunction)_draw_lines, METH_VARARGS},
+#ifdef WITH_ARROW
+ {"draw_outline", (PyCFunction)_draw_outline, METH_VARARGS},
+#endif
+ {"draw_polygon", (PyCFunction)_draw_polygon, METH_VARARGS},
+ {"draw_rectangle", (PyCFunction)_draw_rectangle, METH_VARARGS},
+ {"draw_points", (PyCFunction)_draw_points, METH_VARARGS},
+ {"draw_arc", (PyCFunction)_draw_arc, METH_VARARGS},
+ {"draw_bitmap", (PyCFunction)_draw_bitmap, METH_VARARGS},
+ {"draw_chord", (PyCFunction)_draw_chord, METH_VARARGS},
+ {"draw_ellipse", (PyCFunction)_draw_ellipse, METH_VARARGS},
+ {"draw_pieslice", (PyCFunction)_draw_pieslice, METH_VARARGS},
+ {"draw_ink", (PyCFunction)_draw_ink, METH_VARARGS},
+#endif
+ {NULL, NULL} /* sentinel */
+};
+
+#endif
+
+static PyObject *
+pixel_access_new(ImagingObject *imagep, PyObject *args) {
+ PixelAccessObject *self;
+
+ int readonly = 0;
+ if (!PyArg_ParseTuple(args, "|i", &readonly)) {
+ return NULL;
+ }
+
+ self = PyObject_New(PixelAccessObject, &PixelAccess_Type);
+ if (self == NULL) {
+ return NULL;
+ }
+
+ /* keep a reference to the image object */
+ Py_INCREF(imagep);
+ self->image = imagep;
+
+ self->readonly = readonly;
+
+ return (PyObject *)self;
+}
+
+static void
+pixel_access_dealloc(PixelAccessObject *self) {
+ Py_XDECREF(self->image);
+ PyObject_Del(self);
+}
+
+static PyObject *
+pixel_access_getitem(PixelAccessObject *self, PyObject *xy) {
+ int x, y;
+ if (_getxy(xy, &x, &y)) {
+ return NULL;
+ }
+
+ return getpixel(self->image->image, self->image->access, x, y);
+}
+
+static int
+pixel_access_setitem(PixelAccessObject *self, PyObject *xy, PyObject *color) {
+ Imaging im = self->image->image;
+ char ink[4];
+ int x, y;
+
+ if (self->readonly) {
+ (void)ImagingError_ValueError(readonly);
+ return -1;
+ }
+
+ if (_getxy(xy, &x, &y)) {
+ return -1;
+ }
+
+ if (x < 0) {
+ x = im->xsize + x;
+ }
+ if (y < 0) {
+ y = im->ysize + y;
+ }
+
+ if (x < 0 || x >= im->xsize || y < 0 || y >= im->ysize) {
+ PyErr_SetString(PyExc_IndexError, outside_image);
+ return -1;
+ }
+
+ if (!color) { /* FIXME: raise exception? */
+ return 0;
+ }
+
+ if (!getink(color, im, ink)) {
+ return -1;
+ }
+
+ self->image->access->put_pixel(im, x, y, ink);
+
+ return 0;
+}
+
+/* -------------------------------------------------------------------- */
+/* EFFECTS (experimental) */
+/* -------------------------------------------------------------------- */
+
+#ifdef WITH_EFFECTS
+
+static PyObject *
+_effect_mandelbrot(ImagingObject *self, PyObject *args) {
+ int xsize = 512;
+ int ysize = 512;
+ double extent[4];
+ int quality = 100;
+
+ extent[0] = -3;
+ extent[1] = -2.5;
+ extent[2] = 2;
+ extent[3] = 2.5;
+
+ if (!PyArg_ParseTuple(
+ args,
+ "|(ii)(dddd)i",
+ &xsize,
+ &ysize,
+ &extent[0],
+ &extent[1],
+ &extent[2],
+ &extent[3],
+ &quality)) {
+ return NULL;
+ }
+
+ return PyImagingNew(ImagingEffectMandelbrot(xsize, ysize, extent, quality));
+}
+
+static PyObject *
+_effect_noise(ImagingObject *self, PyObject *args) {
+ int xsize, ysize;
+ float sigma = 128;
+ if (!PyArg_ParseTuple(args, "(ii)|f", &xsize, &ysize, &sigma)) {
+ return NULL;
+ }
+
+ return PyImagingNew(ImagingEffectNoise(xsize, ysize, sigma));
+}
+
+static PyObject *
+_effect_spread(ImagingObject *self, PyObject *args) {
+ int dist;
+
+ if (!PyArg_ParseTuple(args, "i", &dist)) {
+ return NULL;
+ }
+
+ return PyImagingNew(ImagingEffectSpread(self->image, dist));
+}
+
+#endif
+
+/* -------------------------------------------------------------------- */
+/* UTILITIES */
+/* -------------------------------------------------------------------- */
+
+static PyObject *
+_getcodecstatus(PyObject *self, PyObject *args) {
+ int status;
+ char *msg;
+
+ if (!PyArg_ParseTuple(args, "i", &status)) {
+ return NULL;
+ }
+
+ switch (status) {
+ case IMAGING_CODEC_OVERRUN:
+ msg = "buffer overrun";
+ break;
+ case IMAGING_CODEC_BROKEN:
+ msg = "broken data stream";
+ break;
+ case IMAGING_CODEC_UNKNOWN:
+ msg = "unrecognized data stream contents";
+ break;
+ case IMAGING_CODEC_CONFIG:
+ msg = "codec configuration error";
+ break;
+ case IMAGING_CODEC_MEMORY:
+ msg = "out of memory";
+ break;
+ default:
+ Py_RETURN_NONE;
+ }
+
+ return PyUnicode_FromString(msg);
+}
+
+/* -------------------------------------------------------------------- */
+/* DEBUGGING HELPERS */
+/* -------------------------------------------------------------------- */
+
+static PyObject *
+_save_ppm(ImagingObject *self, PyObject *args) {
+ char *filename;
+
+ if (!PyArg_ParseTuple(args, "s", &filename)) {
+ return NULL;
+ }
+
+ if (!ImagingSavePPM(self->image, filename)) {
+ return NULL;
+ }
+
+ Py_INCREF(Py_None);
+ return Py_None;
+}
+
+/* -------------------------------------------------------------------- */
+
+/* methods */
+
+static struct PyMethodDef methods[] = {
+
+ /* Put commonly used methods first */
+ {"getpixel", (PyCFunction)_getpixel, METH_VARARGS},
+ {"putpixel", (PyCFunction)_putpixel, METH_VARARGS},
+
+ {"pixel_access", (PyCFunction)pixel_access_new, METH_VARARGS},
+
+ /* Standard processing methods (Image) */
+ {"color_lut_3d", (PyCFunction)_color_lut_3d, METH_VARARGS},
+ {"convert", (PyCFunction)_convert, METH_VARARGS},
+ {"convert2", (PyCFunction)_convert2, METH_VARARGS},
+ {"convert_matrix", (PyCFunction)_convert_matrix, METH_VARARGS},
+ {"convert_transparent", (PyCFunction)_convert_transparent, METH_VARARGS},
+ {"copy", (PyCFunction)_copy, METH_VARARGS},
+ {"crop", (PyCFunction)_crop, METH_VARARGS},
+ {"expand", (PyCFunction)_expand_image, METH_VARARGS},
+ {"filter", (PyCFunction)_filter, METH_VARARGS},
+ {"histogram", (PyCFunction)_histogram, METH_VARARGS},
+ {"entropy", (PyCFunction)_entropy, METH_VARARGS},
+#ifdef WITH_MODEFILTER
+ {"modefilter", (PyCFunction)_modefilter, METH_VARARGS},
+#endif
+ {"offset", (PyCFunction)_offset, METH_VARARGS},
+ {"paste", (PyCFunction)_paste, METH_VARARGS},
+ {"point", (PyCFunction)_point, METH_VARARGS},
+ {"point_transform", (PyCFunction)_point_transform, METH_VARARGS},
+ {"putdata", (PyCFunction)_putdata, METH_VARARGS},
+#ifdef WITH_QUANTIZE
+ {"quantize", (PyCFunction)_quantize, METH_VARARGS},
+#endif
+#ifdef WITH_RANKFILTER
+ {"rankfilter", (PyCFunction)_rankfilter, METH_VARARGS},
+#endif
+ {"resize", (PyCFunction)_resize, METH_VARARGS},
+ {"reduce", (PyCFunction)_reduce, METH_VARARGS},
+ {"transpose", (PyCFunction)_transpose, METH_VARARGS},
+ {"transform2", (PyCFunction)_transform2, METH_VARARGS},
+
+ {"isblock", (PyCFunction)_isblock, METH_NOARGS},
+
+ {"getbbox", (PyCFunction)_getbbox, METH_VARARGS},
+ {"getcolors", (PyCFunction)_getcolors, METH_VARARGS},
+ {"getextrema", (PyCFunction)_getextrema, METH_NOARGS},
+ {"getprojection", (PyCFunction)_getprojection, METH_NOARGS},
+
+ {"getband", (PyCFunction)_getband, METH_VARARGS},
+ {"putband", (PyCFunction)_putband, METH_VARARGS},
+ {"split", (PyCFunction)_split, METH_NOARGS},
+ {"fillband", (PyCFunction)_fillband, METH_VARARGS},
+
+ {"setmode", (PyCFunction)im_setmode, METH_VARARGS},
+
+ {"getpalette", (PyCFunction)_getpalette, METH_VARARGS},
+ {"getpalettemode", (PyCFunction)_getpalettemode, METH_NOARGS},
+ {"putpalette", (PyCFunction)_putpalette, METH_VARARGS},
+ {"putpalettealpha", (PyCFunction)_putpalettealpha, METH_VARARGS},
+ {"putpalettealphas", (PyCFunction)_putpalettealphas, METH_VARARGS},
+
+#ifdef WITH_IMAGECHOPS
+ /* Channel operations (ImageChops) */
+ {"chop_invert", (PyCFunction)_chop_invert, METH_NOARGS},
+ {"chop_lighter", (PyCFunction)_chop_lighter, METH_VARARGS},
+ {"chop_darker", (PyCFunction)_chop_darker, METH_VARARGS},
+ {"chop_difference", (PyCFunction)_chop_difference, METH_VARARGS},
+ {"chop_multiply", (PyCFunction)_chop_multiply, METH_VARARGS},
+ {"chop_screen", (PyCFunction)_chop_screen, METH_VARARGS},
+ {"chop_add", (PyCFunction)_chop_add, METH_VARARGS},
+ {"chop_subtract", (PyCFunction)_chop_subtract, METH_VARARGS},
+ {"chop_add_modulo", (PyCFunction)_chop_add_modulo, METH_VARARGS},
+ {"chop_subtract_modulo", (PyCFunction)_chop_subtract_modulo, METH_VARARGS},
+ {"chop_and", (PyCFunction)_chop_and, METH_VARARGS},
+ {"chop_or", (PyCFunction)_chop_or, METH_VARARGS},
+ {"chop_xor", (PyCFunction)_chop_xor, METH_VARARGS},
+ {"chop_soft_light", (PyCFunction)_chop_soft_light, METH_VARARGS},
+ {"chop_hard_light", (PyCFunction)_chop_hard_light, METH_VARARGS},
+ {"chop_overlay", (PyCFunction)_chop_overlay, METH_VARARGS},
+
+#endif
+
+#ifdef WITH_UNSHARPMASK
+ /* Kevin Cazabon's unsharpmask extension */
+ {"gaussian_blur", (PyCFunction)_gaussian_blur, METH_VARARGS},
+ {"unsharp_mask", (PyCFunction)_unsharp_mask, METH_VARARGS},
+#endif
+
+ {"box_blur", (PyCFunction)_box_blur, METH_VARARGS},
+
+#ifdef WITH_EFFECTS
+ /* Special effects */
+ {"effect_spread", (PyCFunction)_effect_spread, METH_VARARGS},
+#endif
+
+ /* Misc. */
+ {"new_block", (PyCFunction)_new_block, METH_VARARGS},
+
+ {"save_ppm", (PyCFunction)_save_ppm, METH_VARARGS},
+
+ {NULL, NULL} /* sentinel */
+};
+
+/* attributes */
+
+static PyObject *
+_getattr_mode(ImagingObject *self, void *closure) {
+ return PyUnicode_FromString(self->image->mode);
+}
+
+static PyObject *
+_getattr_size(ImagingObject *self, void *closure) {
+ return Py_BuildValue("ii", self->image->xsize, self->image->ysize);
+}
+
+static PyObject *
+_getattr_bands(ImagingObject *self, void *closure) {
+ return PyLong_FromLong(self->image->bands);
+}
+
+static PyObject *
+_getattr_id(ImagingObject *self, void *closure) {
+ return PyLong_FromSsize_t((Py_ssize_t)self->image);
+}
+
+static PyObject *
+_getattr_ptr(ImagingObject *self, void *closure) {
+ return PyCapsule_New(self->image, IMAGING_MAGIC, NULL);
+}
+
+static PyObject *
+_getattr_unsafe_ptrs(ImagingObject *self, void *closure) {
+ return Py_BuildValue(
+ "(sn)(sn)(sn)",
+ "image8",
+ self->image->image8,
+ "image32",
+ self->image->image32,
+ "image",
+ self->image->image);
+};
+
+static struct PyGetSetDef getsetters[] = {
+ {"mode", (getter)_getattr_mode},
+ {"size", (getter)_getattr_size},
+ {"bands", (getter)_getattr_bands},
+ {"id", (getter)_getattr_id},
+ {"ptr", (getter)_getattr_ptr},
+ {"unsafe_ptrs", (getter)_getattr_unsafe_ptrs},
+ {NULL}};
+
+/* basic sequence semantics */
+
+static Py_ssize_t
+image_length(ImagingObject *self) {
+ Imaging im = self->image;
+
+ return (Py_ssize_t)im->xsize * im->ysize;
+}
+
+static PyObject *
+image_item(ImagingObject *self, Py_ssize_t i) {
+ int x, y;
+ Imaging im = self->image;
+
+ if (im->xsize > 0) {
+ x = i % im->xsize;
+ y = i / im->xsize;
+ } else {
+ x = y = 0; /* leave it to getpixel to raise an exception */
+ }
+
+ return getpixel(im, self->access, x, y);
+}
+
+static PySequenceMethods image_as_sequence = {
+ (lenfunc)image_length, /*sq_length*/
+ (binaryfunc)NULL, /*sq_concat*/
+ (ssizeargfunc)NULL, /*sq_repeat*/
+ (ssizeargfunc)image_item, /*sq_item*/
+ (ssizessizeargfunc)NULL, /*sq_slice*/
+ (ssizeobjargproc)NULL, /*sq_ass_item*/
+ (ssizessizeobjargproc)NULL, /*sq_ass_slice*/
+};
+
+/* type description */
+
+static PyTypeObject Imaging_Type = {
+ PyVarObject_HEAD_INIT(NULL, 0) "ImagingCore", /*tp_name*/
+ sizeof(ImagingObject), /*tp_basicsize*/
+ 0, /*tp_itemsize*/
+ /* methods */
+ (destructor)_dealloc, /*tp_dealloc*/
+ 0, /*tp_vectorcall_offset*/
+ 0, /*tp_getattr*/
+ 0, /*tp_setattr*/
+ 0, /*tp_as_async*/
+ 0, /*tp_repr*/
+ 0, /*tp_as_number*/
+ &image_as_sequence, /*tp_as_sequence*/
+ 0, /*tp_as_mapping*/
+ 0, /*tp_hash*/
+ 0, /*tp_call*/
+ 0, /*tp_str*/
+ 0, /*tp_getattro*/
+ 0, /*tp_setattro*/
+ 0, /*tp_as_buffer*/
+ Py_TPFLAGS_DEFAULT, /*tp_flags*/
+ 0, /*tp_doc*/
+ 0, /*tp_traverse*/
+ 0, /*tp_clear*/
+ 0, /*tp_richcompare*/
+ 0, /*tp_weaklistoffset*/
+ 0, /*tp_iter*/
+ 0, /*tp_iternext*/
+ methods, /*tp_methods*/
+ 0, /*tp_members*/
+ getsetters, /*tp_getset*/
+};
+
+#ifdef WITH_IMAGEDRAW
+
+static PyTypeObject ImagingFont_Type = {
+ PyVarObject_HEAD_INIT(NULL, 0) "ImagingFont", /*tp_name*/
+ sizeof(ImagingFontObject), /*tp_basicsize*/
+ 0, /*tp_itemsize*/
+ /* methods */
+ (destructor)_font_dealloc, /*tp_dealloc*/
+ 0, /*tp_vectorcall_offset*/
+ 0, /*tp_getattr*/
+ 0, /*tp_setattr*/
+ 0, /*tp_as_async*/
+ 0, /*tp_repr*/
+ 0, /*tp_as_number*/
+ 0, /*tp_as_sequence*/
+ 0, /*tp_as_mapping*/
+ 0, /*tp_hash*/
+ 0, /*tp_call*/
+ 0, /*tp_str*/
+ 0, /*tp_getattro*/
+ 0, /*tp_setattro*/
+ 0, /*tp_as_buffer*/
+ Py_TPFLAGS_DEFAULT, /*tp_flags*/
+ 0, /*tp_doc*/
+ 0, /*tp_traverse*/
+ 0, /*tp_clear*/
+ 0, /*tp_richcompare*/
+ 0, /*tp_weaklistoffset*/
+ 0, /*tp_iter*/
+ 0, /*tp_iternext*/
+ _font_methods, /*tp_methods*/
+ 0, /*tp_members*/
+ 0, /*tp_getset*/
+};
+
+static PyTypeObject ImagingDraw_Type = {
+ PyVarObject_HEAD_INIT(NULL, 0) "ImagingDraw", /*tp_name*/
+ sizeof(ImagingDrawObject), /*tp_basicsize*/
+ 0, /*tp_itemsize*/
+ /* methods */
+ (destructor)_draw_dealloc, /*tp_dealloc*/
+ 0, /*tp_vectorcall_offset*/
+ 0, /*tp_getattr*/
+ 0, /*tp_setattr*/
+ 0, /*tp_as_async*/
+ 0, /*tp_repr*/
+ 0, /*tp_as_number*/
+ 0, /*tp_as_sequence*/
+ 0, /*tp_as_mapping*/
+ 0, /*tp_hash*/
+ 0, /*tp_call*/
+ 0, /*tp_str*/
+ 0, /*tp_getattro*/
+ 0, /*tp_setattro*/
+ 0, /*tp_as_buffer*/
+ Py_TPFLAGS_DEFAULT, /*tp_flags*/
+ 0, /*tp_doc*/
+ 0, /*tp_traverse*/
+ 0, /*tp_clear*/
+ 0, /*tp_richcompare*/
+ 0, /*tp_weaklistoffset*/
+ 0, /*tp_iter*/
+ 0, /*tp_iternext*/
+ _draw_methods, /*tp_methods*/
+ 0, /*tp_members*/
+ 0, /*tp_getset*/
+};
+
+#endif
+
+static PyMappingMethods pixel_access_as_mapping = {
+ (lenfunc)NULL, /*mp_length*/
+ (binaryfunc)pixel_access_getitem, /*mp_subscript*/
+ (objobjargproc)pixel_access_setitem, /*mp_ass_subscript*/
+};
+
+/* type description */
+
+static PyTypeObject PixelAccess_Type = {
+ PyVarObject_HEAD_INIT(NULL, 0) "PixelAccess", /*tp_name*/
+ sizeof(PixelAccessObject), /*tp_basicsize*/
+ 0, /*tp_itemsize*/
+ /* methods */
+ (destructor)pixel_access_dealloc, /*tp_dealloc*/
+ 0, /*tp_vectorcall_offset*/
+ 0, /*tp_getattr*/
+ 0, /*tp_setattr*/
+ 0, /*tp_as_async*/
+ 0, /*tp_repr*/
+ 0, /*tp_as_number*/
+ 0, /*tp_as_sequence*/
+ &pixel_access_as_mapping, /*tp_as_mapping*/
+ 0 /*tp_hash*/
+};
+
+/* -------------------------------------------------------------------- */
+
+static PyObject *
+_get_stats(PyObject *self, PyObject *args) {
+ PyObject *d;
+ PyObject *v;
+ ImagingMemoryArena arena = &ImagingDefaultArena;
+
+ if (!PyArg_ParseTuple(args, ":get_stats")) {
+ return NULL;
+ }
+
+ d = PyDict_New();
+ if (!d) {
+ return NULL;
+ }
+ v = PyLong_FromLong(arena->stats_new_count);
+ PyDict_SetItemString(d, "new_count", v ? v : Py_None);
+ Py_XDECREF(v);
+
+ v = PyLong_FromLong(arena->stats_allocated_blocks);
+ PyDict_SetItemString(d, "allocated_blocks", v ? v : Py_None);
+ Py_XDECREF(v);
+
+ v = PyLong_FromLong(arena->stats_reused_blocks);
+ PyDict_SetItemString(d, "reused_blocks", v ? v : Py_None);
+ Py_XDECREF(v);
+
+ v = PyLong_FromLong(arena->stats_reallocated_blocks);
+ PyDict_SetItemString(d, "reallocated_blocks", v ? v : Py_None);
+ Py_XDECREF(v);
+
+ v = PyLong_FromLong(arena->stats_freed_blocks);
+ PyDict_SetItemString(d, "freed_blocks", v ? v : Py_None);
+ Py_XDECREF(v);
+
+ v = PyLong_FromLong(arena->blocks_cached);
+ PyDict_SetItemString(d, "blocks_cached", v ? v : Py_None);
+ Py_XDECREF(v);
+ return d;
+}
+
+static PyObject *
+_reset_stats(PyObject *self, PyObject *args) {
+ ImagingMemoryArena arena = &ImagingDefaultArena;
+
+ if (!PyArg_ParseTuple(args, ":reset_stats")) {
+ return NULL;
+ }
+
+ arena->stats_new_count = 0;
+ arena->stats_allocated_blocks = 0;
+ arena->stats_reused_blocks = 0;
+ arena->stats_reallocated_blocks = 0;
+ arena->stats_freed_blocks = 0;
+
+ Py_INCREF(Py_None);
+ return Py_None;
+}
+
+static PyObject *
+_get_alignment(PyObject *self, PyObject *args) {
+ if (!PyArg_ParseTuple(args, ":get_alignment")) {
+ return NULL;
+ }
+
+ return PyLong_FromLong(ImagingDefaultArena.alignment);
+}
+
+static PyObject *
+_get_block_size(PyObject *self, PyObject *args) {
+ if (!PyArg_ParseTuple(args, ":get_block_size")) {
+ return NULL;
+ }
+
+ return PyLong_FromLong(ImagingDefaultArena.block_size);
+}
+
+static PyObject *
+_get_blocks_max(PyObject *self, PyObject *args) {
+ if (!PyArg_ParseTuple(args, ":get_blocks_max")) {
+ return NULL;
+ }
+
+ return PyLong_FromLong(ImagingDefaultArena.blocks_max);
+}
+
+static PyObject *
+_set_alignment(PyObject *self, PyObject *args) {
+ int alignment;
+ if (!PyArg_ParseTuple(args, "i:set_alignment", &alignment)) {
+ return NULL;
+ }
+
+ if (alignment < 1 || alignment > 128) {
+ PyErr_SetString(PyExc_ValueError, "alignment should be from 1 to 128");
+ return NULL;
+ }
+ /* Is power of two */
+ if (alignment & (alignment - 1)) {
+ PyErr_SetString(PyExc_ValueError, "alignment should be power of two");
+ return NULL;
+ }
+
+ ImagingDefaultArena.alignment = alignment;
+
+ Py_INCREF(Py_None);
+ return Py_None;
+}
+
+static PyObject *
+_set_block_size(PyObject *self, PyObject *args) {
+ int block_size;
+ if (!PyArg_ParseTuple(args, "i:set_block_size", &block_size)) {
+ return NULL;
+ }
+
+ if (block_size <= 0) {
+ PyErr_SetString(PyExc_ValueError, "block_size should be greater than 0");
+ return NULL;
+ }
+
+ if (block_size & 0xfff) {
+ PyErr_SetString(PyExc_ValueError, "block_size should be multiple of 4096");
+ return NULL;
+ }
+
+ ImagingDefaultArena.block_size = block_size;
+
+ Py_INCREF(Py_None);
+ return Py_None;
+}
+
+static PyObject *
+_set_blocks_max(PyObject *self, PyObject *args) {
+ int blocks_max;
+ if (!PyArg_ParseTuple(args, "i:set_blocks_max", &blocks_max)) {
+ return NULL;
+ }
+
+ if (blocks_max < 0) {
+ PyErr_SetString(PyExc_ValueError, "blocks_max should be greater than 0");
+ return NULL;
+ } else if (
+ (unsigned long)blocks_max >
+ SIZE_MAX / sizeof(ImagingDefaultArena.blocks_pool[0])) {
+ PyErr_SetString(PyExc_ValueError, "blocks_max is too large");
+ return NULL;
+ }
+
+ if (!ImagingMemorySetBlocksMax(&ImagingDefaultArena, blocks_max)) {
+ return ImagingError_MemoryError();
+ }
+
+ Py_INCREF(Py_None);
+ return Py_None;
+}
+
+static PyObject *
+_clear_cache(PyObject *self, PyObject *args) {
+ int i = 0;
+
+ if (!PyArg_ParseTuple(args, "|i:clear_cache", &i)) {
+ return NULL;
+ }
+
+ ImagingMemoryClearCache(&ImagingDefaultArena, i);
+
+ Py_INCREF(Py_None);
+ return Py_None;
+}
+
+/* -------------------------------------------------------------------- */
+
+/* FIXME: this is something of a mess. Should replace this with
+ pluggable codecs, but not before PIL 1.2 */
+
+/* Decoders (in decode.c) */
+extern PyObject *
+PyImaging_BcnDecoderNew(PyObject *self, PyObject *args);
+extern PyObject *
+PyImaging_BitDecoderNew(PyObject *self, PyObject *args);
+extern PyObject *
+PyImaging_FliDecoderNew(PyObject *self, PyObject *args);
+extern PyObject *
+PyImaging_GifDecoderNew(PyObject *self, PyObject *args);
+extern PyObject *
+PyImaging_HexDecoderNew(PyObject *self, PyObject *args);
+extern PyObject *
+PyImaging_JpegDecoderNew(PyObject *self, PyObject *args);
+extern PyObject *
+PyImaging_Jpeg2KDecoderNew(PyObject *self, PyObject *args);
+extern PyObject *
+PyImaging_LibTiffDecoderNew(PyObject *self, PyObject *args);
+extern PyObject *
+PyImaging_PackbitsDecoderNew(PyObject *self, PyObject *args);
+extern PyObject *
+PyImaging_PcdDecoderNew(PyObject *self, PyObject *args);
+extern PyObject *
+PyImaging_PcxDecoderNew(PyObject *self, PyObject *args);
+extern PyObject *
+PyImaging_RawDecoderNew(PyObject *self, PyObject *args);
+extern PyObject *
+PyImaging_SgiRleDecoderNew(PyObject *self, PyObject *args);
+extern PyObject *
+PyImaging_SunRleDecoderNew(PyObject *self, PyObject *args);
+extern PyObject *
+PyImaging_TgaRleDecoderNew(PyObject *self, PyObject *args);
+extern PyObject *
+PyImaging_XbmDecoderNew(PyObject *self, PyObject *args);
+extern PyObject *
+PyImaging_ZipDecoderNew(PyObject *self, PyObject *args);
+
+/* Encoders (in encode.c) */
+extern PyObject *
+PyImaging_EpsEncoderNew(PyObject *self, PyObject *args);
+extern PyObject *
+PyImaging_GifEncoderNew(PyObject *self, PyObject *args);
+extern PyObject *
+PyImaging_JpegEncoderNew(PyObject *self, PyObject *args);
+extern PyObject *
+PyImaging_Jpeg2KEncoderNew(PyObject *self, PyObject *args);
+extern PyObject *
+PyImaging_PcxEncoderNew(PyObject *self, PyObject *args);
+extern PyObject *
+PyImaging_RawEncoderNew(PyObject *self, PyObject *args);
+extern PyObject *
+PyImaging_TgaRleEncoderNew(PyObject *self, PyObject *args);
+extern PyObject *
+PyImaging_XbmEncoderNew(PyObject *self, PyObject *args);
+extern PyObject *
+PyImaging_ZipEncoderNew(PyObject *self, PyObject *args);
+extern PyObject *
+PyImaging_LibTiffEncoderNew(PyObject *self, PyObject *args);
+
+/* Display support etc (in display.c) */
+#ifdef _WIN32
+extern PyObject *
+PyImaging_CreateWindowWin32(PyObject *self, PyObject *args);
+extern PyObject *
+PyImaging_DisplayWin32(PyObject *self, PyObject *args);
+extern PyObject *
+PyImaging_DisplayModeWin32(PyObject *self, PyObject *args);
+extern PyObject *
+PyImaging_GrabScreenWin32(PyObject *self, PyObject *args);
+extern PyObject *
+PyImaging_GrabClipboardWin32(PyObject *self, PyObject *args);
+extern PyObject *
+PyImaging_EventLoopWin32(PyObject *self, PyObject *args);
+extern PyObject *
+PyImaging_DrawWmf(PyObject *self, PyObject *args);
+#endif
+#ifdef HAVE_XCB
+extern PyObject *
+PyImaging_GrabScreenX11(PyObject *self, PyObject *args);
+#endif
+
+/* Experimental path stuff (in path.c) */
+extern PyObject *
+PyPath_Create(ImagingObject *self, PyObject *args);
+
+/* Experimental outline stuff (in outline.c) */
+extern PyObject *
+PyOutline_Create(ImagingObject *self, PyObject *args);
+
+extern PyObject *
+PyImaging_MapBuffer(PyObject *self, PyObject *args);
+
+static PyMethodDef functions[] = {
+
+ /* Object factories */
+ {"alpha_composite", (PyCFunction)_alpha_composite, METH_VARARGS},
+ {"blend", (PyCFunction)_blend, METH_VARARGS},
+ {"fill", (PyCFunction)_fill, METH_VARARGS},
+ {"new", (PyCFunction)_new, METH_VARARGS},
+ {"merge", (PyCFunction)_merge, METH_VARARGS},
+
+ /* Functions */
+ {"convert", (PyCFunction)_convert2, METH_VARARGS},
+
+ /* Codecs */
+ {"bcn_decoder", (PyCFunction)PyImaging_BcnDecoderNew, METH_VARARGS},
+ {"bit_decoder", (PyCFunction)PyImaging_BitDecoderNew, METH_VARARGS},
+ {"eps_encoder", (PyCFunction)PyImaging_EpsEncoderNew, METH_VARARGS},
+ {"fli_decoder", (PyCFunction)PyImaging_FliDecoderNew, METH_VARARGS},
+ {"gif_decoder", (PyCFunction)PyImaging_GifDecoderNew, METH_VARARGS},
+ {"gif_encoder", (PyCFunction)PyImaging_GifEncoderNew, METH_VARARGS},
+ {"hex_decoder", (PyCFunction)PyImaging_HexDecoderNew, METH_VARARGS},
+ {"hex_encoder", (PyCFunction)PyImaging_EpsEncoderNew, METH_VARARGS}, /* EPS=HEX! */
+#ifdef HAVE_LIBJPEG
+ {"jpeg_decoder", (PyCFunction)PyImaging_JpegDecoderNew, METH_VARARGS},
+ {"jpeg_encoder", (PyCFunction)PyImaging_JpegEncoderNew, METH_VARARGS},
+#endif
+#ifdef HAVE_OPENJPEG
+ {"jpeg2k_decoder", (PyCFunction)PyImaging_Jpeg2KDecoderNew, METH_VARARGS},
+ {"jpeg2k_encoder", (PyCFunction)PyImaging_Jpeg2KEncoderNew, METH_VARARGS},
+#endif
+#ifdef HAVE_LIBTIFF
+ {"libtiff_decoder", (PyCFunction)PyImaging_LibTiffDecoderNew, METH_VARARGS},
+ {"libtiff_encoder", (PyCFunction)PyImaging_LibTiffEncoderNew, METH_VARARGS},
+#endif
+ {"packbits_decoder", (PyCFunction)PyImaging_PackbitsDecoderNew, METH_VARARGS},
+ {"pcd_decoder", (PyCFunction)PyImaging_PcdDecoderNew, METH_VARARGS},
+ {"pcx_decoder", (PyCFunction)PyImaging_PcxDecoderNew, METH_VARARGS},
+ {"pcx_encoder", (PyCFunction)PyImaging_PcxEncoderNew, METH_VARARGS},
+ {"raw_decoder", (PyCFunction)PyImaging_RawDecoderNew, METH_VARARGS},
+ {"raw_encoder", (PyCFunction)PyImaging_RawEncoderNew, METH_VARARGS},
+ {"sgi_rle_decoder", (PyCFunction)PyImaging_SgiRleDecoderNew, METH_VARARGS},
+ {"sun_rle_decoder", (PyCFunction)PyImaging_SunRleDecoderNew, METH_VARARGS},
+ {"tga_rle_decoder", (PyCFunction)PyImaging_TgaRleDecoderNew, METH_VARARGS},
+ {"tga_rle_encoder", (PyCFunction)PyImaging_TgaRleEncoderNew, METH_VARARGS},
+ {"xbm_decoder", (PyCFunction)PyImaging_XbmDecoderNew, METH_VARARGS},
+ {"xbm_encoder", (PyCFunction)PyImaging_XbmEncoderNew, METH_VARARGS},
+#ifdef HAVE_LIBZ
+ {"zip_decoder", (PyCFunction)PyImaging_ZipDecoderNew, METH_VARARGS},
+ {"zip_encoder", (PyCFunction)PyImaging_ZipEncoderNew, METH_VARARGS},
+#endif
+
+/* Memory mapping */
+#ifdef WITH_MAPPING
+ {"map_buffer", (PyCFunction)PyImaging_MapBuffer, METH_VARARGS},
+#endif
+
+/* Display support */
+#ifdef _WIN32
+ {"display", (PyCFunction)PyImaging_DisplayWin32, METH_VARARGS},
+ {"display_mode", (PyCFunction)PyImaging_DisplayModeWin32, METH_VARARGS},
+ {"grabscreen_win32", (PyCFunction)PyImaging_GrabScreenWin32, METH_VARARGS},
+ {"grabclipboard_win32", (PyCFunction)PyImaging_GrabClipboardWin32, METH_VARARGS},
+ {"createwindow", (PyCFunction)PyImaging_CreateWindowWin32, METH_VARARGS},
+ {"eventloop", (PyCFunction)PyImaging_EventLoopWin32, METH_VARARGS},
+ {"drawwmf", (PyCFunction)PyImaging_DrawWmf, METH_VARARGS},
+#endif
+#ifdef HAVE_XCB
+ {"grabscreen_x11", (PyCFunction)PyImaging_GrabScreenX11, METH_VARARGS},
+#endif
+
+ /* Utilities */
+ {"getcodecstatus", (PyCFunction)_getcodecstatus, METH_VARARGS},
+
+/* Special effects (experimental) */
+#ifdef WITH_EFFECTS
+ {"effect_mandelbrot", (PyCFunction)_effect_mandelbrot, METH_VARARGS},
+ {"effect_noise", (PyCFunction)_effect_noise, METH_VARARGS},
+ {"linear_gradient", (PyCFunction)_linear_gradient, METH_VARARGS},
+ {"radial_gradient", (PyCFunction)_radial_gradient, METH_VARARGS},
+ {"wedge", (PyCFunction)_linear_gradient, METH_VARARGS}, /* Compatibility */
+#endif
+
+/* Drawing support stuff */
+#ifdef WITH_IMAGEDRAW
+ {"font", (PyCFunction)_font_new, METH_VARARGS},
+ {"draw", (PyCFunction)_draw_new, METH_VARARGS},
+#endif
+
+/* Experimental path stuff */
+#ifdef WITH_IMAGEPATH
+ {"path", (PyCFunction)PyPath_Create, METH_VARARGS},
+#endif
+
+/* Experimental arrow graphics stuff */
+#ifdef WITH_ARROW
+ {"outline", (PyCFunction)PyOutline_Create, METH_VARARGS},
+#endif
+
+ /* Resource management */
+ {"get_stats", (PyCFunction)_get_stats, METH_VARARGS},
+ {"reset_stats", (PyCFunction)_reset_stats, METH_VARARGS},
+ {"get_alignment", (PyCFunction)_get_alignment, METH_VARARGS},
+ {"get_block_size", (PyCFunction)_get_block_size, METH_VARARGS},
+ {"get_blocks_max", (PyCFunction)_get_blocks_max, METH_VARARGS},
+ {"set_alignment", (PyCFunction)_set_alignment, METH_VARARGS},
+ {"set_block_size", (PyCFunction)_set_block_size, METH_VARARGS},
+ {"set_blocks_max", (PyCFunction)_set_blocks_max, METH_VARARGS},
+ {"clear_cache", (PyCFunction)_clear_cache, METH_VARARGS},
+
+ {NULL, NULL} /* sentinel */
+};
+
+static int
+setup_module(PyObject *m) {
+ PyObject *d = PyModule_GetDict(m);
+ const char *version = (char *)PILLOW_VERSION;
+
+ /* Ready object types */
+ if (PyType_Ready(&Imaging_Type) < 0) {
+ return -1;
+ }
+
+#ifdef WITH_IMAGEDRAW
+ if (PyType_Ready(&ImagingFont_Type) < 0) {
+ return -1;
+ }
+
+ if (PyType_Ready(&ImagingDraw_Type) < 0) {
+ return -1;
+ }
+#endif
+ if (PyType_Ready(&PixelAccess_Type) < 0) {
+ return -1;
+ }
+
+ ImagingAccessInit();
+
+#ifdef HAVE_LIBJPEG
+ {
+ extern const char *ImagingJpegVersion(void);
+ PyObject *v = PyUnicode_FromString(ImagingJpegVersion());
+ PyDict_SetItemString(d, "jpeglib_version", v ? v : Py_None);
+ Py_XDECREF(v);
+ }
+#endif
+
+#ifdef HAVE_OPENJPEG
+ {
+ extern const char *ImagingJpeg2KVersion(void);
+ PyObject *v = PyUnicode_FromString(ImagingJpeg2KVersion());
+ PyDict_SetItemString(d, "jp2klib_version", v ? v : Py_None);
+ Py_XDECREF(v);
+ }
+#endif
+
+ PyObject *have_libjpegturbo;
+#ifdef LIBJPEG_TURBO_VERSION
+ have_libjpegturbo = Py_True;
+ {
+#define tostr1(a) #a
+#define tostr(a) tostr1(a)
+ PyObject *v = PyUnicode_FromString(tostr(LIBJPEG_TURBO_VERSION));
+ PyDict_SetItemString(d, "libjpeg_turbo_version", v ? v : Py_None);
+ Py_XDECREF(v);
+#undef tostr
+#undef tostr1
+ }
+#else
+ have_libjpegturbo = Py_False;
+#endif
+ Py_INCREF(have_libjpegturbo);
+ PyModule_AddObject(m, "HAVE_LIBJPEGTURBO", have_libjpegturbo);
+
+ PyObject *have_libimagequant;
+#ifdef HAVE_LIBIMAGEQUANT
+ have_libimagequant = Py_True;
+ {
+ extern const char *ImagingImageQuantVersion(void);
+ PyObject *v = PyUnicode_FromString(ImagingImageQuantVersion());
+ PyDict_SetItemString(d, "imagequant_version", v ? v : Py_None);
+ Py_XDECREF(v);
+ }
+#else
+ have_libimagequant = Py_False;
+#endif
+ Py_INCREF(have_libimagequant);
+ PyModule_AddObject(m, "HAVE_LIBIMAGEQUANT", have_libimagequant);
+
+#ifdef HAVE_LIBZ
+ /* zip encoding strategies */
+ PyModule_AddIntConstant(m, "DEFAULT_STRATEGY", Z_DEFAULT_STRATEGY);
+ PyModule_AddIntConstant(m, "FILTERED", Z_FILTERED);
+ PyModule_AddIntConstant(m, "HUFFMAN_ONLY", Z_HUFFMAN_ONLY);
+ PyModule_AddIntConstant(m, "RLE", Z_RLE);
+ PyModule_AddIntConstant(m, "FIXED", Z_FIXED);
+ {
+ extern const char *ImagingZipVersion(void);
+ PyObject *v = PyUnicode_FromString(ImagingZipVersion());
+ PyDict_SetItemString(d, "zlib_version", v ? v : Py_None);
+ Py_XDECREF(v);
+ }
+#endif
+
+#ifdef HAVE_LIBTIFF
+ {
+ extern const char *ImagingTiffVersion(void);
+ PyObject *v = PyUnicode_FromString(ImagingTiffVersion());
+ PyDict_SetItemString(d, "libtiff_version", v ? v : Py_None);
+ Py_XDECREF(v);
+
+ // Test for libtiff 4.0 or later, excluding libtiff 3.9.6 and 3.9.7
+ PyObject *support_custom_tags;
+#if TIFFLIB_VERSION >= 20111221 && TIFFLIB_VERSION != 20120218 && \
+ TIFFLIB_VERSION != 20120922
+ support_custom_tags = Py_True;
+#else
+ support_custom_tags = Py_False;
+#endif
+ PyDict_SetItemString(d, "libtiff_support_custom_tags", support_custom_tags);
+ }
+#endif
+
+ PyObject *have_xcb;
+#ifdef HAVE_XCB
+ have_xcb = Py_True;
+#else
+ have_xcb = Py_False;
+#endif
+ Py_INCREF(have_xcb);
+ PyModule_AddObject(m, "HAVE_XCB", have_xcb);
+
+ PyObject *pillow_version = PyUnicode_FromString(version);
+ PyDict_SetItemString(d, "PILLOW_VERSION", pillow_version ? pillow_version : Py_None);
+ Py_XDECREF(pillow_version);
+
+ return 0;
+}
+
+PyMODINIT_FUNC
+PyInit__imaging(void) {
+ PyObject *m;
+
+ static PyModuleDef module_def = {
+ PyModuleDef_HEAD_INIT,
+ "_imaging", /* m_name */
+ NULL, /* m_doc */
+ -1, /* m_size */
+ functions, /* m_methods */
+ };
+
+ m = PyModule_Create(&module_def);
+
+ if (setup_module(m) < 0) {
+ Py_DECREF(m);
+ return NULL;
+ }
+
+ return m;
+}
diff --git a/contrib/python/Pillow/py3/_imagingcms.c b/contrib/python/Pillow/py3/_imagingcms.c
new file mode 100644
index 00000000000..56d5d73f83c
--- /dev/null
+++ b/contrib/python/Pillow/py3/_imagingcms.c
@@ -0,0 +1,1563 @@
+/*
+ * pyCMS
+ * a Python / PIL interface to the littleCMS ICC Color Management System
+ * Copyright (C) 2002-2003 Kevin Cazabon
+ * https://www.cazabon.com
+ * Adapted/reworked for PIL by Fredrik Lundh
+ * Copyright (c) 2009 Fredrik Lundh
+ * Updated to LCMS2
+ * Copyright (c) 2013 Eric Soroos
+ *
+ * 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
+ */
+
+#define COPYRIGHTINFO \
+ "\
+pyCMS\n\
+a Python / PIL interface to the littleCMS ICC Color Management System\n\
+Copyright (C) 2002-2003 Kevin Cazabon\n\
+https://www.cazabon.com\n\
+"
+
+#define PY_SSIZE_T_CLEAN
+#include "Python.h" // Include before wchar.h so _GNU_SOURCE is set
+#include "wchar.h"
+#include "datetime.h"
+
+#include "lcms2.h"
+#include "libImaging/Imaging.h"
+
+#define PYCMSVERSION "1.0.0 pil"
+
+/* version history */
+
+/*
+ 1.0.0 pil Integrating littleCMS2
+ 0.1.0 pil integration & refactoring
+ 0.0.2 alpha: Minor updates, added interfaces to littleCMS features, Jan 6, 2003
+ - fixed some memory holes in how transforms/profiles were created and passed back to
+ Python due to improper destructor setup for PyCObjects
+ - added buildProofTransformFromOpenProfiles() function
+ - eliminated some code redundancy, centralizing several common tasks with internal
+ functions
+
+ 0.0.1 alpha: First public release Dec 26, 2002
+
+*/
+
+/* known to-do list with current version:
+
+ Verify that PILmode->littleCMStype conversion in findLCMStype is correct for all
+ PIL modes (it probably isn't for the more obscure ones)
+
+ Add support for creating custom RGB profiles on the fly
+ Add support for checking presence of a specific tag in a profile
+ Add support for other littleCMS features as required
+
+*/
+
+/*
+ INTENT_PERCEPTUAL 0
+ INTENT_RELATIVE_COLORIMETRIC 1
+ INTENT_SATURATION 2
+ INTENT_ABSOLUTE_COLORIMETRIC 3
+*/
+
+/* -------------------------------------------------------------------- */
+/* wrapper classes */
+
+/* a profile represents the ICC characteristics for a specific device */
+
+typedef struct {
+ PyObject_HEAD cmsHPROFILE profile;
+} CmsProfileObject;
+
+static PyTypeObject CmsProfile_Type;
+
+#define CmsProfile_Check(op) (Py_TYPE(op) == &CmsProfile_Type)
+
+static PyObject *
+cms_profile_new(cmsHPROFILE profile) {
+ CmsProfileObject *self;
+
+ self = PyObject_New(CmsProfileObject, &CmsProfile_Type);
+ if (!self) {
+ return NULL;
+ }
+
+ self->profile = profile;
+
+ return (PyObject *)self;
+}
+
+static PyObject *
+cms_profile_open(PyObject *self, PyObject *args) {
+ cmsHPROFILE hProfile;
+
+ char *sProfile;
+ if (!PyArg_ParseTuple(args, "s:profile_open", &sProfile)) {
+ return NULL;
+ }
+
+ hProfile = cmsOpenProfileFromFile(sProfile, "r");
+ if (!hProfile) {
+ PyErr_SetString(PyExc_OSError, "cannot open profile file");
+ return NULL;
+ }
+
+ return cms_profile_new(hProfile);
+}
+
+static PyObject *
+cms_profile_frombytes(PyObject *self, PyObject *args) {
+ cmsHPROFILE hProfile;
+
+ char *pProfile;
+ Py_ssize_t nProfile;
+ if (!PyArg_ParseTuple(args, "y#:profile_frombytes", &pProfile, &nProfile)) {
+ return NULL;
+ }
+
+ hProfile = cmsOpenProfileFromMem(pProfile, nProfile);
+ if (!hProfile) {
+ PyErr_SetString(PyExc_OSError, "cannot open profile from string");
+ return NULL;
+ }
+
+ return cms_profile_new(hProfile);
+}
+
+static PyObject *
+cms_profile_tobytes(PyObject *self, PyObject *args) {
+ char *pProfile = NULL;
+ cmsUInt32Number nProfile;
+ PyObject *CmsProfile;
+
+ cmsHPROFILE *profile;
+
+ PyObject *ret;
+ if (!PyArg_ParseTuple(args, "O", &CmsProfile)) {
+ return NULL;
+ }
+
+ profile = ((CmsProfileObject *)CmsProfile)->profile;
+
+ if (!cmsSaveProfileToMem(profile, pProfile, &nProfile)) {
+ PyErr_SetString(PyExc_OSError, "Could not determine profile size");
+ return NULL;
+ }
+
+ pProfile = (char *)malloc(nProfile);
+ if (!pProfile) {
+ PyErr_SetString(PyExc_OSError, "Out of Memory");
+ return NULL;
+ }
+
+ if (!cmsSaveProfileToMem(profile, pProfile, &nProfile)) {
+ PyErr_SetString(PyExc_OSError, "Could not get profile");
+ free(pProfile);
+ return NULL;
+ }
+
+ ret = PyBytes_FromStringAndSize(pProfile, (Py_ssize_t)nProfile);
+
+ free(pProfile);
+ return ret;
+}
+
+static void
+cms_profile_dealloc(CmsProfileObject *self) {
+ (void)cmsCloseProfile(self->profile);
+ PyObject_Del(self);
+}
+
+/* a transform represents the mapping between two profiles */
+
+typedef struct {
+ PyObject_HEAD char mode_in[8];
+ char mode_out[8];
+ cmsHTRANSFORM transform;
+} CmsTransformObject;
+
+static PyTypeObject CmsTransform_Type;
+
+#define CmsTransform_Check(op) (Py_TYPE(op) == &CmsTransform_Type)
+
+static PyObject *
+cms_transform_new(cmsHTRANSFORM transform, char *mode_in, char *mode_out) {
+ CmsTransformObject *self;
+
+ self = PyObject_New(CmsTransformObject, &CmsTransform_Type);
+ if (!self) {
+ return NULL;
+ }
+
+ self->transform = transform;
+
+ strcpy(self->mode_in, mode_in);
+ strcpy(self->mode_out, mode_out);
+
+ return (PyObject *)self;
+}
+
+static void
+cms_transform_dealloc(CmsTransformObject *self) {
+ cmsDeleteTransform(self->transform);
+ PyObject_Del(self);
+}
+
+/* -------------------------------------------------------------------- */
+/* internal functions */
+
+static cmsUInt32Number
+findLCMStype(char *PILmode) {
+ if (strcmp(PILmode, "RGB") == 0) {
+ return TYPE_RGBA_8;
+ } else if (strcmp(PILmode, "RGBA") == 0) {
+ return TYPE_RGBA_8;
+ } else if (strcmp(PILmode, "RGBX") == 0) {
+ return TYPE_RGBA_8;
+ } else if (strcmp(PILmode, "RGBA;16B") == 0) {
+ return TYPE_RGBA_16;
+ } else if (strcmp(PILmode, "CMYK") == 0) {
+ return TYPE_CMYK_8;
+ } else if (strcmp(PILmode, "L") == 0) {
+ return TYPE_GRAY_8;
+ } else if (strcmp(PILmode, "L;16") == 0) {
+ return TYPE_GRAY_16;
+ } else if (strcmp(PILmode, "L;16B") == 0) {
+ return TYPE_GRAY_16_SE;
+ } else if (strcmp(PILmode, "YCCA") == 0) {
+ return TYPE_YCbCr_8;
+ } else if (strcmp(PILmode, "YCC") == 0) {
+ return TYPE_YCbCr_8;
+ } else if (strcmp(PILmode, "LAB") == 0) {
+ // LabX equivalent like ALab, but not reversed -- no #define in lcms2
+ return (COLORSPACE_SH(PT_LabV2) | CHANNELS_SH(3) | BYTES_SH(1) | EXTRA_SH(1));
+ }
+
+ else {
+ /* take a wild guess... but you probably should fail instead. */
+ return TYPE_GRAY_8; /* so there's no buffer overrun... */
+ }
+}
+
+#define Cms_Min(a, b) ((a) < (b) ? (a) : (b))
+
+static int
+pyCMSgetAuxChannelChannel(cmsUInt32Number format, int auxChannelNdx) {
+ int numColors = T_CHANNELS(format);
+ int numExtras = T_EXTRA(format);
+
+ if (T_SWAPFIRST(format) && T_DOSWAP(format)) {
+ // reverse order, before anything but last extra is shifted last
+ if (auxChannelNdx == numExtras - 1) {
+ return numColors + numExtras - 1;
+ } else {
+ return numExtras - 2 - auxChannelNdx;
+ }
+ } else if (T_SWAPFIRST(format)) {
+ // in order, after color channels, but last extra is shifted to first
+ if (auxChannelNdx == numExtras - 1) {
+ return 0;
+ } else {
+ return numColors + 1 + auxChannelNdx;
+ }
+ } else if (T_DOSWAP(format)) {
+ // reverse order, before anything
+ return numExtras - 1 - auxChannelNdx;
+ } else {
+ // in order, after color channels
+ return numColors + auxChannelNdx;
+ }
+}
+
+static void
+pyCMScopyAux(cmsHTRANSFORM hTransform, Imaging imDst, const Imaging imSrc) {
+ cmsUInt32Number dstLCMSFormat;
+ cmsUInt32Number srcLCMSFormat;
+ int numSrcExtras;
+ int numDstExtras;
+ int numExtras;
+ int ySize;
+ int xSize;
+ int channelSize;
+ int srcChunkSize;
+ int dstChunkSize;
+ int e;
+
+ // trivially copied
+ if (imDst == imSrc) {
+ return;
+ }
+
+ dstLCMSFormat = cmsGetTransformOutputFormat(hTransform);
+ srcLCMSFormat = cmsGetTransformInputFormat(hTransform);
+
+ // currently, all Pillow formats are chunky formats, but check it anyway
+ if (T_PLANAR(dstLCMSFormat) || T_PLANAR(srcLCMSFormat)) {
+ return;
+ }
+
+ // copy only if channel format is identical, except OPTIMIZED is ignored as it
+ // does not affect the aux channel
+ if (T_FLOAT(dstLCMSFormat) != T_FLOAT(srcLCMSFormat) ||
+ T_FLAVOR(dstLCMSFormat) != T_FLAVOR(srcLCMSFormat) ||
+ T_ENDIAN16(dstLCMSFormat) != T_ENDIAN16(srcLCMSFormat) ||
+ T_BYTES(dstLCMSFormat) != T_BYTES(srcLCMSFormat)) {
+ return;
+ }
+
+ numSrcExtras = T_EXTRA(srcLCMSFormat);
+ numDstExtras = T_EXTRA(dstLCMSFormat);
+ numExtras = Cms_Min(numSrcExtras, numDstExtras);
+ ySize = Cms_Min(imSrc->ysize, imDst->ysize);
+ xSize = Cms_Min(imSrc->xsize, imDst->xsize);
+ channelSize = T_BYTES(dstLCMSFormat);
+ srcChunkSize = (T_CHANNELS(srcLCMSFormat) + T_EXTRA(srcLCMSFormat)) * channelSize;
+ dstChunkSize = (T_CHANNELS(dstLCMSFormat) + T_EXTRA(dstLCMSFormat)) * channelSize;
+
+ for (e = 0; e < numExtras; ++e) {
+ int y;
+ int dstChannel = pyCMSgetAuxChannelChannel(dstLCMSFormat, e);
+ int srcChannel = pyCMSgetAuxChannelChannel(srcLCMSFormat, e);
+
+ for (y = 0; y < ySize; y++) {
+ int x;
+ char *pDstExtras = imDst->image[y] + dstChannel * channelSize;
+ const char *pSrcExtras = imSrc->image[y] + srcChannel * channelSize;
+
+ for (x = 0; x < xSize; x++) {
+ memcpy(
+ pDstExtras + x * dstChunkSize,
+ pSrcExtras + x * srcChunkSize,
+ channelSize);
+ }
+ }
+ }
+}
+
+static int
+pyCMSdoTransform(Imaging im, Imaging imOut, cmsHTRANSFORM hTransform) {
+ int i;
+
+ if (im->xsize > imOut->xsize || im->ysize > imOut->ysize) {
+ return -1;
+ }
+
+ Py_BEGIN_ALLOW_THREADS
+
+ // transform color channels only
+ for (i = 0; i < im->ysize; i++) {
+ cmsDoTransform(hTransform, im->image[i], imOut->image[i], im->xsize);
+ }
+
+ // lcms by default does nothing to the auxiliary channels leaving those
+ // unchanged. To do "the right thing" here, i.e. maintain identical results
+ // with and without inPlace, we replicate those channels to the output.
+ //
+ // As of lcms 2.8, a new cmsFLAGS_COPY_ALPHA flag is introduced which would
+ // do the same thing automagically. Unfortunately, lcms2.8 is not yet widely
+ // enough available on all platforms, so we polyfill it here for now.
+ pyCMScopyAux(hTransform, imOut, im);
+
+ Py_END_ALLOW_THREADS
+
+ return 0;
+}
+
+static cmsHTRANSFORM
+_buildTransform(
+ cmsHPROFILE hInputProfile,
+ cmsHPROFILE hOutputProfile,
+ char *sInMode,
+ char *sOutMode,
+ int iRenderingIntent,
+ cmsUInt32Number cmsFLAGS) {
+ cmsHTRANSFORM hTransform;
+
+ Py_BEGIN_ALLOW_THREADS
+
+ /* create the transform */
+ hTransform = cmsCreateTransform(
+ hInputProfile,
+ findLCMStype(sInMode),
+ hOutputProfile,
+ findLCMStype(sOutMode),
+ iRenderingIntent,
+ cmsFLAGS);
+
+ Py_END_ALLOW_THREADS
+
+ if (!hTransform) {
+ PyErr_SetString(PyExc_ValueError, "cannot build transform");
+ }
+
+ return hTransform; /* if NULL, an exception is set */
+}
+
+static cmsHTRANSFORM
+_buildProofTransform(
+ cmsHPROFILE hInputProfile,
+ cmsHPROFILE hOutputProfile,
+ cmsHPROFILE hProofProfile,
+ char *sInMode,
+ char *sOutMode,
+ int iRenderingIntent,
+ int iProofIntent,
+ cmsUInt32Number cmsFLAGS) {
+ cmsHTRANSFORM hTransform;
+
+ Py_BEGIN_ALLOW_THREADS
+
+ /* create the transform */
+ hTransform = cmsCreateProofingTransform(
+ hInputProfile,
+ findLCMStype(sInMode),
+ hOutputProfile,
+ findLCMStype(sOutMode),
+ hProofProfile,
+ iRenderingIntent,
+ iProofIntent,
+ cmsFLAGS);
+
+ Py_END_ALLOW_THREADS
+
+ if (!hTransform) {
+ PyErr_SetString(PyExc_ValueError, "cannot build proof transform");
+ }
+
+ return hTransform; /* if NULL, an exception is set */
+}
+
+/* -------------------------------------------------------------------- */
+/* Python callable functions */
+
+static PyObject *
+buildTransform(PyObject *self, PyObject *args) {
+ CmsProfileObject *pInputProfile;
+ CmsProfileObject *pOutputProfile;
+ char *sInMode;
+ char *sOutMode;
+ int iRenderingIntent = 0;
+ int cmsFLAGS = 0;
+
+ cmsHTRANSFORM transform = NULL;
+
+ if (!PyArg_ParseTuple(
+ args,
+ "O!O!ss|ii:buildTransform",
+ &CmsProfile_Type,
+ &pInputProfile,
+ &CmsProfile_Type,
+ &pOutputProfile,
+ &sInMode,
+ &sOutMode,
+ &iRenderingIntent,
+ &cmsFLAGS)) {
+ return NULL;
+ }
+
+ transform = _buildTransform(
+ pInputProfile->profile,
+ pOutputProfile->profile,
+ sInMode,
+ sOutMode,
+ iRenderingIntent,
+ cmsFLAGS);
+
+ if (!transform) {
+ return NULL;
+ }
+
+ return cms_transform_new(transform, sInMode, sOutMode);
+}
+
+static PyObject *
+buildProofTransform(PyObject *self, PyObject *args) {
+ CmsProfileObject *pInputProfile;
+ CmsProfileObject *pOutputProfile;
+ CmsProfileObject *pProofProfile;
+ char *sInMode;
+ char *sOutMode;
+ int iRenderingIntent = 0;
+ int iProofIntent = 0;
+ int cmsFLAGS = 0;
+
+ cmsHTRANSFORM transform = NULL;
+
+ if (!PyArg_ParseTuple(
+ args,
+ "O!O!O!ss|iii:buildProofTransform",
+ &CmsProfile_Type,
+ &pInputProfile,
+ &CmsProfile_Type,
+ &pOutputProfile,
+ &CmsProfile_Type,
+ &pProofProfile,
+ &sInMode,
+ &sOutMode,
+ &iRenderingIntent,
+ &iProofIntent,
+ &cmsFLAGS)) {
+ return NULL;
+ }
+
+ transform = _buildProofTransform(
+ pInputProfile->profile,
+ pOutputProfile->profile,
+ pProofProfile->profile,
+ sInMode,
+ sOutMode,
+ iRenderingIntent,
+ iProofIntent,
+ cmsFLAGS);
+
+ if (!transform) {
+ return NULL;
+ }
+
+ return cms_transform_new(transform, sInMode, sOutMode);
+}
+
+static PyObject *
+cms_transform_apply(CmsTransformObject *self, PyObject *args) {
+ Py_ssize_t idIn;
+ Py_ssize_t idOut;
+ Imaging im;
+ Imaging imOut;
+
+ int result;
+
+ if (!PyArg_ParseTuple(args, "nn:apply", &idIn, &idOut)) {
+ return NULL;
+ }
+
+ im = (Imaging)idIn;
+ imOut = (Imaging)idOut;
+
+ result = pyCMSdoTransform(im, imOut, self->transform);
+
+ return Py_BuildValue("i", result);
+}
+
+/* -------------------------------------------------------------------- */
+/* Python-Callable On-The-Fly profile creation functions */
+
+static PyObject *
+createProfile(PyObject *self, PyObject *args) {
+ char *sColorSpace;
+ cmsHPROFILE hProfile;
+ cmsFloat64Number dColorTemp = 0.0;
+ cmsCIExyY whitePoint;
+ cmsBool result;
+
+ if (!PyArg_ParseTuple(args, "s|d:createProfile", &sColorSpace, &dColorTemp)) {
+ return NULL;
+ }
+
+ if (strcmp(sColorSpace, "LAB") == 0) {
+ if (dColorTemp > 0.0) {
+ result = cmsWhitePointFromTemp(&whitePoint, dColorTemp);
+ if (!result) {
+ PyErr_SetString(
+ PyExc_ValueError,
+ "ERROR: Could not calculate white point from color temperature "
+ "provided, must be float in degrees Kelvin");
+ return NULL;
+ }
+ hProfile = cmsCreateLab2Profile(&whitePoint);
+ } else {
+ hProfile = cmsCreateLab2Profile(NULL);
+ }
+ } else if (strcmp(sColorSpace, "XYZ") == 0) {
+ hProfile = cmsCreateXYZProfile();
+ } else if (strcmp(sColorSpace, "sRGB") == 0) {
+ hProfile = cmsCreate_sRGBProfile();
+ } else {
+ hProfile = NULL;
+ }
+
+ if (!hProfile) {
+ PyErr_SetString(PyExc_ValueError, "failed to create requested color space");
+ return NULL;
+ }
+
+ return cms_profile_new(hProfile);
+}
+
+/* -------------------------------------------------------------------- */
+/* profile methods */
+
+static PyObject *
+cms_profile_is_intent_supported(CmsProfileObject *self, PyObject *args) {
+ cmsBool result;
+
+ int intent;
+ int direction;
+ if (!PyArg_ParseTuple(args, "ii:is_intent_supported", &intent, &direction)) {
+ return NULL;
+ }
+
+ result = cmsIsIntentSupported(self->profile, intent, direction);
+
+ /* printf("cmsIsIntentSupported(%p, %d, %d) => %d\n", self->profile, intent,
+ * direction, result); */
+
+ return PyLong_FromLong(result != 0);
+}
+
+#ifdef _WIN32
+
+#ifdef _WIN64
+#define F_HANDLE "K"
+#else
+#define F_HANDLE "k"
+#endif
+
+static PyObject *
+cms_get_display_profile_win32(PyObject *self, PyObject *args) {
+ char filename[MAX_PATH];
+ cmsUInt32Number filename_size;
+ BOOL ok;
+
+ HANDLE handle = 0;
+ int is_dc = 0;
+ if (!PyArg_ParseTuple(
+ args, "|" F_HANDLE "i:get_display_profile", &handle, &is_dc)) {
+ return NULL;
+ }
+
+ filename_size = sizeof(filename);
+
+ if (is_dc) {
+ ok = GetICMProfile((HDC)handle, &filename_size, filename);
+ } else {
+ HDC dc = GetDC((HWND)handle);
+ ok = GetICMProfile(dc, &filename_size, filename);
+ ReleaseDC((HWND)handle, dc);
+ }
+
+ if (ok) {
+ return PyUnicode_FromStringAndSize(filename, filename_size - 1);
+ }
+
+ Py_INCREF(Py_None);
+ return Py_None;
+}
+#endif
+
+/* -------------------------------------------------------------------- */
+/* Helper functions. */
+
+static PyObject *
+_profile_read_mlu(CmsProfileObject *self, cmsTagSignature info) {
+ PyObject *uni;
+ char *lc = "en";
+ char *cc = cmsNoCountry;
+ cmsMLU *mlu;
+ cmsUInt32Number len;
+ wchar_t *buf;
+
+ if (!cmsIsTag(self->profile, info)) {
+ Py_INCREF(Py_None);
+ return Py_None;
+ }
+
+ mlu = cmsReadTag(self->profile, info);
+ if (!mlu) {
+ Py_INCREF(Py_None);
+ return Py_None;
+ }
+
+ len = cmsMLUgetWide(mlu, lc, cc, NULL, 0);
+ if (len == 0) {
+ Py_INCREF(Py_None);
+ return Py_None;
+ }
+
+ buf = malloc(len);
+ if (!buf) {
+ PyErr_SetString(PyExc_OSError, "Out of Memory");
+ return NULL;
+ }
+ /* Just in case the next call fails. */
+ buf[0] = '\0';
+
+ cmsMLUgetWide(mlu, lc, cc, buf, len);
+ // buf contains additional junk after \0
+ uni = PyUnicode_FromWideChar(buf, wcslen(buf));
+ free(buf);
+
+ return uni;
+}
+
+static PyObject *
+_profile_read_int_as_string(cmsUInt32Number nr) {
+ PyObject *ret;
+ char buf[5];
+ buf[0] = (char)((nr >> 24) & 0xff);
+ buf[1] = (char)((nr >> 16) & 0xff);
+ buf[2] = (char)((nr >> 8) & 0xff);
+ buf[3] = (char)(nr & 0xff);
+ buf[4] = 0;
+
+ ret = PyUnicode_DecodeASCII(buf, 4, NULL);
+ return ret;
+}
+
+static PyObject *
+_profile_read_signature(CmsProfileObject *self, cmsTagSignature info) {
+ unsigned int *sig;
+
+ if (!cmsIsTag(self->profile, info)) {
+ Py_INCREF(Py_None);
+ return Py_None;
+ }
+
+ sig = (unsigned int *)cmsReadTag(self->profile, info);
+ if (!sig) {
+ Py_INCREF(Py_None);
+ return Py_None;
+ }
+
+ return _profile_read_int_as_string(*sig);
+}
+
+static PyObject *
+_xyz_py(cmsCIEXYZ *XYZ) {
+ cmsCIExyY xyY;
+ cmsXYZ2xyY(&xyY, XYZ);
+ return Py_BuildValue(
+ "((d,d,d),(d,d,d))", XYZ->X, XYZ->Y, XYZ->Z, xyY.x, xyY.y, xyY.Y);
+}
+
+static PyObject *
+_xyz3_py(cmsCIEXYZ *XYZ) {
+ cmsCIExyY xyY[3];
+ cmsXYZ2xyY(&xyY[0], &XYZ[0]);
+ cmsXYZ2xyY(&xyY[1], &XYZ[1]);
+ cmsXYZ2xyY(&xyY[2], &XYZ[2]);
+
+ return Py_BuildValue(
+ "(((d,d,d),(d,d,d),(d,d,d)),((d,d,d),(d,d,d),(d,d,d)))",
+ XYZ[0].X,
+ XYZ[0].Y,
+ XYZ[0].Z,
+ XYZ[1].X,
+ XYZ[1].Y,
+ XYZ[1].Z,
+ XYZ[2].X,
+ XYZ[2].Y,
+ XYZ[2].Z,
+ xyY[0].x,
+ xyY[0].y,
+ xyY[0].Y,
+ xyY[1].x,
+ xyY[1].y,
+ xyY[1].Y,
+ xyY[2].x,
+ xyY[2].y,
+ xyY[2].Y);
+}
+
+static PyObject *
+_profile_read_ciexyz(CmsProfileObject *self, cmsTagSignature info, int multi) {
+ cmsCIEXYZ *XYZ;
+
+ if (!cmsIsTag(self->profile, info)) {
+ Py_INCREF(Py_None);
+ return Py_None;
+ }
+
+ XYZ = (cmsCIEXYZ *)cmsReadTag(self->profile, info);
+ if (!XYZ) {
+ Py_INCREF(Py_None);
+ return Py_None;
+ }
+ if (multi) {
+ return _xyz3_py(XYZ);
+ } else {
+ return _xyz_py(XYZ);
+ }
+}
+
+static PyObject *
+_profile_read_ciexyy_triple(CmsProfileObject *self, cmsTagSignature info) {
+ cmsCIExyYTRIPLE *triple;
+
+ if (!cmsIsTag(self->profile, info)) {
+ Py_INCREF(Py_None);
+ return Py_None;
+ }
+
+ triple = (cmsCIExyYTRIPLE *)cmsReadTag(self->profile, info);
+ if (!triple) {
+ Py_INCREF(Py_None);
+ return Py_None;
+ }
+
+ /* Note: lcms does all the heavy lifting and error checking (nr of
+ channels == 3). */
+ return Py_BuildValue(
+ "((d,d,d),(d,d,d),(d,d,d)),",
+ triple->Red.x,
+ triple->Red.y,
+ triple->Red.Y,
+ triple->Green.x,
+ triple->Green.y,
+ triple->Green.Y,
+ triple->Blue.x,
+ triple->Blue.y,
+ triple->Blue.Y);
+}
+
+static PyObject *
+_profile_read_named_color_list(CmsProfileObject *self, cmsTagSignature info) {
+ cmsNAMEDCOLORLIST *ncl;
+ int i, n;
+ char name[cmsMAX_PATH];
+ PyObject *result;
+
+ if (!cmsIsTag(self->profile, info)) {
+ Py_INCREF(Py_None);
+ return Py_None;
+ }
+
+ ncl = (cmsNAMEDCOLORLIST *)cmsReadTag(self->profile, info);
+ if (ncl == NULL) {
+ Py_INCREF(Py_None);
+ return Py_None;
+ }
+
+ n = cmsNamedColorCount(ncl);
+ result = PyList_New(n);
+ if (!result) {
+ Py_INCREF(Py_None);
+ return Py_None;
+ }
+
+ for (i = 0; i < n; i++) {
+ PyObject *str;
+ cmsNamedColorInfo(ncl, i, name, NULL, NULL, NULL, NULL);
+ str = PyUnicode_FromString(name);
+ if (str == NULL) {
+ Py_DECREF(result);
+ Py_INCREF(Py_None);
+ return Py_None;
+ }
+ PyList_SET_ITEM(result, i, str);
+ }
+
+ return result;
+}
+
+static cmsBool
+_calculate_rgb_primaries(CmsProfileObject *self, cmsCIEXYZTRIPLE *result) {
+ double input[3][3] = {{1, 0, 0}, {0, 1, 0}, {0, 0, 1}};
+ cmsHPROFILE hXYZ;
+ cmsHTRANSFORM hTransform;
+
+ /* https://littlecms2.blogspot.com/2009/07/less-is-more.html */
+
+ // double array of RGB values with max on each identity
+ hXYZ = cmsCreateXYZProfile();
+ if (hXYZ == NULL) {
+ return 0;
+ }
+
+ // transform from our profile to XYZ using doubles for highest precision
+ hTransform = cmsCreateTransform(
+ self->profile,
+ TYPE_RGB_DBL,
+ hXYZ,
+ TYPE_XYZ_DBL,
+ INTENT_RELATIVE_COLORIMETRIC,
+ cmsFLAGS_NOCACHE | cmsFLAGS_NOOPTIMIZE);
+ cmsCloseProfile(hXYZ);
+ if (hTransform == NULL) {
+ return 0;
+ }
+
+ cmsDoTransform(hTransform, (void *)input, result, 3);
+ cmsDeleteTransform(hTransform);
+ return 1;
+}
+
+static cmsBool
+_check_intent(
+ int clut,
+ cmsHPROFILE hProfile,
+ cmsUInt32Number Intent,
+ cmsUInt32Number UsedDirection) {
+ if (clut) {
+ return cmsIsCLUT(hProfile, Intent, UsedDirection);
+ } else {
+ return cmsIsIntentSupported(hProfile, Intent, UsedDirection);
+ }
+}
+
+#define INTENTS 200
+
+static PyObject *
+_is_intent_supported(CmsProfileObject *self, int clut) {
+ PyObject *result;
+ int n;
+ int i;
+ cmsUInt32Number intent_ids[INTENTS];
+ char *intent_descs[INTENTS];
+
+ result = PyDict_New();
+ if (result == NULL) {
+ Py_INCREF(Py_None);
+ return Py_None;
+ }
+
+ n = cmsGetSupportedIntents(INTENTS, intent_ids, intent_descs);
+ for (i = 0; i < n; i++) {
+ int intent = (int)intent_ids[i];
+ PyObject *id;
+ PyObject *entry;
+
+ /* Only valid for ICC Intents (otherwise we read invalid memory in lcms
+ * cmsio1.c). */
+ if (!(intent == INTENT_PERCEPTUAL || intent == INTENT_RELATIVE_COLORIMETRIC ||
+ intent == INTENT_SATURATION || intent == INTENT_ABSOLUTE_COLORIMETRIC)) {
+ continue;
+ }
+
+ id = PyLong_FromLong((long)intent);
+ entry = Py_BuildValue(
+ "(OOO)",
+ _check_intent(clut, self->profile, intent, LCMS_USED_AS_INPUT) ? Py_True
+ : Py_False,
+ _check_intent(clut, self->profile, intent, LCMS_USED_AS_OUTPUT) ? Py_True
+ : Py_False,
+ _check_intent(clut, self->profile, intent, LCMS_USED_AS_PROOF) ? Py_True
+ : Py_False);
+ if (id == NULL || entry == NULL) {
+ Py_XDECREF(id);
+ Py_XDECREF(entry);
+ Py_XDECREF(result);
+ Py_INCREF(Py_None);
+ return Py_None;
+ }
+ PyDict_SetItem(result, id, entry);
+ Py_DECREF(id);
+ Py_DECREF(entry);
+ }
+ return result;
+}
+
+/* -------------------------------------------------------------------- */
+/* Python interface setup */
+
+static PyMethodDef pyCMSdll_methods[] = {
+
+ {"profile_open", cms_profile_open, METH_VARARGS},
+ {"profile_frombytes", cms_profile_frombytes, METH_VARARGS},
+ {"profile_tobytes", cms_profile_tobytes, METH_VARARGS},
+
+ /* profile and transform functions */
+ {"buildTransform", buildTransform, METH_VARARGS},
+ {"buildProofTransform", buildProofTransform, METH_VARARGS},
+ {"createProfile", createProfile, METH_VARARGS},
+
+/* platform specific tools */
+#ifdef _WIN32
+ {"get_display_profile_win32", cms_get_display_profile_win32, METH_VARARGS},
+#endif
+
+ {NULL, NULL}};
+
+static struct PyMethodDef cms_profile_methods[] = {
+ {"is_intent_supported", (PyCFunction)cms_profile_is_intent_supported, METH_VARARGS},
+ {NULL, NULL} /* sentinel */
+};
+
+static PyObject *
+cms_profile_getattr_rendering_intent(CmsProfileObject *self, void *closure) {
+ return PyLong_FromLong(cmsGetHeaderRenderingIntent(self->profile));
+}
+
+/* New-style unicode interfaces. */
+static PyObject *
+cms_profile_getattr_copyright(CmsProfileObject *self, void *closure) {
+ return _profile_read_mlu(self, cmsSigCopyrightTag);
+}
+
+static PyObject *
+cms_profile_getattr_target(CmsProfileObject *self, void *closure) {
+ return _profile_read_mlu(self, cmsSigCharTargetTag);
+}
+
+static PyObject *
+cms_profile_getattr_manufacturer(CmsProfileObject *self, void *closure) {
+ return _profile_read_mlu(self, cmsSigDeviceMfgDescTag);
+}
+
+static PyObject *
+cms_profile_getattr_model(CmsProfileObject *self, void *closure) {
+ return _profile_read_mlu(self, cmsSigDeviceModelDescTag);
+}
+
+static PyObject *
+cms_profile_getattr_profile_description(CmsProfileObject *self, void *closure) {
+ return _profile_read_mlu(self, cmsSigProfileDescriptionTag);
+}
+
+static PyObject *
+cms_profile_getattr_screening_description(CmsProfileObject *self, void *closure) {
+ return _profile_read_mlu(self, cmsSigScreeningDescTag);
+}
+
+static PyObject *
+cms_profile_getattr_viewing_condition(CmsProfileObject *self, void *closure) {
+ return _profile_read_mlu(self, cmsSigViewingCondDescTag);
+}
+
+static PyObject *
+cms_profile_getattr_creation_date(CmsProfileObject *self, void *closure) {
+ cmsBool result;
+ struct tm ct;
+
+ result = cmsGetHeaderCreationDateTime(self->profile, &ct);
+ if (!result) {
+ Py_INCREF(Py_None);
+ return Py_None;
+ }
+
+ return PyDateTime_FromDateAndTime(
+ 1900 + ct.tm_year, ct.tm_mon, ct.tm_mday, ct.tm_hour, ct.tm_min, ct.tm_sec, 0);
+}
+
+static PyObject *
+cms_profile_getattr_version(CmsProfileObject *self, void *closure) {
+ cmsFloat64Number version = cmsGetProfileVersion(self->profile);
+ return PyFloat_FromDouble(version);
+}
+
+static PyObject *
+cms_profile_getattr_icc_version(CmsProfileObject *self, void *closure) {
+ return PyLong_FromLong((long)cmsGetEncodedICCversion(self->profile));
+}
+
+static PyObject *
+cms_profile_getattr_attributes(CmsProfileObject *self, void *closure) {
+ cmsUInt64Number attr;
+ cmsGetHeaderAttributes(self->profile, &attr);
+ /* This works just as well on Windows (LLP64), 32-bit Linux
+ (ILP32) and 64-bit Linux (LP64) systems. */
+ return PyLong_FromUnsignedLongLong((unsigned long long)attr);
+}
+
+static PyObject *
+cms_profile_getattr_header_flags(CmsProfileObject *self, void *closure) {
+ cmsUInt32Number flags = cmsGetHeaderFlags(self->profile);
+ return PyLong_FromLong(flags);
+}
+
+static PyObject *
+cms_profile_getattr_header_manufacturer(CmsProfileObject *self, void *closure) {
+ return _profile_read_int_as_string(cmsGetHeaderManufacturer(self->profile));
+}
+
+static PyObject *
+cms_profile_getattr_header_model(CmsProfileObject *self, void *closure) {
+ return _profile_read_int_as_string(cmsGetHeaderModel(self->profile));
+}
+
+static PyObject *
+cms_profile_getattr_device_class(CmsProfileObject *self, void *closure) {
+ return _profile_read_int_as_string(cmsGetDeviceClass(self->profile));
+}
+
+static PyObject *
+cms_profile_getattr_connection_space(CmsProfileObject *self, void *closure) {
+ return _profile_read_int_as_string(cmsGetPCS(self->profile));
+}
+
+static PyObject *
+cms_profile_getattr_xcolor_space(CmsProfileObject *self, void *closure) {
+ return _profile_read_int_as_string(cmsGetColorSpace(self->profile));
+}
+
+static PyObject *
+cms_profile_getattr_profile_id(CmsProfileObject *self, void *closure) {
+ cmsUInt8Number id[16];
+ cmsGetHeaderProfileID(self->profile, id);
+ return PyBytes_FromStringAndSize((char *)id, 16);
+}
+
+static PyObject *
+cms_profile_getattr_is_matrix_shaper(CmsProfileObject *self, void *closure) {
+ return PyBool_FromLong((long)cmsIsMatrixShaper(self->profile));
+}
+
+static PyObject *
+cms_profile_getattr_technology(CmsProfileObject *self, void *closure) {
+ return _profile_read_signature(self, cmsSigTechnologyTag);
+}
+
+static PyObject *
+cms_profile_getattr_colorimetric_intent(CmsProfileObject *self, void *closure) {
+ return _profile_read_signature(self, cmsSigColorimetricIntentImageStateTag);
+}
+
+static PyObject *
+cms_profile_getattr_perceptual_rendering_intent_gamut(
+ CmsProfileObject *self, void *closure) {
+ return _profile_read_signature(self, cmsSigPerceptualRenderingIntentGamutTag);
+}
+
+static PyObject *
+cms_profile_getattr_saturation_rendering_intent_gamut(
+ CmsProfileObject *self, void *closure) {
+ return _profile_read_signature(self, cmsSigSaturationRenderingIntentGamutTag);
+}
+
+static PyObject *
+cms_profile_getattr_red_colorant(CmsProfileObject *self, void *closure) {
+ if (!cmsIsMatrixShaper(self->profile)) {
+ Py_INCREF(Py_None);
+ return Py_None;
+ }
+ return _profile_read_ciexyz(self, cmsSigRedColorantTag, 0);
+}
+
+static PyObject *
+cms_profile_getattr_green_colorant(CmsProfileObject *self, void *closure) {
+ if (!cmsIsMatrixShaper(self->profile)) {
+ Py_INCREF(Py_None);
+ return Py_None;
+ }
+ return _profile_read_ciexyz(self, cmsSigGreenColorantTag, 0);
+}
+
+static PyObject *
+cms_profile_getattr_blue_colorant(CmsProfileObject *self, void *closure) {
+ if (!cmsIsMatrixShaper(self->profile)) {
+ Py_INCREF(Py_None);
+ return Py_None;
+ }
+ return _profile_read_ciexyz(self, cmsSigBlueColorantTag, 0);
+}
+
+static PyObject *
+cms_profile_getattr_media_white_point_temperature(
+ CmsProfileObject *self, void *closure) {
+ cmsCIEXYZ *XYZ;
+ cmsCIExyY xyY;
+ cmsFloat64Number tempK;
+ cmsTagSignature info = cmsSigMediaWhitePointTag;
+ cmsBool result;
+
+ if (!cmsIsTag(self->profile, info)) {
+ Py_INCREF(Py_None);
+ return Py_None;
+ }
+
+ XYZ = (cmsCIEXYZ *)cmsReadTag(self->profile, info);
+ if (XYZ == NULL || XYZ->X == 0) {
+ Py_INCREF(Py_None);
+ return Py_None;
+ }
+
+ cmsXYZ2xyY(&xyY, XYZ);
+ result = cmsTempFromWhitePoint(&tempK, &xyY);
+ if (!result) {
+ Py_INCREF(Py_None);
+ return Py_None;
+ }
+ return PyFloat_FromDouble(tempK);
+}
+
+static PyObject *
+cms_profile_getattr_media_white_point(CmsProfileObject *self, void *closure) {
+ return _profile_read_ciexyz(self, cmsSigMediaWhitePointTag, 0);
+}
+
+static PyObject *
+cms_profile_getattr_media_black_point(CmsProfileObject *self, void *closure) {
+ return _profile_read_ciexyz(self, cmsSigMediaBlackPointTag, 0);
+}
+
+static PyObject *
+cms_profile_getattr_luminance(CmsProfileObject *self, void *closure) {
+ return _profile_read_ciexyz(self, cmsSigLuminanceTag, 0);
+}
+
+static PyObject *
+cms_profile_getattr_chromatic_adaptation(CmsProfileObject *self, void *closure) {
+ return _profile_read_ciexyz(self, cmsSigChromaticAdaptationTag, 1);
+}
+
+static PyObject *
+cms_profile_getattr_chromaticity(CmsProfileObject *self, void *closure) {
+ return _profile_read_ciexyy_triple(self, cmsSigChromaticityTag);
+}
+
+static PyObject *
+cms_profile_getattr_red_primary(CmsProfileObject *self, void *closure) {
+ cmsBool result = 0;
+ cmsCIEXYZTRIPLE primaries;
+
+ if (cmsIsMatrixShaper(self->profile)) {
+ result = _calculate_rgb_primaries(self, &primaries);
+ }
+ if (!result) {
+ Py_INCREF(Py_None);
+ return Py_None;
+ }
+
+ return _xyz_py(&primaries.Red);
+}
+
+static PyObject *
+cms_profile_getattr_green_primary(CmsProfileObject *self, void *closure) {
+ cmsBool result = 0;
+ cmsCIEXYZTRIPLE primaries;
+
+ if (cmsIsMatrixShaper(self->profile)) {
+ result = _calculate_rgb_primaries(self, &primaries);
+ }
+ if (!result) {
+ Py_INCREF(Py_None);
+ return Py_None;
+ }
+
+ return _xyz_py(&primaries.Green);
+}
+
+static PyObject *
+cms_profile_getattr_blue_primary(CmsProfileObject *self, void *closure) {
+ cmsBool result = 0;
+ cmsCIEXYZTRIPLE primaries;
+
+ if (cmsIsMatrixShaper(self->profile)) {
+ result = _calculate_rgb_primaries(self, &primaries);
+ }
+ if (!result) {
+ Py_INCREF(Py_None);
+ return Py_None;
+ }
+
+ return _xyz_py(&primaries.Blue);
+}
+
+static PyObject *
+cms_profile_getattr_colorant_table(CmsProfileObject *self, void *closure) {
+ return _profile_read_named_color_list(self, cmsSigColorantTableTag);
+}
+
+static PyObject *
+cms_profile_getattr_colorant_table_out(CmsProfileObject *self, void *closure) {
+ return _profile_read_named_color_list(self, cmsSigColorantTableOutTag);
+}
+
+static PyObject *
+cms_profile_getattr_is_intent_supported(CmsProfileObject *self, void *closure) {
+ return _is_intent_supported(self, 0);
+}
+
+static PyObject *
+cms_profile_getattr_is_clut(CmsProfileObject *self, void *closure) {
+ return _is_intent_supported(self, 1);
+}
+
+static const char *
+_illu_map(int i) {
+ switch (i) {
+ case 0:
+ return "unknown";
+ case 1:
+ return "D50";
+ case 2:
+ return "D65";
+ case 3:
+ return "D93";
+ case 4:
+ return "F2";
+ case 5:
+ return "D55";
+ case 6:
+ return "A";
+ case 7:
+ return "E";
+ case 8:
+ return "F8";
+ default:
+ return NULL;
+ }
+}
+
+static PyObject *
+cms_profile_getattr_icc_measurement_condition(CmsProfileObject *self, void *closure) {
+ cmsICCMeasurementConditions *mc;
+ cmsTagSignature info = cmsSigMeasurementTag;
+ const char *geo;
+
+ if (!cmsIsTag(self->profile, info)) {
+ Py_INCREF(Py_None);
+ return Py_None;
+ }
+
+ mc = (cmsICCMeasurementConditions *)cmsReadTag(self->profile, info);
+ if (!mc) {
+ Py_INCREF(Py_None);
+ return Py_None;
+ }
+
+ if (mc->Geometry == 1) {
+ geo = "45/0, 0/45";
+ } else if (mc->Geometry == 2) {
+ geo = "0d, d/0";
+ } else {
+ geo = "unknown";
+ }
+
+ return Py_BuildValue(
+ "{s:i,s:(ddd),s:s,s:d,s:s}",
+ "observer",
+ mc->Observer,
+ "backing",
+ mc->Backing.X,
+ mc->Backing.Y,
+ mc->Backing.Z,
+ "geo",
+ geo,
+ "flare",
+ mc->Flare,
+ "illuminant_type",
+ _illu_map(mc->IlluminantType));
+}
+
+static PyObject *
+cms_profile_getattr_icc_viewing_condition(CmsProfileObject *self, void *closure) {
+ cmsICCViewingConditions *vc;
+ cmsTagSignature info = cmsSigViewingConditionsTag;
+
+ if (!cmsIsTag(self->profile, info)) {
+ Py_INCREF(Py_None);
+ return Py_None;
+ }
+
+ vc = (cmsICCViewingConditions *)cmsReadTag(self->profile, info);
+ if (!vc) {
+ Py_INCREF(Py_None);
+ return Py_None;
+ }
+
+ return Py_BuildValue(
+ "{s:(ddd),s:(ddd),s:s}",
+ "illuminant",
+ vc->IlluminantXYZ.X,
+ vc->IlluminantXYZ.Y,
+ vc->IlluminantXYZ.Z,
+ "surround",
+ vc->SurroundXYZ.X,
+ vc->SurroundXYZ.Y,
+ vc->SurroundXYZ.Z,
+ "illuminant_type",
+ _illu_map(vc->IlluminantType));
+}
+
+static struct PyGetSetDef cms_profile_getsetters[] = {
+ /* New style interfaces. */
+ {"rendering_intent", (getter)cms_profile_getattr_rendering_intent},
+ {"creation_date", (getter)cms_profile_getattr_creation_date},
+ {"copyright", (getter)cms_profile_getattr_copyright},
+ {"target", (getter)cms_profile_getattr_target},
+ {"manufacturer", (getter)cms_profile_getattr_manufacturer},
+ {"model", (getter)cms_profile_getattr_model},
+ {"profile_description", (getter)cms_profile_getattr_profile_description},
+ {"screening_description", (getter)cms_profile_getattr_screening_description},
+ {"viewing_condition", (getter)cms_profile_getattr_viewing_condition},
+ {"version", (getter)cms_profile_getattr_version},
+ {"icc_version", (getter)cms_profile_getattr_icc_version},
+ {"attributes", (getter)cms_profile_getattr_attributes},
+ {"header_flags", (getter)cms_profile_getattr_header_flags},
+ {"header_manufacturer", (getter)cms_profile_getattr_header_manufacturer},
+ {"header_model", (getter)cms_profile_getattr_header_model},
+ {"device_class", (getter)cms_profile_getattr_device_class},
+ {"connection_space", (getter)cms_profile_getattr_connection_space},
+ {"xcolor_space", (getter)cms_profile_getattr_xcolor_space},
+ {"profile_id", (getter)cms_profile_getattr_profile_id},
+ {"is_matrix_shaper", (getter)cms_profile_getattr_is_matrix_shaper},
+ {"technology", (getter)cms_profile_getattr_technology},
+ {"colorimetric_intent", (getter)cms_profile_getattr_colorimetric_intent},
+ {"perceptual_rendering_intent_gamut",
+ (getter)cms_profile_getattr_perceptual_rendering_intent_gamut},
+ {"saturation_rendering_intent_gamut",
+ (getter)cms_profile_getattr_saturation_rendering_intent_gamut},
+ {"red_colorant", (getter)cms_profile_getattr_red_colorant},
+ {"green_colorant", (getter)cms_profile_getattr_green_colorant},
+ {"blue_colorant", (getter)cms_profile_getattr_blue_colorant},
+ {"red_primary", (getter)cms_profile_getattr_red_primary},
+ {"green_primary", (getter)cms_profile_getattr_green_primary},
+ {"blue_primary", (getter)cms_profile_getattr_blue_primary},
+ {"media_white_point_temperature",
+ (getter)cms_profile_getattr_media_white_point_temperature},
+ {"media_white_point", (getter)cms_profile_getattr_media_white_point},
+ {"media_black_point", (getter)cms_profile_getattr_media_black_point},
+ {"luminance", (getter)cms_profile_getattr_luminance},
+ {"chromatic_adaptation", (getter)cms_profile_getattr_chromatic_adaptation},
+ {"chromaticity", (getter)cms_profile_getattr_chromaticity},
+ {"colorant_table", (getter)cms_profile_getattr_colorant_table},
+ {"colorant_table_out", (getter)cms_profile_getattr_colorant_table_out},
+ {"intent_supported", (getter)cms_profile_getattr_is_intent_supported},
+ {"clut", (getter)cms_profile_getattr_is_clut},
+ {"icc_measurement_condition",
+ (getter)cms_profile_getattr_icc_measurement_condition},
+ {"icc_viewing_condition", (getter)cms_profile_getattr_icc_viewing_condition},
+
+ {NULL}};
+
+static PyTypeObject CmsProfile_Type = {
+ PyVarObject_HEAD_INIT(NULL, 0) "PIL._imagingcms.CmsProfile", /*tp_name*/
+ sizeof(CmsProfileObject), /*tp_basicsize*/
+ 0, /*tp_itemsize*/
+ /* methods */
+ (destructor)cms_profile_dealloc, /*tp_dealloc*/
+ 0, /*tp_vectorcall_offset*/
+ 0, /*tp_getattr*/
+ 0, /*tp_setattr*/
+ 0, /*tp_as_async*/
+ 0, /*tp_repr*/
+ 0, /*tp_as_number*/
+ 0, /*tp_as_sequence*/
+ 0, /*tp_as_mapping*/
+ 0, /*tp_hash*/
+ 0, /*tp_call*/
+ 0, /*tp_str*/
+ 0, /*tp_getattro*/
+ 0, /*tp_setattro*/
+ 0, /*tp_as_buffer*/
+ Py_TPFLAGS_DEFAULT, /*tp_flags*/
+ 0, /*tp_doc*/
+ 0, /*tp_traverse*/
+ 0, /*tp_clear*/
+ 0, /*tp_richcompare*/
+ 0, /*tp_weaklistoffset*/
+ 0, /*tp_iter*/
+ 0, /*tp_iternext*/
+ cms_profile_methods, /*tp_methods*/
+ 0, /*tp_members*/
+ cms_profile_getsetters, /*tp_getset*/
+};
+
+static struct PyMethodDef cms_transform_methods[] = {
+ {"apply", (PyCFunction)cms_transform_apply, 1}, {NULL, NULL} /* sentinel */
+};
+
+static PyObject *
+cms_transform_getattr_inputMode(CmsTransformObject *self, void *closure) {
+ return PyUnicode_FromString(self->mode_in);
+}
+
+static PyObject *
+cms_transform_getattr_outputMode(CmsTransformObject *self, void *closure) {
+ return PyUnicode_FromString(self->mode_out);
+}
+
+static struct PyGetSetDef cms_transform_getsetters[] = {
+ {"inputMode", (getter)cms_transform_getattr_inputMode},
+ {"outputMode", (getter)cms_transform_getattr_outputMode},
+ {NULL}};
+
+static PyTypeObject CmsTransform_Type = {
+ PyVarObject_HEAD_INIT(NULL, 0) "CmsTransform", /*tp_name*/
+ sizeof(CmsTransformObject), /*tp_basicsize*/
+ 0, /*tp_itemsize*/
+ /* methods */
+ (destructor)cms_transform_dealloc, /*tp_dealloc*/
+ 0, /*tp_vectorcall_offset*/
+ 0, /*tp_getattr*/
+ 0, /*tp_setattr*/
+ 0, /*tp_as_async*/
+ 0, /*tp_repr*/
+ 0, /*tp_as_number*/
+ 0, /*tp_as_sequence*/
+ 0, /*tp_as_mapping*/
+ 0, /*tp_hash*/
+ 0, /*tp_call*/
+ 0, /*tp_str*/
+ 0, /*tp_getattro*/
+ 0, /*tp_setattro*/
+ 0, /*tp_as_buffer*/
+ Py_TPFLAGS_DEFAULT, /*tp_flags*/
+ 0, /*tp_doc*/
+ 0, /*tp_traverse*/
+ 0, /*tp_clear*/
+ 0, /*tp_richcompare*/
+ 0, /*tp_weaklistoffset*/
+ 0, /*tp_iter*/
+ 0, /*tp_iternext*/
+ cms_transform_methods, /*tp_methods*/
+ 0, /*tp_members*/
+ cms_transform_getsetters, /*tp_getset*/
+};
+
+static int
+setup_module(PyObject *m) {
+ PyObject *d;
+ PyObject *v;
+ int vn;
+
+ CmsProfile_Type.tp_new = PyType_GenericNew;
+
+ /* Ready object types */
+ PyType_Ready(&CmsProfile_Type);
+ PyType_Ready(&CmsTransform_Type);
+
+ Py_INCREF(&CmsProfile_Type);
+ PyModule_AddObject(m, "CmsProfile", (PyObject *)&CmsProfile_Type);
+
+ d = PyModule_GetDict(m);
+
+ /* this check is also in PIL.features.pilinfo() */
+#if LCMS_VERSION < 2070
+ vn = LCMS_VERSION;
+#else
+ vn = cmsGetEncodedCMMversion();
+#endif
+ if (vn % 10) {
+ v = PyUnicode_FromFormat("%d.%d.%d", vn / 1000, (vn / 10) % 100, vn % 10);
+ } else {
+ v = PyUnicode_FromFormat("%d.%d", vn / 1000, (vn / 10) % 100);
+ }
+ PyDict_SetItemString(d, "littlecms_version", v ? v : Py_None);
+ Py_XDECREF(v);
+
+ return 0;
+}
+
+PyMODINIT_FUNC
+PyInit__imagingcms(void) {
+ PyObject *m;
+
+ static PyModuleDef module_def = {
+ PyModuleDef_HEAD_INIT,
+ "_imagingcms", /* m_name */
+ NULL, /* m_doc */
+ -1, /* m_size */
+ pyCMSdll_methods, /* m_methods */
+ };
+
+ m = PyModule_Create(&module_def);
+
+ if (setup_module(m) < 0) {
+ return NULL;
+ }
+
+ PyDateTime_IMPORT;
+
+ return m;
+}
diff --git a/contrib/python/Pillow/py3/_imagingft.c b/contrib/python/Pillow/py3/_imagingft.c
new file mode 100644
index 00000000000..069e7cd1478
--- /dev/null
+++ b/contrib/python/Pillow/py3/_imagingft.c
@@ -0,0 +1,1522 @@
+/*
+ * PIL FreeType Driver
+ *
+ * a FreeType 2.X driver for PIL
+ *
+ * history:
+ * 2001-02-17 fl Created (based on old experimental freetype 1.0 code)
+ * 2001-04-18 fl Fixed some egcs compiler nits
+ * 2002-11-08 fl Added unicode support; more font metrics, etc
+ * 2003-05-20 fl Fixed compilation under 1.5.2 and newer non-unicode builds
+ * 2003-09-27 fl Added charmap encoding support
+ * 2004-05-15 fl Fixed compilation for FreeType 2.1.8
+ * 2004-09-10 fl Added support for monochrome bitmaps
+ * 2006-06-18 fl Fixed glyph bearing calculation
+ * 2007-12-23 fl Fixed crash in family/style attribute fetch
+ * 2008-01-02 fl Handle Unicode filenames properly
+ *
+ * Copyright (c) 1998-2007 by Secret Labs AB
+ */
+
+#define PY_SSIZE_T_CLEAN
+#include "Python.h"
+#include "libImaging/Imaging.h"
+
+#include <ft2build.h>
+#include FT_FREETYPE_H
+#include FT_GLYPH_H
+#include FT_BITMAP_H
+#include FT_STROKER_H
+#include FT_MULTIPLE_MASTERS_H
+#include FT_SFNT_NAMES_H
+#ifdef FT_COLOR_H
+#include FT_COLOR_H
+#endif
+
+/* -------------------------------------------------------------------- */
+/* error table */
+
+#undef FTERRORS_H
+#undef __FTERRORS_H__
+
+#define FT_ERRORDEF(e, v, s) {e, s},
+#define FT_ERROR_START_LIST {
+#define FT_ERROR_END_LIST \
+ { 0, 0 } \
+ } \
+ ;
+
+#ifdef HAVE_RAQM
+# ifdef HAVE_RAQM_SYSTEM
+# error #include <raqm.h>
+# else
+# error #include "thirdparty/raqm/raqm.h"
+# ifdef HAVE_FRIBIDI_SYSTEM
+# error #include <fribidi.h>
+# else
+# error #include "thirdparty/fribidi-shim/fribidi.h"
+# error #include <hb.h>
+# endif
+# endif
+#endif
+
+static int have_raqm = 0;
+
+#define LAYOUT_FALLBACK 0
+#define LAYOUT_RAQM 1
+
+typedef struct {
+ int index, x_offset, x_advance, y_offset, y_advance;
+ unsigned int cluster;
+} GlyphInfo;
+
+struct {
+ int code;
+ const char *message;
+} ft_errors[] =
+
+#include FT_ERRORS_H
+
+ /* -------------------------------------------------------------------- */
+ /* font objects */
+
+ static FT_Library library;
+
+typedef struct {
+ PyObject_HEAD FT_Face face;
+ unsigned char *font_bytes;
+ int layout_engine;
+} FontObject;
+
+static PyTypeObject Font_Type;
+
+/* round a 26.6 pixel coordinate to the nearest integer */
+#define PIXEL(x) ((((x) + 32) & -64) >> 6)
+
+static PyObject *
+geterror(int code) {
+ int i;
+
+ for (i = 0; ft_errors[i].message; i++) {
+ if (ft_errors[i].code == code) {
+ PyErr_SetString(PyExc_OSError, ft_errors[i].message);
+ return NULL;
+ }
+ }
+
+ PyErr_SetString(PyExc_OSError, "unknown freetype error");
+ return NULL;
+}
+
+static PyObject *
+getfont(PyObject *self_, PyObject *args, PyObject *kw) {
+ /* create a font object from a file name and a size (in pixels) */
+
+ FontObject *self;
+ int error = 0;
+
+ char *filename = NULL;
+ float size;
+ FT_Size_RequestRec req;
+ FT_Long width;
+ Py_ssize_t index = 0;
+ Py_ssize_t layout_engine = 0;
+ unsigned char *encoding;
+ unsigned char *font_bytes;
+ Py_ssize_t font_bytes_size = 0;
+ static char *kwlist[] = {
+ "filename", "size", "index", "encoding", "font_bytes", "layout_engine", NULL};
+
+ if (!library) {
+ PyErr_SetString(PyExc_OSError, "failed to initialize FreeType library");
+ return NULL;
+ }
+
+#if PY_MAJOR_VERSION > 3 || PY_MINOR_VERSION > 11
+ PyConfig config;
+ PyConfig_InitPythonConfig(&config);
+ if (!PyArg_ParseTupleAndKeywords(
+ args,
+ kw,
+ "etf|nsy#n",
+ kwlist,
+ config.filesystem_encoding,
+ &filename,
+ &size,
+ &index,
+ &encoding,
+ &font_bytes,
+ &font_bytes_size,
+ &layout_engine)) {
+ PyConfig_Clear(&config);
+ return NULL;
+ }
+ PyConfig_Clear(&config);
+#else
+ if (!PyArg_ParseTupleAndKeywords(
+ args,
+ kw,
+ "etf|nsy#n",
+ kwlist,
+ Py_FileSystemDefaultEncoding,
+ &filename,
+ &size,
+ &index,
+ &encoding,
+ &font_bytes,
+ &font_bytes_size,
+ &layout_engine)) {
+ return NULL;
+ }
+#endif
+
+ self = PyObject_New(FontObject, &Font_Type);
+ if (!self) {
+ if (filename) {
+ PyMem_Free(filename);
+ }
+ return NULL;
+ }
+
+ self->face = NULL;
+ self->layout_engine = layout_engine;
+
+ if (filename && font_bytes_size <= 0) {
+ self->font_bytes = NULL;
+ error = FT_New_Face(library, filename, index, &self->face);
+ } else {
+ /* need to have allocated storage for font_bytes for the life of the object.*/
+ /* Don't free this before FT_Done_Face */
+ self->font_bytes = PyMem_Malloc(font_bytes_size);
+ if (!self->font_bytes) {
+ error = FT_Err_Out_Of_Memory;
+ }
+ if (!error) {
+ memcpy(self->font_bytes, font_bytes, (size_t)font_bytes_size);
+ error = FT_New_Memory_Face(
+ library,
+ (FT_Byte *)self->font_bytes,
+ font_bytes_size,
+ index,
+ &self->face);
+ }
+ }
+
+ if (!error) {
+ width = size * 64;
+ req.type = FT_SIZE_REQUEST_TYPE_NOMINAL;
+ req.width = width;
+ req.height = width;
+ req.horiResolution = 0;
+ req.vertResolution = 0;
+ error = FT_Request_Size(self->face, &req);
+ }
+
+ if (!error && encoding && strlen((char *)encoding) == 4) {
+ FT_Encoding encoding_tag =
+ FT_MAKE_TAG(encoding[0], encoding[1], encoding[2], encoding[3]);
+ error = FT_Select_Charmap(self->face, encoding_tag);
+ }
+ if (filename) {
+ PyMem_Free(filename);
+ }
+
+ if (error) {
+ if (self->font_bytes) {
+ PyMem_Free(self->font_bytes);
+ self->font_bytes = NULL;
+ }
+ Py_DECREF(self);
+ return geterror(error);
+ }
+
+ return (PyObject *)self;
+}
+
+static int
+font_getchar(PyObject *string, int index, FT_ULong *char_out) {
+ if (PyUnicode_Check(string)) {
+ if (index >= PyUnicode_GET_LENGTH(string)) {
+ return 0;
+ }
+ *char_out = PyUnicode_READ_CHAR(string, index);
+ return 1;
+ }
+ return 0;
+}
+
+#ifdef HAVE_RAQM
+
+static size_t
+text_layout_raqm(
+ PyObject *string,
+ FontObject *self,
+ const char *dir,
+ PyObject *features,
+ const char *lang,
+ GlyphInfo **glyph_info) {
+ size_t i = 0, count = 0, start = 0;
+ raqm_t *rq;
+ raqm_glyph_t *glyphs = NULL;
+ raqm_direction_t direction;
+
+ rq = raqm_create();
+ if (rq == NULL) {
+ PyErr_SetString(PyExc_ValueError, "raqm_create() failed.");
+ goto failed;
+ }
+
+ if (PyUnicode_Check(string)) {
+ Py_UCS4 *text = PyUnicode_AsUCS4Copy(string);
+ Py_ssize_t size = PyUnicode_GET_LENGTH(string);
+ if (!text || !size) {
+ /* return 0 and clean up, no glyphs==no size,
+ and raqm fails with empty strings */
+ goto failed;
+ }
+ int set_text = raqm_set_text(rq, text, size);
+ PyMem_Free(text);
+ if (!set_text) {
+ PyErr_SetString(PyExc_ValueError, "raqm_set_text() failed");
+ goto failed;
+ }
+ if (lang) {
+ if (!raqm_set_language(rq, lang, start, size)) {
+ PyErr_SetString(PyExc_ValueError, "raqm_set_language() failed");
+ goto failed;
+ }
+ }
+ } else {
+ PyErr_SetString(PyExc_TypeError, "expected string");
+ goto failed;
+ }
+
+ direction = RAQM_DIRECTION_DEFAULT;
+ if (dir) {
+ if (strcmp(dir, "rtl") == 0) {
+ direction = RAQM_DIRECTION_RTL;
+ } else if (strcmp(dir, "ltr") == 0) {
+ direction = RAQM_DIRECTION_LTR;
+ } else if (strcmp(dir, "ttb") == 0) {
+ direction = RAQM_DIRECTION_TTB;
+#if !defined(RAQM_VERSION_ATLEAST)
+ /* RAQM_VERSION_ATLEAST was added in Raqm 0.7.0 */
+ PyErr_SetString(
+ PyExc_ValueError,
+ "libraqm 0.7 or greater required for 'ttb' direction");
+ goto failed;
+#endif
+ } else {
+ PyErr_SetString(
+ PyExc_ValueError, "direction must be either 'rtl', 'ltr' or 'ttb'");
+ goto failed;
+ }
+ }
+
+ if (!raqm_set_par_direction(rq, direction)) {
+ PyErr_SetString(PyExc_ValueError, "raqm_set_par_direction() failed");
+ goto failed;
+ }
+
+ if (features != Py_None) {
+ int j, len;
+ PyObject *seq = PySequence_Fast(features, "expected a sequence");
+ if (!seq) {
+ goto failed;
+ }
+
+ len = PySequence_Fast_GET_SIZE(seq);
+ for (j = 0; j < len; j++) {
+ PyObject *item = PySequence_Fast_GET_ITEM(seq, j);
+ char *feature = NULL;
+ Py_ssize_t size = 0;
+ PyObject *bytes;
+
+ if (!PyUnicode_Check(item)) {
+ Py_DECREF(seq);
+ PyErr_SetString(PyExc_TypeError, "expected a string");
+ goto failed;
+ }
+ bytes = PyUnicode_AsUTF8String(item);
+ if (bytes == NULL) {
+ Py_DECREF(seq);
+ goto failed;
+ }
+ feature = PyBytes_AS_STRING(bytes);
+ size = PyBytes_GET_SIZE(bytes);
+ if (!raqm_add_font_feature(rq, feature, size)) {
+ Py_DECREF(seq);
+ Py_DECREF(bytes);
+ PyErr_SetString(PyExc_ValueError, "raqm_add_font_feature() failed");
+ goto failed;
+ }
+ Py_DECREF(bytes);
+ }
+ Py_DECREF(seq);
+ }
+
+ if (!raqm_set_freetype_face(rq, self->face)) {
+ PyErr_SetString(PyExc_RuntimeError, "raqm_set_freetype_face() failed.");
+ goto failed;
+ }
+
+ if (!raqm_layout(rq)) {
+ PyErr_SetString(PyExc_RuntimeError, "raqm_layout() failed.");
+ goto failed;
+ }
+
+ glyphs = raqm_get_glyphs(rq, &count);
+ if (glyphs == NULL) {
+ PyErr_SetString(PyExc_ValueError, "raqm_get_glyphs() failed.");
+ count = 0;
+ goto failed;
+ }
+
+ (*glyph_info) = PyMem_New(GlyphInfo, count);
+ if ((*glyph_info) == NULL) {
+ PyErr_SetString(PyExc_MemoryError, "PyMem_New() failed");
+ count = 0;
+ goto failed;
+ }
+
+ for (i = 0; i < count; i++) {
+ (*glyph_info)[i].index = glyphs[i].index;
+ (*glyph_info)[i].x_offset = glyphs[i].x_offset;
+ (*glyph_info)[i].x_advance = glyphs[i].x_advance;
+ (*glyph_info)[i].y_offset = glyphs[i].y_offset;
+ (*glyph_info)[i].y_advance = glyphs[i].y_advance;
+ (*glyph_info)[i].cluster = glyphs[i].cluster;
+ }
+
+failed:
+ raqm_destroy(rq);
+ return count;
+}
+
+#endif
+
+static size_t
+text_layout_fallback(
+ PyObject *string,
+ FontObject *self,
+ const char *dir,
+ PyObject *features,
+ const char *lang,
+ GlyphInfo **glyph_info,
+ int mask,
+ int color) {
+ int error, load_flags;
+ FT_ULong ch;
+ Py_ssize_t count;
+ FT_GlyphSlot glyph;
+ FT_Bool kerning = FT_HAS_KERNING(self->face);
+ FT_UInt last_index = 0;
+ int i;
+
+ if (features != Py_None || dir != NULL || lang != NULL) {
+ PyErr_SetString(
+ PyExc_KeyError,
+ "setting text direction, language or font features is not supported "
+ "without libraqm");
+ }
+ if (!PyUnicode_Check(string)) {
+ PyErr_SetString(PyExc_TypeError, "expected string");
+ return 0;
+ }
+
+ count = 0;
+ while (font_getchar(string, count, &ch)) {
+ count++;
+ }
+ if (count == 0) {
+ return 0;
+ }
+
+ (*glyph_info) = PyMem_New(GlyphInfo, count);
+ if ((*glyph_info) == NULL) {
+ PyErr_SetString(PyExc_MemoryError, "PyMem_New() failed");
+ return 0;
+ }
+
+ load_flags = FT_LOAD_DEFAULT;
+ if (mask) {
+ load_flags |= FT_LOAD_TARGET_MONO;
+ }
+ if (color) {
+ load_flags |= FT_LOAD_COLOR;
+ }
+ for (i = 0; font_getchar(string, i, &ch); i++) {
+ (*glyph_info)[i].index = FT_Get_Char_Index(self->face, ch);
+ error = FT_Load_Glyph(self->face, (*glyph_info)[i].index, load_flags);
+ if (error) {
+ geterror(error);
+ return 0;
+ }
+ glyph = self->face->glyph;
+ (*glyph_info)[i].x_offset = 0;
+ (*glyph_info)[i].y_offset = 0;
+ if (kerning && last_index && (*glyph_info)[i].index) {
+ FT_Vector delta;
+ if (FT_Get_Kerning(
+ self->face,
+ last_index,
+ (*glyph_info)[i].index,
+ ft_kerning_default,
+ &delta) == 0) {
+ (*glyph_info)[i - 1].x_advance += PIXEL(delta.x);
+ (*glyph_info)[i - 1].y_advance += PIXEL(delta.y);
+ }
+ }
+
+ (*glyph_info)[i].x_advance = glyph->metrics.horiAdvance;
+ // y_advance is only used in ttb, which is not supported by basic layout
+ (*glyph_info)[i].y_advance = 0;
+ last_index = (*glyph_info)[i].index;
+ (*glyph_info)[i].cluster = ch;
+ }
+ return count;
+}
+
+static size_t
+text_layout(
+ PyObject *string,
+ FontObject *self,
+ const char *dir,
+ PyObject *features,
+ const char *lang,
+ GlyphInfo **glyph_info,
+ int mask,
+ int color) {
+ size_t count;
+#ifdef HAVE_RAQM
+ if (have_raqm && self->layout_engine == LAYOUT_RAQM) {
+ count = text_layout_raqm(
+ string, self, dir, features, lang, glyph_info);
+ } else
+#endif
+ {
+ count = text_layout_fallback(
+ string, self, dir, features, lang, glyph_info, mask, color);
+ }
+ return count;
+}
+
+static PyObject *
+font_getlength(FontObject *self, PyObject *args) {
+ int length; /* length along primary axis, in 26.6 precision */
+ GlyphInfo *glyph_info = NULL; /* computed text layout */
+ size_t i, count; /* glyph_info index and length */
+ int horizontal_dir; /* is primary axis horizontal? */
+ int mask = 0; /* is FT_LOAD_TARGET_MONO enabled? */
+ int color = 0; /* is FT_LOAD_COLOR enabled? */
+ const char *mode = NULL;
+ const char *dir = NULL;
+ const char *lang = NULL;
+ PyObject *features = Py_None;
+ PyObject *string;
+
+ /* calculate size and bearing for a given string */
+
+ if (!PyArg_ParseTuple(
+ args, "O|zzOz:getlength", &string, &mode, &dir, &features, &lang)) {
+ return NULL;
+ }
+
+ horizontal_dir = dir && strcmp(dir, "ttb") == 0 ? 0 : 1;
+
+ mask = mode && strcmp(mode, "1") == 0;
+ color = mode && strcmp(mode, "RGBA") == 0;
+
+ count = text_layout(string, self, dir, features, lang, &glyph_info, mask, color);
+ if (PyErr_Occurred()) {
+ return NULL;
+ }
+
+ length = 0;
+ for (i = 0; i < count; i++) {
+ if (horizontal_dir) {
+ length += glyph_info[i].x_advance;
+ } else {
+ length -= glyph_info[i].y_advance;
+ }
+ }
+
+ if (glyph_info) {
+ PyMem_Free(glyph_info);
+ glyph_info = NULL;
+ }
+
+ return PyLong_FromLong(length);
+}
+
+static int
+bounding_box_and_anchors(FT_Face face, const char *anchor, int horizontal_dir, GlyphInfo *glyph_info, size_t count, int load_flags, int *width, int *height, int *x_offset, int *y_offset) {
+ int position; /* pen position along primary axis, in 26.6 precision */
+ int advanced; /* pen position along primary axis, in pixels */
+ int px, py; /* position of current glyph, in pixels */
+ int x_min, x_max, y_min, y_max; /* text bounding box, in pixels */
+ int x_anchor, y_anchor; /* offset of point drawn at (0, 0), in pixels */
+ int error;
+ FT_Glyph glyph;
+ FT_BBox bbox; /* glyph bounding box */
+ size_t i; /* glyph_info index */
+ /*
+ * text bounds are given by:
+ * - bounding boxes of individual glyphs
+ * - pen line, i.e. 0 to `advanced` along primary axis
+ * this means point (0, 0) is part of the text bounding box
+ */
+ position = x_min = x_max = y_min = y_max = 0;
+ for (i = 0; i < count; i++) {
+ if (horizontal_dir) {
+ px = PIXEL(position + glyph_info[i].x_offset);
+ py = PIXEL(glyph_info[i].y_offset);
+
+ position += glyph_info[i].x_advance;
+ advanced = PIXEL(position);
+ if (advanced > x_max) {
+ x_max = advanced;
+ }
+ } else {
+ px = PIXEL(glyph_info[i].x_offset);
+ py = PIXEL(position + glyph_info[i].y_offset);
+
+ position += glyph_info[i].y_advance;
+ advanced = PIXEL(position);
+ if (advanced < y_min) {
+ y_min = advanced;
+ }
+ }
+
+ error = FT_Load_Glyph(face, glyph_info[i].index, load_flags);
+ if (error) {
+ geterror(error);
+ return 1;
+ }
+
+ error = FT_Get_Glyph(face->glyph, &glyph);
+ if (error) {
+ geterror(error);
+ return 1;
+ }
+
+ FT_Glyph_Get_CBox(glyph, FT_GLYPH_BBOX_PIXELS, &bbox);
+ bbox.xMax += px;
+ if (bbox.xMax > x_max) {
+ x_max = bbox.xMax;
+ }
+ bbox.xMin += px;
+ if (bbox.xMin < x_min) {
+ x_min = bbox.xMin;
+ }
+ bbox.yMax += py;
+ if (bbox.yMax > y_max) {
+ y_max = bbox.yMax;
+ }
+ bbox.yMin += py;
+ if (bbox.yMin < y_min) {
+ y_min = bbox.yMin;
+ }
+
+ FT_Done_Glyph(glyph);
+ }
+
+ if (anchor == NULL) {
+ anchor = horizontal_dir ? "la" : "lt";
+ }
+ if (strlen(anchor) != 2) {
+ goto bad_anchor;
+ }
+
+ x_anchor = y_anchor = 0;
+ if (count) {
+ if (horizontal_dir) {
+ switch (anchor[0]) {
+ case 'l': // left
+ x_anchor = 0;
+ break;
+ case 'm': // middle (left + right) / 2
+ x_anchor = PIXEL(position / 2);
+ break;
+ case 'r': // right
+ x_anchor = PIXEL(position);
+ break;
+ case 's': // vertical baseline
+ default:
+ goto bad_anchor;
+ }
+ switch (anchor[1]) {
+ case 'a': // ascender
+ y_anchor = PIXEL(face->size->metrics.ascender);
+ break;
+ case 't': // top
+ y_anchor = y_max;
+ break;
+ case 'm': // middle (ascender + descender) / 2
+ y_anchor = PIXEL(
+ (face->size->metrics.ascender +
+ face->size->metrics.descender) /
+ 2);
+ break;
+ case 's': // horizontal baseline
+ y_anchor = 0;
+ break;
+ case 'b': // bottom
+ y_anchor = y_min;
+ break;
+ case 'd': // descender
+ y_anchor = PIXEL(face->size->metrics.descender);
+ break;
+ default:
+ goto bad_anchor;
+ }
+ } else {
+ switch (anchor[0]) {
+ case 'l': // left
+ x_anchor = x_min;
+ break;
+ case 'm': // middle (left + right) / 2
+ x_anchor = (x_min + x_max) / 2;
+ break;
+ case 'r': // right
+ x_anchor = x_max;
+ break;
+ case 's': // vertical baseline
+ x_anchor = 0;
+ break;
+ default:
+ goto bad_anchor;
+ }
+ switch (anchor[1]) {
+ case 't': // top
+ y_anchor = 0;
+ break;
+ case 'm': // middle (top + bottom) / 2
+ y_anchor = PIXEL(position / 2);
+ break;
+ case 'b': // bottom
+ y_anchor = PIXEL(position);
+ break;
+ case 'a': // ascender
+ case 's': // horizontal baseline
+ case 'd': // descender
+ default:
+ goto bad_anchor;
+ }
+ }
+ }
+ *width = x_max - x_min;
+ *height = y_max - y_min;
+ *x_offset = -x_anchor + x_min;
+ *y_offset = -(-y_anchor + y_max);
+ return 0;
+
+bad_anchor:
+ PyErr_Format(PyExc_ValueError, "bad anchor specified: %s", anchor);
+ return 1;
+}
+
+static PyObject *
+font_getsize(FontObject *self, PyObject *args) {
+ int width, height, x_offset, y_offset;
+ int load_flags; /* FreeType load_flags parameter */
+ int error;
+ GlyphInfo *glyph_info = NULL; /* computed text layout */
+ size_t count; /* glyph_info length */
+ int horizontal_dir; /* is primary axis horizontal? */
+ int mask = 0; /* is FT_LOAD_TARGET_MONO enabled? */
+ int color = 0; /* is FT_LOAD_COLOR enabled? */
+ const char *mode = NULL;
+ const char *dir = NULL;
+ const char *lang = NULL;
+ const char *anchor = NULL;
+ PyObject *features = Py_None;
+ PyObject *string;
+
+ /* calculate size and bearing for a given string */
+
+ if (!PyArg_ParseTuple(
+ args, "O|zzOzz:getsize", &string, &mode, &dir, &features, &lang, &anchor)) {
+ return NULL;
+ }
+
+ horizontal_dir = dir && strcmp(dir, "ttb") == 0 ? 0 : 1;
+
+ mask = mode && strcmp(mode, "1") == 0;
+ color = mode && strcmp(mode, "RGBA") == 0;
+
+ count = text_layout(string, self, dir, features, lang, &glyph_info, mask, color);
+ if (PyErr_Occurred()) {
+ return NULL;
+ }
+
+ load_flags = FT_LOAD_DEFAULT;
+ if (mask) {
+ load_flags |= FT_LOAD_TARGET_MONO;
+ }
+ if (color) {
+ load_flags |= FT_LOAD_COLOR;
+ }
+
+ error = bounding_box_and_anchors(self->face, anchor, horizontal_dir, glyph_info, count, load_flags, &width, &height, &x_offset, &y_offset);
+ if (glyph_info) {
+ PyMem_Free(glyph_info);
+ glyph_info = NULL;
+ }
+ if (error) {
+ return NULL;
+ }
+
+ return Py_BuildValue(
+ "(ii)(ii)",
+ width,
+ height,
+ x_offset,
+ y_offset);
+}
+
+static PyObject *
+font_render(FontObject *self, PyObject *args) {
+ int x, y; /* pen position, in 26.6 precision */
+ int px, py; /* position of current glyph, in pixels */
+ int x_min, y_max; /* text offset in 26.6 precision */
+ int load_flags; /* FreeType load_flags parameter */
+ int error;
+ FT_Glyph glyph;
+ FT_GlyphSlot glyph_slot;
+ FT_Bitmap bitmap;
+ FT_Bitmap bitmap_converted; /* initialized lazily, for non-8bpp fonts */
+ FT_BitmapGlyph bitmap_glyph;
+ FT_Stroker stroker = NULL;
+ int bitmap_converted_ready = 0; /* has bitmap_converted been initialized */
+ GlyphInfo *glyph_info = NULL; /* computed text layout */
+ size_t i, count; /* glyph_info index and length */
+ int xx, yy; /* pixel offset of current glyph bitmap */
+ int x0, x1; /* horizontal bounds of glyph bitmap to copy */
+ unsigned int bitmap_y; /* glyph bitmap y index */
+ unsigned char *source; /* glyph bitmap source buffer */
+ unsigned char convert_scale; /* scale factor for non-8bpp bitmaps */
+ PyObject *image;
+ Imaging im;
+ Py_ssize_t id;
+ int mask = 0; /* is FT_LOAD_TARGET_MONO enabled? */
+ int color = 0; /* is FT_LOAD_COLOR enabled? */
+ int stroke_width = 0;
+ PY_LONG_LONG foreground_ink_long = 0;
+ unsigned int foreground_ink;
+ const char *mode = NULL;
+ const char *dir = NULL;
+ const char *lang = NULL;
+ const char *anchor = NULL;
+ PyObject *features = Py_None;
+ PyObject *string;
+ PyObject *fill;
+ float x_start = 0;
+ float y_start = 0;
+ int width, height, x_offset, y_offset;
+ int horizontal_dir; /* is primary axis horizontal? */
+
+ /* render string into given buffer (the buffer *must* have
+ the right size, or this will crash) */
+
+ if (!PyArg_ParseTuple(
+ args,
+ "OO|zzOzizLffO:render",
+ &string,
+ &fill,
+ &mode,
+ &dir,
+ &features,
+ &lang,
+ &stroke_width,
+ &anchor,
+ &foreground_ink_long,
+ &x_start,
+ &y_start)) {
+ return NULL;
+ }
+
+ mask = mode && strcmp(mode, "1") == 0;
+ color = mode && strcmp(mode, "RGBA") == 0;
+
+ foreground_ink = foreground_ink_long;
+
+#ifdef FT_COLOR_H
+ if (color) {
+ FT_Color foreground_color;
+ FT_Byte *ink = (FT_Byte *)&foreground_ink;
+ foreground_color.red = ink[0];
+ foreground_color.green = ink[1];
+ foreground_color.blue = ink[2];
+ foreground_color.alpha =
+ (FT_Byte)255; /* ink alpha is handled in ImageDraw.text */
+ FT_Palette_Set_Foreground_Color(self->face, foreground_color);
+ }
+#endif
+
+ count = text_layout(string, self, dir, features, lang, &glyph_info, mask, color);
+ if (PyErr_Occurred()) {
+ return NULL;
+ }
+
+ load_flags = stroke_width ? FT_LOAD_NO_BITMAP : FT_LOAD_DEFAULT;
+ if (mask) {
+ load_flags |= FT_LOAD_TARGET_MONO;
+ }
+ if (color) {
+ load_flags |= FT_LOAD_COLOR;
+ }
+
+ horizontal_dir = dir && strcmp(dir, "ttb") == 0 ? 0 : 1;
+
+ error = bounding_box_and_anchors(self->face, anchor, horizontal_dir, glyph_info, count, load_flags, &width, &height, &x_offset, &y_offset);
+ if (error) {
+ PyMem_Del(glyph_info);
+ return NULL;
+ }
+
+ width += stroke_width * 2 + ceil(x_start);
+ height += stroke_width * 2 + ceil(y_start);
+ image = PyObject_CallFunction(fill, "s(ii)", strcmp(mode, "RGBA") == 0 ? "RGBA" : "L", width, height);
+ if (image == Py_None) {
+ PyMem_Del(glyph_info);
+ return Py_BuildValue("ii", 0, 0);
+ } else if (image == NULL) {
+ PyMem_Del(glyph_info);
+ return NULL;
+ }
+ id = PyLong_AsSsize_t(PyObject_GetAttrString(image, "id"));
+ im = (Imaging)id;
+
+ x_offset -= stroke_width;
+ y_offset -= stroke_width;
+ if (count == 0 || width == 0 || height == 0) {
+ PyMem_Del(glyph_info);
+ return Py_BuildValue("ii", x_offset, y_offset);
+ }
+
+ if (stroke_width) {
+ error = FT_Stroker_New(library, &stroker);
+ if (error) {
+ geterror(error);
+ goto glyph_error;
+ }
+
+ FT_Stroker_Set(
+ stroker,
+ (FT_Fixed)stroke_width * 64,
+ FT_STROKER_LINECAP_ROUND,
+ FT_STROKER_LINEJOIN_ROUND,
+ 0);
+ }
+
+ /*
+ * calculate x_min and y_max
+ * must match font_getsize or there may be clipping!
+ */
+ x = y = x_min = y_max = 0;
+ for (i = 0; i < count; i++) {
+ px = PIXEL(x + glyph_info[i].x_offset);
+ py = PIXEL(y + glyph_info[i].y_offset);
+
+ error =
+ FT_Load_Glyph(self->face, glyph_info[i].index, load_flags | FT_LOAD_RENDER);
+ if (error) {
+ geterror(error);
+ goto glyph_error;
+ }
+
+ glyph_slot = self->face->glyph;
+ bitmap = glyph_slot->bitmap;
+
+ if (glyph_slot->bitmap_top + py > y_max) {
+ y_max = glyph_slot->bitmap_top + py;
+ }
+ if (glyph_slot->bitmap_left + px < x_min) {
+ x_min = glyph_slot->bitmap_left + px;
+ }
+
+ x += glyph_info[i].x_advance;
+ y += glyph_info[i].y_advance;
+ }
+
+ /* set pen position to text origin */
+ x = (-x_min + stroke_width + x_start) * 64;
+ y = (-y_max + (-stroke_width) - y_start) * 64;
+
+ if (stroker == NULL) {
+ load_flags |= FT_LOAD_RENDER;
+ }
+
+ for (i = 0; i < count; i++) {
+ px = PIXEL(x + glyph_info[i].x_offset);
+ py = PIXEL(y + glyph_info[i].y_offset);
+
+ error = FT_Load_Glyph(self->face, glyph_info[i].index, load_flags);
+ if (error) {
+ geterror(error);
+ goto glyph_error;
+ }
+
+ glyph_slot = self->face->glyph;
+ if (stroker != NULL) {
+ error = FT_Get_Glyph(glyph_slot, &glyph);
+ if (!error) {
+ error = FT_Glyph_Stroke(&glyph, stroker, 1);
+ }
+ if (!error) {
+ FT_Vector origin = {0, 0};
+ error = FT_Glyph_To_Bitmap(&glyph, FT_RENDER_MODE_NORMAL, &origin, 1);
+ }
+ if (error) {
+ geterror(error);
+ goto glyph_error;
+ }
+
+ bitmap_glyph = (FT_BitmapGlyph)glyph;
+
+ bitmap = bitmap_glyph->bitmap;
+ xx = px + bitmap_glyph->left;
+ yy = -(py + bitmap_glyph->top);
+ } else {
+ bitmap = glyph_slot->bitmap;
+ xx = px + glyph_slot->bitmap_left;
+ yy = -(py + glyph_slot->bitmap_top);
+ }
+
+ // Null buffer, is dereferenced in FT_Bitmap_Convert
+ if (!bitmap.buffer && bitmap.rows) {
+ PyErr_SetString(PyExc_OSError, "Bitmap missing for glyph");
+ goto glyph_error;
+ }
+
+ /* convert non-8bpp bitmaps */
+ switch (bitmap.pixel_mode) {
+ case FT_PIXEL_MODE_MONO:
+ convert_scale = 255;
+ break;
+ case FT_PIXEL_MODE_GRAY2:
+ convert_scale = 255 / 3;
+ break;
+ case FT_PIXEL_MODE_GRAY4:
+ convert_scale = 255 / 15;
+ break;
+ default:
+ convert_scale = 1;
+ }
+ switch (bitmap.pixel_mode) {
+ case FT_PIXEL_MODE_MONO:
+ case FT_PIXEL_MODE_GRAY2:
+ case FT_PIXEL_MODE_GRAY4:
+ if (!bitmap_converted_ready) {
+ FT_Bitmap_Init(&bitmap_converted);
+ bitmap_converted_ready = 1;
+ }
+ error = FT_Bitmap_Convert(library, &bitmap, &bitmap_converted, 1);
+ if (error) {
+ geterror(error);
+ goto glyph_error;
+ }
+ bitmap = bitmap_converted;
+ /* bitmap is now FT_PIXEL_MODE_GRAY, fall through */
+ case FT_PIXEL_MODE_GRAY:
+ break;
+ case FT_PIXEL_MODE_BGRA:
+ if (color) {
+ break;
+ }
+ /* we didn't ask for color, fall through to default */
+ default:
+ PyErr_SetString(PyExc_OSError, "unsupported bitmap pixel mode");
+ goto glyph_error;
+ }
+
+ /* clip glyph bitmap width to target image bounds */
+ x0 = 0;
+ x1 = bitmap.width;
+ if (xx < 0) {
+ x0 = -xx;
+ }
+ if (xx + x1 > im->xsize) {
+ x1 = im->xsize - xx;
+ }
+
+ source = (unsigned char *)bitmap.buffer;
+ for (bitmap_y = 0; bitmap_y < bitmap.rows; bitmap_y++, yy++) {
+ /* clip glyph bitmap height to target image bounds */
+ if (yy >= 0 && yy < im->ysize) {
+ /* blend this glyph into the buffer */
+ int k;
+ unsigned char v;
+ unsigned char *target;
+ if (color) {
+ /* target[RGB] returns the color, target[A] returns the mask */
+ /* target bands get split again in ImageDraw.text */
+ target = (unsigned char *)im->image[yy] + xx * 4;
+ } else {
+ target = im->image8[yy] + xx;
+ }
+ if (color && bitmap.pixel_mode == FT_PIXEL_MODE_BGRA) {
+ /* paste color glyph */
+ for (k = x0; k < x1; k++) {
+ if (target[k * 4 + 3] < source[k * 4 + 3]) {
+ /* unpremultiply BGRa to RGBA */
+ target[k * 4 + 0] = CLIP8(
+ (255 * (int)source[k * 4 + 2]) / source[k * 4 + 3]);
+ target[k * 4 + 1] = CLIP8(
+ (255 * (int)source[k * 4 + 1]) / source[k * 4 + 3]);
+ target[k * 4 + 2] = CLIP8(
+ (255 * (int)source[k * 4 + 0]) / source[k * 4 + 3]);
+ target[k * 4 + 3] = source[k * 4 + 3];
+ }
+ }
+ } else if (bitmap.pixel_mode == FT_PIXEL_MODE_GRAY) {
+ if (color) {
+ unsigned char *ink = (unsigned char *)&foreground_ink;
+ for (k = x0; k < x1; k++) {
+ v = source[k] * convert_scale;
+ if (target[k * 4 + 3] < v) {
+ target[k * 4 + 0] = ink[0];
+ target[k * 4 + 1] = ink[1];
+ target[k * 4 + 2] = ink[2];
+ target[k * 4 + 3] = v;
+ }
+ }
+ } else {
+ for (k = x0; k < x1; k++) {
+ v = source[k] * convert_scale;
+ if (target[k] < v) {
+ target[k] = v;
+ }
+ }
+ }
+ } else {
+ PyErr_SetString(PyExc_OSError, "unsupported bitmap pixel mode");
+ goto glyph_error;
+ }
+ }
+ source += bitmap.pitch;
+ }
+ x += glyph_info[i].x_advance;
+ y += glyph_info[i].y_advance;
+ if (stroker != NULL) {
+ FT_Done_Glyph(glyph);
+ }
+ }
+
+ if (bitmap_converted_ready) {
+ FT_Bitmap_Done(library, &bitmap_converted);
+ }
+ Py_DECREF(image);
+ FT_Stroker_Done(stroker);
+ PyMem_Del(glyph_info);
+ return Py_BuildValue("ii", x_offset, y_offset);
+
+glyph_error:
+ if (im->destroy) {
+ im->destroy(im);
+ }
+ if (im->image) {
+ free(im->image);
+ }
+ if (stroker != NULL) {
+ FT_Done_Glyph(glyph);
+ }
+ if (bitmap_converted_ready) {
+ FT_Bitmap_Done(library, &bitmap_converted);
+ }
+ FT_Stroker_Done(stroker);
+ PyMem_Del(glyph_info);
+ return NULL;
+}
+
+#if FREETYPE_MAJOR > 2 || (FREETYPE_MAJOR == 2 && FREETYPE_MINOR > 9) || \
+ (FREETYPE_MAJOR == 2 && FREETYPE_MINOR == 9 && FREETYPE_PATCH == 1)
+static PyObject *
+font_getvarnames(FontObject *self) {
+ int error;
+ FT_UInt i, j, num_namedstyles, name_count;
+ FT_MM_Var *master;
+ FT_SfntName name;
+ PyObject *list_names, *list_name;
+
+ error = FT_Get_MM_Var(self->face, &master);
+ if (error) {
+ return geterror(error);
+ }
+
+ num_namedstyles = master->num_namedstyles;
+ list_names = PyList_New(num_namedstyles);
+ if (list_names == NULL) {
+ FT_Done_MM_Var(library, master);
+ return NULL;
+ }
+
+ name_count = FT_Get_Sfnt_Name_Count(self->face);
+ for (i = 0; i < name_count; i++) {
+ error = FT_Get_Sfnt_Name(self->face, i, &name);
+ if (error) {
+ Py_DECREF(list_names);
+ FT_Done_MM_Var(library, master);
+ return geterror(error);
+ }
+
+ for (j = 0; j < num_namedstyles; j++) {
+ if (PyList_GetItem(list_names, j) != NULL) {
+ continue;
+ }
+
+ if (master->namedstyle[j].strid == name.name_id) {
+ list_name = Py_BuildValue("y#", name.string, name.string_len);
+ PyList_SetItem(list_names, j, list_name);
+ break;
+ }
+ }
+ }
+
+ FT_Done_MM_Var(library, master);
+
+ return list_names;
+}
+
+static PyObject *
+font_getvaraxes(FontObject *self) {
+ int error;
+ FT_UInt i, j, num_axis, name_count;
+ FT_MM_Var *master;
+ FT_Var_Axis axis;
+ FT_SfntName name;
+ PyObject *list_axes, *list_axis, *axis_name;
+ error = FT_Get_MM_Var(self->face, &master);
+ if (error) {
+ return geterror(error);
+ }
+
+ num_axis = master->num_axis;
+ name_count = FT_Get_Sfnt_Name_Count(self->face);
+
+ list_axes = PyList_New(num_axis);
+ if (list_axes == NULL) {
+ FT_Done_MM_Var(library, master);
+ return NULL;
+ }
+ for (i = 0; i < num_axis; i++) {
+ axis = master->axis[i];
+
+ list_axis = PyDict_New();
+ if (list_axis == NULL) {
+ Py_DECREF(list_axes);
+ FT_Done_MM_Var(library, master);
+ return NULL;
+ }
+ PyObject *minimum = PyLong_FromLong(axis.minimum / 65536);
+ PyDict_SetItemString(list_axis, "minimum", minimum ? minimum : Py_None);
+ Py_XDECREF(minimum);
+
+ PyObject *def = PyLong_FromLong(axis.def / 65536);
+ PyDict_SetItemString(list_axis, "default", def ? def : Py_None);
+ Py_XDECREF(def);
+
+ PyObject *maximum = PyLong_FromLong(axis.maximum / 65536);
+ PyDict_SetItemString(list_axis, "maximum", maximum ? maximum : Py_None);
+ Py_XDECREF(maximum);
+
+ for (j = 0; j < name_count; j++) {
+ error = FT_Get_Sfnt_Name(self->face, j, &name);
+ if (error) {
+ Py_DECREF(list_axis);
+ Py_DECREF(list_axes);
+ FT_Done_MM_Var(library, master);
+ return geterror(error);
+ }
+
+ if (name.name_id == axis.strid) {
+ axis_name = Py_BuildValue("y#", name.string, name.string_len);
+ PyDict_SetItemString(list_axis, "name", axis_name ? axis_name : Py_None);
+ Py_XDECREF(axis_name);
+ break;
+ }
+ }
+
+ PyList_SetItem(list_axes, i, list_axis);
+ }
+
+ FT_Done_MM_Var(library, master);
+
+ return list_axes;
+}
+
+static PyObject *
+font_setvarname(FontObject *self, PyObject *args) {
+ int error;
+
+ int instance_index;
+ if (!PyArg_ParseTuple(args, "i", &instance_index)) {
+ return NULL;
+ }
+
+ error = FT_Set_Named_Instance(self->face, instance_index);
+ if (error) {
+ return geterror(error);
+ }
+
+ Py_INCREF(Py_None);
+ return Py_None;
+}
+
+static PyObject *
+font_setvaraxes(FontObject *self, PyObject *args) {
+ int error;
+
+ PyObject *axes, *item;
+ Py_ssize_t i, num_coords;
+ FT_Fixed *coords;
+ FT_Fixed coord;
+ if (!PyArg_ParseTuple(args, "O", &axes)) {
+ return NULL;
+ }
+
+ if (!PyList_Check(axes)) {
+ PyErr_SetString(PyExc_TypeError, "argument must be a list");
+ return NULL;
+ }
+
+ num_coords = PyObject_Length(axes);
+ coords = (FT_Fixed*)malloc(num_coords * sizeof(FT_Fixed));
+ if (coords == NULL) {
+ return PyErr_NoMemory();
+ }
+ for (i = 0; i < num_coords; i++) {
+ item = PyList_GET_ITEM(axes, i);
+ if (PyFloat_Check(item)) {
+ coord = PyFloat_AS_DOUBLE(item);
+ } else if (PyLong_Check(item)) {
+ coord = (float)PyLong_AS_LONG(item);
+ } else if (PyNumber_Check(item)) {
+ coord = PyFloat_AsDouble(item);
+ } else {
+ free(coords);
+ PyErr_SetString(PyExc_TypeError, "list must contain numbers");
+ return NULL;
+ }
+ coords[i] = coord * 65536;
+ }
+
+ error = FT_Set_Var_Design_Coordinates(self->face, num_coords, coords);
+ free(coords);
+ if (error) {
+ return geterror(error);
+ }
+
+ Py_INCREF(Py_None);
+ return Py_None;
+}
+#endif
+
+static void
+font_dealloc(FontObject *self) {
+ if (self->face) {
+ FT_Done_Face(self->face);
+ }
+ if (self->font_bytes) {
+ PyMem_Free(self->font_bytes);
+ }
+ PyObject_Del(self);
+}
+
+static PyMethodDef font_methods[] = {
+ {"render", (PyCFunction)font_render, METH_VARARGS},
+ {"getsize", (PyCFunction)font_getsize, METH_VARARGS},
+ {"getlength", (PyCFunction)font_getlength, METH_VARARGS},
+#if FREETYPE_MAJOR > 2 || (FREETYPE_MAJOR == 2 && FREETYPE_MINOR > 9) || \
+ (FREETYPE_MAJOR == 2 && FREETYPE_MINOR == 9 && FREETYPE_PATCH == 1)
+ {"getvarnames", (PyCFunction)font_getvarnames, METH_NOARGS},
+ {"getvaraxes", (PyCFunction)font_getvaraxes, METH_NOARGS},
+ {"setvarname", (PyCFunction)font_setvarname, METH_VARARGS},
+ {"setvaraxes", (PyCFunction)font_setvaraxes, METH_VARARGS},
+#endif
+ {NULL, NULL}};
+
+static PyObject *
+font_getattr_family(FontObject *self, void *closure) {
+ if (self->face->family_name) {
+ return PyUnicode_FromString(self->face->family_name);
+ }
+ Py_RETURN_NONE;
+}
+
+static PyObject *
+font_getattr_style(FontObject *self, void *closure) {
+ if (self->face->style_name) {
+ return PyUnicode_FromString(self->face->style_name);
+ }
+ Py_RETURN_NONE;
+}
+
+static PyObject *
+font_getattr_ascent(FontObject *self, void *closure) {
+ return PyLong_FromLong(PIXEL(self->face->size->metrics.ascender));
+}
+
+static PyObject *
+font_getattr_descent(FontObject *self, void *closure) {
+ return PyLong_FromLong(-PIXEL(self->face->size->metrics.descender));
+}
+
+static PyObject *
+font_getattr_height(FontObject *self, void *closure) {
+ return PyLong_FromLong(PIXEL(self->face->size->metrics.height));
+}
+
+static PyObject *
+font_getattr_x_ppem(FontObject *self, void *closure) {
+ return PyLong_FromLong(self->face->size->metrics.x_ppem);
+}
+
+static PyObject *
+font_getattr_y_ppem(FontObject *self, void *closure) {
+ return PyLong_FromLong(self->face->size->metrics.y_ppem);
+}
+
+static PyObject *
+font_getattr_glyphs(FontObject *self, void *closure) {
+ return PyLong_FromLong(self->face->num_glyphs);
+}
+
+static struct PyGetSetDef font_getsetters[] = {
+ {"family", (getter)font_getattr_family},
+ {"style", (getter)font_getattr_style},
+ {"ascent", (getter)font_getattr_ascent},
+ {"descent", (getter)font_getattr_descent},
+ {"height", (getter)font_getattr_height},
+ {"x_ppem", (getter)font_getattr_x_ppem},
+ {"y_ppem", (getter)font_getattr_y_ppem},
+ {"glyphs", (getter)font_getattr_glyphs},
+ {NULL}};
+
+static PyTypeObject Font_Type = {
+ PyVarObject_HEAD_INIT(NULL, 0) "Font", /*tp_name*/
+ sizeof(FontObject), /*tp_basicsize*/
+ 0, /*tp_itemsize*/
+ /* methods */
+ (destructor)font_dealloc, /*tp_dealloc*/
+ 0, /*tp_vectorcall_offset*/
+ 0, /*tp_getattr*/
+ 0, /*tp_setattr*/
+ 0, /*tp_as_async*/
+ 0, /*tp_repr*/
+ 0, /*tp_as_number*/
+ 0, /*tp_as_sequence*/
+ 0, /*tp_as_mapping*/
+ 0, /*tp_hash*/
+ 0, /*tp_call*/
+ 0, /*tp_str*/
+ 0, /*tp_getattro*/
+ 0, /*tp_setattro*/
+ 0, /*tp_as_buffer*/
+ Py_TPFLAGS_DEFAULT, /*tp_flags*/
+ 0, /*tp_doc*/
+ 0, /*tp_traverse*/
+ 0, /*tp_clear*/
+ 0, /*tp_richcompare*/
+ 0, /*tp_weaklistoffset*/
+ 0, /*tp_iter*/
+ 0, /*tp_iternext*/
+ font_methods, /*tp_methods*/
+ 0, /*tp_members*/
+ font_getsetters, /*tp_getset*/
+};
+
+static PyMethodDef _functions[] = {
+ {"getfont", (PyCFunction)getfont, METH_VARARGS | METH_KEYWORDS}, {NULL, NULL}};
+
+static int
+setup_module(PyObject *m) {
+ PyObject *d;
+ PyObject *v;
+ int major, minor, patch;
+
+ d = PyModule_GetDict(m);
+
+ /* Ready object type */
+ PyType_Ready(&Font_Type);
+
+ if (FT_Init_FreeType(&library)) {
+ return 0; /* leave it uninitialized */
+ }
+
+ FT_Library_Version(library, &major, &minor, &patch);
+
+ v = PyUnicode_FromFormat("%d.%d.%d", major, minor, patch);
+ PyDict_SetItemString(d, "freetype2_version", v ? v : Py_None);
+ Py_XDECREF(v);
+
+#ifdef HAVE_RAQM
+#if defined(HAVE_RAQM_SYSTEM) || defined(HAVE_FRIBIDI_SYSTEM)
+ have_raqm = 1;
+#else
+ load_fribidi();
+ have_raqm = !!p_fribidi;
+#endif
+#else
+ have_raqm = 0;
+#endif
+
+ /* if we have Raqm, we have all three (but possibly no version info) */
+ v = PyBool_FromLong(have_raqm);
+ PyDict_SetItemString(d, "HAVE_RAQM", v);
+ PyDict_SetItemString(d, "HAVE_FRIBIDI", v);
+ PyDict_SetItemString(d, "HAVE_HARFBUZZ", v);
+ Py_DECREF(v);
+ if (have_raqm) {
+ v = NULL;
+#ifdef RAQM_VERSION_MAJOR
+ v = PyUnicode_FromString(raqm_version_string());
+#endif
+ PyDict_SetItemString(d, "raqm_version", v ? v : Py_None);
+ Py_XDECREF(v);
+
+ v = NULL;
+#ifdef FRIBIDI_MAJOR_VERSION
+ {
+ const char *a = strchr(fribidi_version_info, ')');
+ const char *b = strchr(fribidi_version_info, '\n');
+ if (a && b && a + 2 < b) {
+ v = PyUnicode_FromStringAndSize(a + 2, b - (a + 2));
+ }
+ }
+#endif
+ PyDict_SetItemString(d, "fribidi_version", v ? v : Py_None);
+ Py_XDECREF(v);
+
+ v = NULL;
+#ifdef HB_VERSION_STRING
+ v = PyUnicode_FromString(hb_version_string());
+#endif
+ PyDict_SetItemString(d, "harfbuzz_version", v ? v : Py_None);
+ Py_XDECREF(v);
+ }
+
+ return 0;
+}
+
+PyMODINIT_FUNC
+PyInit__imagingft(void) {
+ PyObject *m;
+
+ static PyModuleDef module_def = {
+ PyModuleDef_HEAD_INIT,
+ "_imagingft", /* m_name */
+ NULL, /* m_doc */
+ -1, /* m_size */
+ _functions, /* m_methods */
+ };
+
+ m = PyModule_Create(&module_def);
+
+ if (setup_module(m) < 0) {
+ return NULL;
+ }
+
+ return m;
+}
diff --git a/contrib/python/Pillow/py3/_imagingmath.c b/contrib/python/Pillow/py3/_imagingmath.c
new file mode 100644
index 00000000000..067c165b248
--- /dev/null
+++ b/contrib/python/Pillow/py3/_imagingmath.c
@@ -0,0 +1,294 @@
+/*
+ * The Python Imaging Library
+ *
+ * a simple math add-on for the Python Imaging Library
+ *
+ * history:
+ * 1999-02-15 fl Created
+ * 2005-05-05 fl Simplified and cleaned up for PIL 1.1.6
+ *
+ * Copyright (c) 1999-2005 by Secret Labs AB
+ * Copyright (c) 2005 by Fredrik Lundh
+ *
+ * See the README file for information on usage and redistribution.
+ */
+
+#include "Python.h"
+
+#include "libImaging/Imaging.h"
+
+#include "math.h"
+#include "float.h"
+
+#define MAX_INT32 2147483647.0
+#define MIN_INT32 -2147483648.0
+
+#define UNOP(name, op, type) \
+ void name(Imaging out, Imaging im1) { \
+ int x, y; \
+ for (y = 0; y < out->ysize; y++) { \
+ type *p0 = (type *)out->image[y]; \
+ type *p1 = (type *)im1->image[y]; \
+ for (x = 0; x < out->xsize; x++) { \
+ *p0 = op(type, *p1); \
+ p0++; \
+ p1++; \
+ } \
+ } \
+ }
+
+#define BINOP(name, op, type) \
+ void name(Imaging out, Imaging im1, Imaging im2) { \
+ int x, y; \
+ for (y = 0; y < out->ysize; y++) { \
+ type *p0 = (type *)out->image[y]; \
+ type *p1 = (type *)im1->image[y]; \
+ type *p2 = (type *)im2->image[y]; \
+ for (x = 0; x < out->xsize; x++) { \
+ *p0 = op(type, *p1, *p2); \
+ p0++; \
+ p1++; \
+ p2++; \
+ } \
+ } \
+ }
+
+#define NEG(type, v1) -(v1)
+#define INVERT(type, v1) ~(v1)
+
+#define ADD(type, v1, v2) (v1) + (v2)
+#define SUB(type, v1, v2) (v1) - (v2)
+#define MUL(type, v1, v2) (v1) * (v2)
+
+#define MIN(type, v1, v2) ((v1) < (v2)) ? (v1) : (v2)
+#define MAX(type, v1, v2) ((v1) > (v2)) ? (v1) : (v2)
+
+#define AND(type, v1, v2) (v1) & (v2)
+#define OR(type, v1, v2) (v1) | (v2)
+#define XOR(type, v1, v2) (v1) ^ (v2)
+#define LSHIFT(type, v1, v2) (v1) << (v2)
+#define RSHIFT(type, v1, v2) (v1) >> (v2)
+
+#define ABS_I(type, v1) abs((v1))
+#define ABS_F(type, v1) fabs((v1))
+
+/* --------------------------------------------------------------------
+ * some day, we should add FPE protection mechanisms. see pyfpe.h for
+ * details.
+ *
+ * PyFPE_START_PROTECT("Error in foobar", return 0)
+ * PyFPE_END_PROTECT(result)
+ */
+
+#define DIV_I(type, v1, v2) ((v2) != 0) ? (v1) / (v2) : 0
+#define DIV_F(type, v1, v2) ((v2) != 0.0F) ? (v1) / (v2) : 0.0F
+
+#define MOD_I(type, v1, v2) ((v2) != 0) ? (v1) % (v2) : 0
+#define MOD_F(type, v1, v2) ((v2) != 0.0F) ? fmod((v1), (v2)) : 0.0F
+
+static int
+powi(int x, int y) {
+ double v = pow(x, y) + 0.5;
+ if (errno == EDOM) {
+ return 0;
+ }
+ if (v < MIN_INT32) {
+ v = MIN_INT32;
+ } else if (v > MAX_INT32) {
+ v = MAX_INT32;
+ }
+ return (int)v;
+}
+
+#define POW_I(type, v1, v2) powi(v1, v2)
+#define POW_F(type, v1, v2) powf(v1, v2) /* FIXME: EDOM handling */
+
+#define DIFF_I(type, v1, v2) abs((v1) - (v2))
+#define DIFF_F(type, v1, v2) fabs((v1) - (v2))
+
+#define EQ(type, v1, v2) (v1) == (v2)
+#define NE(type, v1, v2) (v1) != (v2)
+#define LT(type, v1, v2) (v1) < (v2)
+#define LE(type, v1, v2) (v1) <= (v2)
+#define GT(type, v1, v2) (v1) > (v2)
+#define GE(type, v1, v2) (v1) >= (v2)
+
+UNOP(abs_I, ABS_I, INT32)
+UNOP(neg_I, NEG, INT32)
+
+BINOP(add_I, ADD, INT32)
+BINOP(sub_I, SUB, INT32)
+BINOP(mul_I, MUL, INT32)
+BINOP(div_I, DIV_I, INT32)
+BINOP(mod_I, MOD_I, INT32)
+BINOP(pow_I, POW_I, INT32)
+BINOP(diff_I, DIFF_I, INT32)
+
+UNOP(invert_I, INVERT, INT32)
+BINOP(and_I, AND, INT32)
+BINOP(or_I, OR, INT32)
+BINOP(xor_I, XOR, INT32)
+BINOP(lshift_I, LSHIFT, INT32)
+BINOP(rshift_I, RSHIFT, INT32)
+
+BINOP(min_I, MIN, INT32)
+BINOP(max_I, MAX, INT32)
+
+BINOP(eq_I, EQ, INT32)
+BINOP(ne_I, NE, INT32)
+BINOP(lt_I, LT, INT32)
+BINOP(le_I, LE, INT32)
+BINOP(gt_I, GT, INT32)
+BINOP(ge_I, GE, INT32)
+
+UNOP(abs_F, ABS_F, FLOAT32)
+UNOP(neg_F, NEG, FLOAT32)
+
+BINOP(add_F, ADD, FLOAT32)
+BINOP(sub_F, SUB, FLOAT32)
+BINOP(mul_F, MUL, FLOAT32)
+BINOP(div_F, DIV_F, FLOAT32)
+BINOP(mod_F, MOD_F, FLOAT32)
+BINOP(pow_F, POW_F, FLOAT32)
+BINOP(diff_F, DIFF_F, FLOAT32)
+
+BINOP(min_F, MIN, FLOAT32)
+BINOP(max_F, MAX, FLOAT32)
+
+BINOP(eq_F, EQ, FLOAT32)
+BINOP(ne_F, NE, FLOAT32)
+BINOP(lt_F, LT, FLOAT32)
+BINOP(le_F, LE, FLOAT32)
+BINOP(gt_F, GT, FLOAT32)
+BINOP(ge_F, GE, FLOAT32)
+
+static PyObject *
+_unop(PyObject *self, PyObject *args) {
+ Imaging out;
+ Imaging im1;
+ void (*unop)(Imaging, Imaging);
+
+ Py_ssize_t op, i0, i1;
+ if (!PyArg_ParseTuple(args, "nnn", &op, &i0, &i1)) {
+ return NULL;
+ }
+
+ out = (Imaging)i0;
+ im1 = (Imaging)i1;
+
+ unop = (void *)op;
+
+ unop(out, im1);
+
+ Py_INCREF(Py_None);
+ return Py_None;
+}
+
+static PyObject *
+_binop(PyObject *self, PyObject *args) {
+ Imaging out;
+ Imaging im1;
+ Imaging im2;
+ void (*binop)(Imaging, Imaging, Imaging);
+
+ Py_ssize_t op, i0, i1, i2;
+ if (!PyArg_ParseTuple(args, "nnnn", &op, &i0, &i1, &i2)) {
+ return NULL;
+ }
+
+ out = (Imaging)i0;
+ im1 = (Imaging)i1;
+ im2 = (Imaging)i2;
+
+ binop = (void *)op;
+
+ binop(out, im1, im2);
+
+ Py_INCREF(Py_None);
+ return Py_None;
+}
+
+static PyMethodDef _functions[] = {
+ {"unop", _unop, 1}, {"binop", _binop, 1}, {NULL, NULL}};
+
+static void
+install(PyObject *d, char *name, void *value) {
+ PyObject *v = PyLong_FromSsize_t((Py_ssize_t)value);
+ if (!v || PyDict_SetItemString(d, name, v)) {
+ PyErr_Clear();
+ }
+ Py_XDECREF(v);
+}
+
+static int
+setup_module(PyObject *m) {
+ PyObject *d = PyModule_GetDict(m);
+
+ install(d, "abs_I", abs_I);
+ install(d, "neg_I", neg_I);
+ install(d, "add_I", add_I);
+ install(d, "sub_I", sub_I);
+ install(d, "diff_I", diff_I);
+ install(d, "mul_I", mul_I);
+ install(d, "div_I", div_I);
+ install(d, "mod_I", mod_I);
+ install(d, "min_I", min_I);
+ install(d, "max_I", max_I);
+ install(d, "pow_I", pow_I);
+
+ install(d, "invert_I", invert_I);
+ install(d, "and_I", and_I);
+ install(d, "or_I", or_I);
+ install(d, "xor_I", xor_I);
+ install(d, "lshift_I", lshift_I);
+ install(d, "rshift_I", rshift_I);
+
+ install(d, "eq_I", eq_I);
+ install(d, "ne_I", ne_I);
+ install(d, "lt_I", lt_I);
+ install(d, "le_I", le_I);
+ install(d, "gt_I", gt_I);
+ install(d, "ge_I", ge_I);
+
+ install(d, "abs_F", abs_F);
+ install(d, "neg_F", neg_F);
+ install(d, "add_F", add_F);
+ install(d, "sub_F", sub_F);
+ install(d, "diff_F", diff_F);
+ install(d, "mul_F", mul_F);
+ install(d, "div_F", div_F);
+ install(d, "mod_F", mod_F);
+ install(d, "min_F", min_F);
+ install(d, "max_F", max_F);
+ install(d, "pow_F", pow_F);
+
+ install(d, "eq_F", eq_F);
+ install(d, "ne_F", ne_F);
+ install(d, "lt_F", lt_F);
+ install(d, "le_F", le_F);
+ install(d, "gt_F", gt_F);
+ install(d, "ge_F", ge_F);
+
+ return 0;
+}
+
+PyMODINIT_FUNC
+PyInit__imagingmath(void) {
+ PyObject *m;
+
+ static PyModuleDef module_def = {
+ PyModuleDef_HEAD_INIT,
+ "_imagingmath", /* m_name */
+ NULL, /* m_doc */
+ -1, /* m_size */
+ _functions, /* m_methods */
+ };
+
+ m = PyModule_Create(&module_def);
+
+ if (setup_module(m) < 0) {
+ return NULL;
+ }
+
+ return m;
+}
diff --git a/contrib/python/Pillow/py3/_imagingmorph.c b/contrib/python/Pillow/py3/_imagingmorph.c
new file mode 100644
index 00000000000..8815c2b7ec6
--- /dev/null
+++ b/contrib/python/Pillow/py3/_imagingmorph.c
@@ -0,0 +1,273 @@
+/*
+ * The Python Imaging Library
+ *
+ * A binary morphology add-on for the Python Imaging Library
+ *
+ * History:
+ * 2014-06-04 Initial version.
+ *
+ * Copyright (c) 2014 Dov Grobgeld <[email protected]>
+ *
+ * See the README file for information on usage and redistribution.
+ */
+
+#include "Python.h"
+#include "libImaging/Imaging.h"
+
+#define LUT_SIZE (1 << 9)
+
+/* Apply a morphologic LUT to a binary image. Outputs a
+ a new binary image.
+
+ Expected parameters:
+
+ 1. a LUT - a 512 byte size lookup table.
+ 2. an input Imaging image id.
+ 3. an output Imaging image id
+
+ Returns number of changed pixels.
+*/
+static PyObject *
+apply(PyObject *self, PyObject *args) {
+ const char *lut;
+ PyObject *py_lut;
+ Py_ssize_t lut_len, i0, i1;
+ Imaging imgin, imgout;
+ int width, height;
+ int row_idx, col_idx;
+ UINT8 **inrows, **outrows;
+ int num_changed_pixels = 0;
+
+ if (!PyArg_ParseTuple(args, "Onn", &py_lut, &i0, &i1)) {
+ PyErr_SetString(PyExc_RuntimeError, "Argument parsing problem");
+ return NULL;
+ }
+
+ if (!PyBytes_Check(py_lut)) {
+ PyErr_SetString(PyExc_RuntimeError, "The morphology LUT is not a bytes object");
+ return NULL;
+ }
+
+ lut_len = PyBytes_Size(py_lut);
+
+ if (lut_len < LUT_SIZE) {
+ PyErr_SetString(PyExc_RuntimeError, "The morphology LUT has the wrong size");
+ return NULL;
+ }
+
+ lut = PyBytes_AsString(py_lut);
+
+ imgin = (Imaging)i0;
+ imgout = (Imaging)i1;
+ width = imgin->xsize;
+ height = imgin->ysize;
+
+ if (imgin->type != IMAGING_TYPE_UINT8 || imgin->bands != 1) {
+ PyErr_SetString(PyExc_RuntimeError, "Unsupported image type");
+ return NULL;
+ }
+ if (imgout->type != IMAGING_TYPE_UINT8 || imgout->bands != 1) {
+ PyErr_SetString(PyExc_RuntimeError, "Unsupported image type");
+ return NULL;
+ }
+
+ inrows = imgin->image8;
+ outrows = imgout->image8;
+
+ for (row_idx = 0; row_idx < height; row_idx++) {
+ UINT8 *outrow = outrows[row_idx];
+ UINT8 *inrow = inrows[row_idx];
+ UINT8 *prow, *nrow; /* Previous and next row */
+
+ /* zero boundary conditions. TBD support other modes */
+ outrow[0] = outrow[width - 1] = 0;
+ if (row_idx == 0 || row_idx == height - 1) {
+ for (col_idx = 0; col_idx < width; col_idx++) {
+ outrow[col_idx] = 0;
+ }
+ continue;
+ }
+
+ prow = inrows[row_idx - 1];
+ nrow = inrows[row_idx + 1];
+
+ for (col_idx = 1; col_idx < width - 1; col_idx++) {
+ int cim = col_idx - 1;
+ int cip = col_idx + 1;
+ unsigned char b0 = prow[cim] & 1;
+ unsigned char b1 = prow[col_idx] & 1;
+ unsigned char b2 = prow[cip] & 1;
+
+ unsigned char b3 = inrow[cim] & 1;
+ unsigned char b4 = inrow[col_idx] & 1;
+ unsigned char b5 = inrow[cip] & 1;
+
+ unsigned char b6 = nrow[cim] & 1;
+ unsigned char b7 = nrow[col_idx] & 1;
+ unsigned char b8 = nrow[cip] & 1;
+
+ int lut_idx =
+ (b0 | (b1 << 1) | (b2 << 2) | (b3 << 3) | (b4 << 4) | (b5 << 5) |
+ (b6 << 6) | (b7 << 7) | (b8 << 8));
+ outrow[col_idx] = 255 * (lut[lut_idx] & 1);
+ num_changed_pixels += ((b4 & 1) != (outrow[col_idx] & 1));
+ }
+ }
+ return Py_BuildValue("i", num_changed_pixels);
+}
+
+/* Match a morphologic LUT to a binary image and return a list
+ of the coordinates of all matching pixels.
+
+ Expected parameters:
+
+ 1. a LUT - a 512 byte size lookup table.
+ 2. an input Imaging image id.
+
+ Returns list of matching pixels.
+*/
+static PyObject *
+match(PyObject *self, PyObject *args) {
+ const char *lut;
+ PyObject *py_lut;
+ Py_ssize_t lut_len, i0;
+ Imaging imgin;
+ int width, height;
+ int row_idx, col_idx;
+ UINT8 **inrows;
+ PyObject *ret = PyList_New(0);
+ if (ret == NULL) {
+ return NULL;
+ }
+
+ if (!PyArg_ParseTuple(args, "On", &py_lut, &i0)) {
+ Py_DECREF(ret);
+ PyErr_SetString(PyExc_RuntimeError, "Argument parsing problem");
+ return NULL;
+ }
+
+ if (!PyBytes_Check(py_lut)) {
+ Py_DECREF(ret);
+ PyErr_SetString(PyExc_RuntimeError, "The morphology LUT is not a bytes object");
+ return NULL;
+ }
+
+ lut_len = PyBytes_Size(py_lut);
+
+ if (lut_len < LUT_SIZE) {
+ Py_DECREF(ret);
+ PyErr_SetString(PyExc_RuntimeError, "The morphology LUT has the wrong size");
+ return NULL;
+ }
+
+ lut = PyBytes_AsString(py_lut);
+ imgin = (Imaging)i0;
+
+ if (imgin->type != IMAGING_TYPE_UINT8 || imgin->bands != 1) {
+ Py_DECREF(ret);
+ PyErr_SetString(PyExc_RuntimeError, "Unsupported image type");
+ return NULL;
+ }
+
+ inrows = imgin->image8;
+ width = imgin->xsize;
+ height = imgin->ysize;
+
+ for (row_idx = 1; row_idx < height - 1; row_idx++) {
+ UINT8 *inrow = inrows[row_idx];
+ UINT8 *prow, *nrow;
+
+ prow = inrows[row_idx - 1];
+ nrow = inrows[row_idx + 1];
+
+ for (col_idx = 1; col_idx < width - 1; col_idx++) {
+ int cim = col_idx - 1;
+ int cip = col_idx + 1;
+ unsigned char b0 = prow[cim] & 1;
+ unsigned char b1 = prow[col_idx] & 1;
+ unsigned char b2 = prow[cip] & 1;
+
+ unsigned char b3 = inrow[cim] & 1;
+ unsigned char b4 = inrow[col_idx] & 1;
+ unsigned char b5 = inrow[cip] & 1;
+
+ unsigned char b6 = nrow[cim] & 1;
+ unsigned char b7 = nrow[col_idx] & 1;
+ unsigned char b8 = nrow[cip] & 1;
+
+ int lut_idx =
+ (b0 | (b1 << 1) | (b2 << 2) | (b3 << 3) | (b4 << 4) | (b5 << 5) |
+ (b6 << 6) | (b7 << 7) | (b8 << 8));
+ if (lut[lut_idx]) {
+ PyObject *coordObj = Py_BuildValue("(nn)", col_idx, row_idx);
+ PyList_Append(ret, coordObj);
+ Py_XDECREF(coordObj);
+ }
+ }
+ }
+
+ return ret;
+}
+
+/* Return a list of the coordinates of all turned on pixels in an image.
+ May be used to extract features after a sequence of MorphOps were applied.
+ This is faster than match as only 1x1 lookup is made.
+*/
+static PyObject *
+get_on_pixels(PyObject *self, PyObject *args) {
+ Py_ssize_t i0;
+ Imaging img;
+ UINT8 **rows;
+ int row_idx, col_idx;
+ int width, height;
+ PyObject *ret = PyList_New(0);
+ if (ret == NULL) {
+ return NULL;
+ }
+
+ if (!PyArg_ParseTuple(args, "n", &i0)) {
+ Py_DECREF(ret);
+ PyErr_SetString(PyExc_RuntimeError, "Argument parsing problem");
+ return NULL;
+ }
+ img = (Imaging)i0;
+ rows = img->image8;
+ width = img->xsize;
+ height = img->ysize;
+
+ for (row_idx = 0; row_idx < height; row_idx++) {
+ UINT8 *row = rows[row_idx];
+ for (col_idx = 0; col_idx < width; col_idx++) {
+ if (row[col_idx]) {
+ PyObject *coordObj = Py_BuildValue("(nn)", col_idx, row_idx);
+ PyList_Append(ret, coordObj);
+ Py_XDECREF(coordObj);
+ }
+ }
+ }
+ return ret;
+}
+
+static PyMethodDef functions[] = {
+ /* Functions */
+ {"apply", (PyCFunction)apply, METH_VARARGS, NULL},
+ {"get_on_pixels", (PyCFunction)get_on_pixels, METH_VARARGS, NULL},
+ {"match", (PyCFunction)match, METH_VARARGS, NULL},
+ {NULL, NULL, 0, NULL}};
+
+PyMODINIT_FUNC
+PyInit__imagingmorph(void) {
+ PyObject *m;
+
+ static PyModuleDef module_def = {
+ PyModuleDef_HEAD_INIT,
+ "_imagingmorph", /* m_name */
+ "A module for doing image morphology", /* m_doc */
+ -1, /* m_size */
+ functions, /* m_methods */
+ };
+
+ m = PyModule_Create(&module_def);
+
+ return m;
+}
diff --git a/contrib/python/Pillow/py3/_webp.c b/contrib/python/Pillow/py3/_webp.c
new file mode 100644
index 00000000000..f59a6b2fd73
--- /dev/null
+++ b/contrib/python/Pillow/py3/_webp.c
@@ -0,0 +1,988 @@
+#define PY_SSIZE_T_CLEAN
+#include <Python.h>
+#include "libImaging/Imaging.h"
+#include <webp/encode.h>
+#include <webp/decode.h>
+#include <webp/types.h>
+
+#ifdef HAVE_WEBPMUX
+#include <webp/mux.h>
+#include <webp/demux.h>
+
+/*
+ * Check the versions from mux.h and demux.h, to ensure the WebPAnimEncoder and
+ * WebPAnimDecoder APIs are present (initial support was added in 0.5.0). The
+ * very early versions had some significant differences, so we require later
+ * versions, before enabling animation support.
+ */
+#if WEBP_MUX_ABI_VERSION >= 0x0104 && WEBP_DEMUX_ABI_VERSION >= 0x0105
+#define HAVE_WEBPANIM
+#endif
+
+#endif
+
+/* -------------------------------------------------------------------- */
+/* WebP Muxer Error Handling */
+/* -------------------------------------------------------------------- */
+
+#ifdef HAVE_WEBPMUX
+
+static const char *const kErrorMessages[-WEBP_MUX_NOT_ENOUGH_DATA + 1] = {
+ "WEBP_MUX_NOT_FOUND",
+ "WEBP_MUX_INVALID_ARGUMENT",
+ "WEBP_MUX_BAD_DATA",
+ "WEBP_MUX_MEMORY_ERROR",
+ "WEBP_MUX_NOT_ENOUGH_DATA"};
+
+PyObject *
+HandleMuxError(WebPMuxError err, char *chunk) {
+ char message[100];
+ int message_len;
+ assert(err <= WEBP_MUX_NOT_FOUND && err >= WEBP_MUX_NOT_ENOUGH_DATA);
+
+ // Check for a memory error first
+ if (err == WEBP_MUX_MEMORY_ERROR) {
+ return PyErr_NoMemory();
+ }
+
+ // Create the error message
+ if (chunk == NULL) {
+ message_len =
+ sprintf(message, "could not assemble chunks: %s", kErrorMessages[-err]);
+ } else {
+ message_len = sprintf(
+ message, "could not set %.4s chunk: %s", chunk, kErrorMessages[-err]);
+ }
+ if (message_len < 0) {
+ PyErr_SetString(PyExc_RuntimeError, "failed to construct error message");
+ return NULL;
+ }
+
+ // Set the proper error type
+ switch (err) {
+ case WEBP_MUX_NOT_FOUND:
+ case WEBP_MUX_INVALID_ARGUMENT:
+ PyErr_SetString(PyExc_ValueError, message);
+ break;
+
+ case WEBP_MUX_BAD_DATA:
+ case WEBP_MUX_NOT_ENOUGH_DATA:
+ PyErr_SetString(PyExc_OSError, message);
+ break;
+
+ default:
+ PyErr_SetString(PyExc_RuntimeError, message);
+ break;
+ }
+ return NULL;
+}
+
+#endif
+
+/* -------------------------------------------------------------------- */
+/* WebP Animation Support */
+/* -------------------------------------------------------------------- */
+
+#ifdef HAVE_WEBPANIM
+
+// Encoder type
+typedef struct {
+ PyObject_HEAD WebPAnimEncoder *enc;
+ WebPPicture frame;
+} WebPAnimEncoderObject;
+
+static PyTypeObject WebPAnimEncoder_Type;
+
+// Decoder type
+typedef struct {
+ PyObject_HEAD WebPAnimDecoder *dec;
+ WebPAnimInfo info;
+ WebPData data;
+ char *mode;
+} WebPAnimDecoderObject;
+
+static PyTypeObject WebPAnimDecoder_Type;
+
+// Encoder functions
+PyObject *
+_anim_encoder_new(PyObject *self, PyObject *args) {
+ int width, height;
+ uint32_t bgcolor;
+ int loop_count;
+ int minimize_size;
+ int kmin, kmax;
+ int allow_mixed;
+ int verbose;
+ WebPAnimEncoderOptions enc_options;
+ WebPAnimEncoderObject *encp = NULL;
+ WebPAnimEncoder *enc = NULL;
+
+ if (!PyArg_ParseTuple(
+ args,
+ "iiIiiiiii",
+ &width,
+ &height,
+ &bgcolor,
+ &loop_count,
+ &minimize_size,
+ &kmin,
+ &kmax,
+ &allow_mixed,
+ &verbose)) {
+ return NULL;
+ }
+
+ // Setup and configure the encoder's options (these are animation-specific)
+ if (!WebPAnimEncoderOptionsInit(&enc_options)) {
+ PyErr_SetString(PyExc_RuntimeError, "failed to initialize encoder options");
+ return NULL;
+ }
+ enc_options.anim_params.bgcolor = bgcolor;
+ enc_options.anim_params.loop_count = loop_count;
+ enc_options.minimize_size = minimize_size;
+ enc_options.kmin = kmin;
+ enc_options.kmax = kmax;
+ enc_options.allow_mixed = allow_mixed;
+ enc_options.verbose = verbose;
+
+ // Validate canvas dimensions
+ if (width <= 0 || height <= 0) {
+ PyErr_SetString(PyExc_ValueError, "invalid canvas dimensions");
+ return NULL;
+ }
+
+ // Create a new animation encoder and picture frame
+ encp = PyObject_New(WebPAnimEncoderObject, &WebPAnimEncoder_Type);
+ if (encp) {
+ if (WebPPictureInit(&(encp->frame))) {
+ enc = WebPAnimEncoderNew(width, height, &enc_options);
+ if (enc) {
+ encp->enc = enc;
+ return (PyObject *)encp;
+ }
+ WebPPictureFree(&(encp->frame));
+ }
+ PyObject_Del(encp);
+ }
+ PyErr_SetString(PyExc_RuntimeError, "could not create encoder object");
+ return NULL;
+}
+
+void
+_anim_encoder_dealloc(PyObject *self) {
+ WebPAnimEncoderObject *encp = (WebPAnimEncoderObject *)self;
+ WebPPictureFree(&(encp->frame));
+ WebPAnimEncoderDelete(encp->enc);
+}
+
+PyObject *
+_anim_encoder_add(PyObject *self, PyObject *args) {
+ uint8_t *rgb;
+ Py_ssize_t size;
+ int timestamp;
+ int width;
+ int height;
+ char *mode;
+ int lossless;
+ float quality_factor;
+ int method;
+ WebPConfig config;
+ WebPAnimEncoderObject *encp = (WebPAnimEncoderObject *)self;
+ WebPAnimEncoder *enc = encp->enc;
+ WebPPicture *frame = &(encp->frame);
+
+ if (!PyArg_ParseTuple(
+ args,
+ "z#iiisifi",
+ (char **)&rgb,
+ &size,
+ &timestamp,
+ &width,
+ &height,
+ &mode,
+ &lossless,
+ &quality_factor,
+ &method)) {
+ return NULL;
+ }
+
+ // Check for NULL frame, which sets duration of final frame
+ if (!rgb) {
+ WebPAnimEncoderAdd(enc, NULL, timestamp, NULL);
+ Py_RETURN_NONE;
+ }
+
+ // Setup config for this frame
+ if (!WebPConfigInit(&config)) {
+ PyErr_SetString(PyExc_RuntimeError, "failed to initialize config!");
+ return NULL;
+ }
+ config.lossless = lossless;
+ config.quality = quality_factor;
+ config.method = method;
+
+ // Validate the config
+ if (!WebPValidateConfig(&config)) {
+ PyErr_SetString(PyExc_ValueError, "invalid configuration");
+ return NULL;
+ }
+
+ // Populate the frame with raw bytes passed to us
+ frame->width = width;
+ frame->height = height;
+ frame->use_argb = 1; // Don't convert RGB pixels to YUV
+ if (strcmp(mode, "RGBA") == 0) {
+ WebPPictureImportRGBA(frame, rgb, 4 * width);
+ } else if (strcmp(mode, "RGBX") == 0) {
+ WebPPictureImportRGBX(frame, rgb, 4 * width);
+ } else {
+ WebPPictureImportRGB(frame, rgb, 3 * width);
+ }
+
+ // Add the frame to the encoder
+ if (!WebPAnimEncoderAdd(enc, frame, timestamp, &config)) {
+ PyErr_SetString(PyExc_RuntimeError, WebPAnimEncoderGetError(enc));
+ return NULL;
+ }
+
+ Py_RETURN_NONE;
+}
+
+PyObject *
+_anim_encoder_assemble(PyObject *self, PyObject *args) {
+ uint8_t *icc_bytes;
+ uint8_t *exif_bytes;
+ uint8_t *xmp_bytes;
+ Py_ssize_t icc_size;
+ Py_ssize_t exif_size;
+ Py_ssize_t xmp_size;
+ WebPData webp_data;
+ WebPAnimEncoderObject *encp = (WebPAnimEncoderObject *)self;
+ WebPAnimEncoder *enc = encp->enc;
+ WebPMux *mux = NULL;
+ PyObject *ret = NULL;
+
+ if (!PyArg_ParseTuple(
+ args,
+ "s#s#s#",
+ &icc_bytes,
+ &icc_size,
+ &exif_bytes,
+ &exif_size,
+ &xmp_bytes,
+ &xmp_size)) {
+ return NULL;
+ }
+
+ // Init the output buffer
+ WebPDataInit(&webp_data);
+
+ // Assemble everything into the output buffer
+ if (!WebPAnimEncoderAssemble(enc, &webp_data)) {
+ PyErr_SetString(PyExc_RuntimeError, WebPAnimEncoderGetError(enc));
+ return NULL;
+ }
+
+ // Re-mux to add metadata as needed
+ if (icc_size > 0 || exif_size > 0 || xmp_size > 0) {
+ WebPMuxError err = WEBP_MUX_OK;
+ int i_icc_size = (int)icc_size;
+ int i_exif_size = (int)exif_size;
+ int i_xmp_size = (int)xmp_size;
+ WebPData icc_profile = {icc_bytes, i_icc_size};
+ WebPData exif = {exif_bytes, i_exif_size};
+ WebPData xmp = {xmp_bytes, i_xmp_size};
+
+ mux = WebPMuxCreate(&webp_data, 1);
+ if (mux == NULL) {
+ PyErr_SetString(PyExc_RuntimeError, "could not re-mux to add metadata");
+ return NULL;
+ }
+ WebPDataClear(&webp_data);
+
+ // Add ICCP chunk
+ if (i_icc_size > 0) {
+ err = WebPMuxSetChunk(mux, "ICCP", &icc_profile, 1);
+ if (err != WEBP_MUX_OK) {
+ return HandleMuxError(err, "ICCP");
+ }
+ }
+
+ // Add EXIF chunk
+ if (i_exif_size > 0) {
+ err = WebPMuxSetChunk(mux, "EXIF", &exif, 1);
+ if (err != WEBP_MUX_OK) {
+ return HandleMuxError(err, "EXIF");
+ }
+ }
+
+ // Add XMP chunk
+ if (i_xmp_size > 0) {
+ err = WebPMuxSetChunk(mux, "XMP ", &xmp, 1);
+ if (err != WEBP_MUX_OK) {
+ return HandleMuxError(err, "XMP");
+ }
+ }
+
+ err = WebPMuxAssemble(mux, &webp_data);
+ if (err != WEBP_MUX_OK) {
+ return HandleMuxError(err, NULL);
+ }
+ }
+
+ // Convert to Python bytes
+ ret = PyBytes_FromStringAndSize((char *)webp_data.bytes, webp_data.size);
+ WebPDataClear(&webp_data);
+
+ // If we had to re-mux, we should free it now that we're done with it
+ if (mux != NULL) {
+ WebPMuxDelete(mux);
+ }
+
+ return ret;
+}
+
+// Decoder functions
+PyObject *
+_anim_decoder_new(PyObject *self, PyObject *args) {
+ PyBytesObject *webp_string;
+ const uint8_t *webp;
+ Py_ssize_t size;
+ WebPData webp_src;
+ char *mode;
+ WebPDecoderConfig config;
+ WebPAnimDecoderObject *decp = NULL;
+ WebPAnimDecoder *dec = NULL;
+
+ if (!PyArg_ParseTuple(args, "S", &webp_string)) {
+ return NULL;
+ }
+ PyBytes_AsStringAndSize((PyObject *)webp_string, (char **)&webp, &size);
+ webp_src.bytes = webp;
+ webp_src.size = size;
+
+ // Sniff the mode, since the decoder API doesn't tell us
+ mode = "RGBA";
+ if (WebPGetFeatures(webp, size, &config.input) == VP8_STATUS_OK) {
+ if (!config.input.has_alpha) {
+ mode = "RGBX";
+ }
+ }
+
+ // Create the decoder (default mode is RGBA, if no options passed)
+ decp = PyObject_New(WebPAnimDecoderObject, &WebPAnimDecoder_Type);
+ if (decp) {
+ decp->mode = mode;
+ if (WebPDataCopy(&webp_src, &(decp->data))) {
+ dec = WebPAnimDecoderNew(&(decp->data), NULL);
+ if (dec) {
+ if (WebPAnimDecoderGetInfo(dec, &(decp->info))) {
+ decp->dec = dec;
+ return (PyObject *)decp;
+ }
+ }
+ WebPDataClear(&(decp->data));
+ }
+ PyObject_Del(decp);
+ }
+ PyErr_SetString(PyExc_OSError, "could not create decoder object");
+ return NULL;
+}
+
+void
+_anim_decoder_dealloc(PyObject *self) {
+ WebPAnimDecoderObject *decp = (WebPAnimDecoderObject *)self;
+ WebPDataClear(&(decp->data));
+ WebPAnimDecoderDelete(decp->dec);
+}
+
+PyObject *
+_anim_decoder_get_info(PyObject *self) {
+ WebPAnimDecoderObject *decp = (WebPAnimDecoderObject *)self;
+ WebPAnimInfo *info = &(decp->info);
+
+ return Py_BuildValue(
+ "IIIIIs",
+ info->canvas_width,
+ info->canvas_height,
+ info->loop_count,
+ info->bgcolor,
+ info->frame_count,
+ decp->mode);
+}
+
+PyObject *
+_anim_decoder_get_chunk(PyObject *self, PyObject *args) {
+ char *mode;
+ WebPAnimDecoderObject *decp = (WebPAnimDecoderObject *)self;
+ const WebPDemuxer *demux;
+ WebPChunkIterator iter;
+ PyObject *ret;
+
+ if (!PyArg_ParseTuple(args, "s", &mode)) {
+ return NULL;
+ }
+
+ demux = WebPAnimDecoderGetDemuxer(decp->dec);
+ if (!WebPDemuxGetChunk(demux, mode, 1, &iter)) {
+ Py_RETURN_NONE;
+ }
+
+ ret = PyBytes_FromStringAndSize((const char *)iter.chunk.bytes, iter.chunk.size);
+ WebPDemuxReleaseChunkIterator(&iter);
+
+ return ret;
+}
+
+PyObject *
+_anim_decoder_get_next(PyObject *self) {
+ uint8_t *buf;
+ int timestamp;
+ PyObject *bytes;
+ PyObject *ret;
+ WebPAnimDecoderObject *decp = (WebPAnimDecoderObject *)self;
+
+ if (!WebPAnimDecoderGetNext(decp->dec, &buf, &timestamp)) {
+ PyErr_SetString(PyExc_OSError, "failed to read next frame");
+ return NULL;
+ }
+
+ bytes = PyBytes_FromStringAndSize(
+ (char *)buf, decp->info.canvas_width * 4 * decp->info.canvas_height);
+
+ ret = Py_BuildValue("Si", bytes, timestamp);
+
+ Py_DECREF(bytes);
+ return ret;
+}
+
+PyObject *
+_anim_decoder_reset(PyObject *self) {
+ WebPAnimDecoderObject *decp = (WebPAnimDecoderObject *)self;
+ WebPAnimDecoderReset(decp->dec);
+ Py_RETURN_NONE;
+}
+
+/* -------------------------------------------------------------------- */
+/* Type Definitions */
+/* -------------------------------------------------------------------- */
+
+// WebPAnimEncoder methods
+static struct PyMethodDef _anim_encoder_methods[] = {
+ {"add", (PyCFunction)_anim_encoder_add, METH_VARARGS, "add"},
+ {"assemble", (PyCFunction)_anim_encoder_assemble, METH_VARARGS, "assemble"},
+ {NULL, NULL} /* sentinel */
+};
+
+// WebPAnimEncoder type definition
+static PyTypeObject WebPAnimEncoder_Type = {
+ PyVarObject_HEAD_INIT(NULL, 0) "WebPAnimEncoder", /*tp_name */
+ sizeof(WebPAnimEncoderObject), /*tp_basicsize */
+ 0, /*tp_itemsize */
+ /* methods */
+ (destructor)_anim_encoder_dealloc, /*tp_dealloc*/
+ 0, /*tp_vectorcall_offset*/
+ 0, /*tp_getattr*/
+ 0, /*tp_setattr*/
+ 0, /*tp_as_async*/
+ 0, /*tp_repr*/
+ 0, /*tp_as_number*/
+ 0, /*tp_as_sequence*/
+ 0, /*tp_as_mapping*/
+ 0, /*tp_hash*/
+ 0, /*tp_call*/
+ 0, /*tp_str*/
+ 0, /*tp_getattro*/
+ 0, /*tp_setattro*/
+ 0, /*tp_as_buffer*/
+ Py_TPFLAGS_DEFAULT, /*tp_flags*/
+ 0, /*tp_doc*/
+ 0, /*tp_traverse*/
+ 0, /*tp_clear*/
+ 0, /*tp_richcompare*/
+ 0, /*tp_weaklistoffset*/
+ 0, /*tp_iter*/
+ 0, /*tp_iternext*/
+ _anim_encoder_methods, /*tp_methods*/
+ 0, /*tp_members*/
+ 0, /*tp_getset*/
+};
+
+// WebPAnimDecoder methods
+static struct PyMethodDef _anim_decoder_methods[] = {
+ {"get_info", (PyCFunction)_anim_decoder_get_info, METH_NOARGS, "get_info"},
+ {"get_chunk", (PyCFunction)_anim_decoder_get_chunk, METH_VARARGS, "get_chunk"},
+ {"get_next", (PyCFunction)_anim_decoder_get_next, METH_NOARGS, "get_next"},
+ {"reset", (PyCFunction)_anim_decoder_reset, METH_NOARGS, "reset"},
+ {NULL, NULL} /* sentinel */
+};
+
+// WebPAnimDecoder type definition
+static PyTypeObject WebPAnimDecoder_Type = {
+ PyVarObject_HEAD_INIT(NULL, 0) "WebPAnimDecoder", /*tp_name */
+ sizeof(WebPAnimDecoderObject), /*tp_basicsize */
+ 0, /*tp_itemsize */
+ /* methods */
+ (destructor)_anim_decoder_dealloc, /*tp_dealloc*/
+ 0, /*tp_vectorcall_offset*/
+ 0, /*tp_getattr*/
+ 0, /*tp_setattr*/
+ 0, /*tp_as_async*/
+ 0, /*tp_repr*/
+ 0, /*tp_as_number*/
+ 0, /*tp_as_sequence*/
+ 0, /*tp_as_mapping*/
+ 0, /*tp_hash*/
+ 0, /*tp_call*/
+ 0, /*tp_str*/
+ 0, /*tp_getattro*/
+ 0, /*tp_setattro*/
+ 0, /*tp_as_buffer*/
+ Py_TPFLAGS_DEFAULT, /*tp_flags*/
+ 0, /*tp_doc*/
+ 0, /*tp_traverse*/
+ 0, /*tp_clear*/
+ 0, /*tp_richcompare*/
+ 0, /*tp_weaklistoffset*/
+ 0, /*tp_iter*/
+ 0, /*tp_iternext*/
+ _anim_decoder_methods, /*tp_methods*/
+ 0, /*tp_members*/
+ 0, /*tp_getset*/
+};
+
+#endif
+
+/* -------------------------------------------------------------------- */
+/* Legacy WebP Support */
+/* -------------------------------------------------------------------- */
+
+PyObject *
+WebPEncode_wrapper(PyObject *self, PyObject *args) {
+ int width;
+ int height;
+ int lossless;
+ float quality_factor;
+ int method;
+ int exact;
+ uint8_t *rgb;
+ uint8_t *icc_bytes;
+ uint8_t *exif_bytes;
+ uint8_t *xmp_bytes;
+ uint8_t *output;
+ char *mode;
+ Py_ssize_t size;
+ Py_ssize_t icc_size;
+ Py_ssize_t exif_size;
+ Py_ssize_t xmp_size;
+ size_t ret_size;
+ int rgba_mode;
+ int channels;
+ int ok;
+ ImagingSectionCookie cookie;
+ WebPConfig config;
+ WebPMemoryWriter writer;
+ WebPPicture pic;
+
+ if (!PyArg_ParseTuple(
+ args,
+ "y#iiifss#iis#s#",
+ (char **)&rgb,
+ &size,
+ &width,
+ &height,
+ &lossless,
+ &quality_factor,
+ &mode,
+ &icc_bytes,
+ &icc_size,
+ &method,
+ &exact,
+ &exif_bytes,
+ &exif_size,
+ &xmp_bytes,
+ &xmp_size)) {
+ return NULL;
+ }
+
+ rgba_mode = strcmp(mode, "RGBA") == 0;
+ if (!rgba_mode && strcmp(mode, "RGB") != 0) {
+ Py_RETURN_NONE;
+ }
+
+ channels = rgba_mode ? 4 : 3;
+ if (size < width * height * channels) {
+ Py_RETURN_NONE;
+ }
+
+ // Setup config for this frame
+ if (!WebPConfigInit(&config)) {
+ PyErr_SetString(PyExc_RuntimeError, "failed to initialize config!");
+ return NULL;
+ }
+ config.lossless = lossless;
+ config.quality = quality_factor;
+ config.method = method;
+#if WEBP_ENCODER_ABI_VERSION >= 0x0209
+ // the "exact" flag is only available in libwebp 0.5.0 and later
+ config.exact = exact;
+#endif
+
+ // Validate the config
+ if (!WebPValidateConfig(&config)) {
+ PyErr_SetString(PyExc_ValueError, "invalid configuration");
+ return NULL;
+ }
+
+ if (!WebPPictureInit(&pic)) {
+ PyErr_SetString(PyExc_ValueError, "could not initialise picture");
+ return NULL;
+ }
+ pic.width = width;
+ pic.height = height;
+ pic.use_argb = 1; // Don't convert RGB pixels to YUV
+
+ if (rgba_mode) {
+ WebPPictureImportRGBA(&pic, rgb, channels * width);
+ } else {
+ WebPPictureImportRGB(&pic, rgb, channels * width);
+ }
+
+ WebPMemoryWriterInit(&writer);
+ pic.writer = WebPMemoryWrite;
+ pic.custom_ptr = &writer;
+
+ ImagingSectionEnter(&cookie);
+ ok = WebPEncode(&config, &pic);
+ ImagingSectionLeave(&cookie);
+
+ WebPPictureFree(&pic);
+ if (!ok) {
+ PyErr_Format(PyExc_ValueError, "encoding error %d", (&pic)->error_code);
+ return NULL;
+ }
+ output = writer.mem;
+ ret_size = writer.size;
+
+#ifndef HAVE_WEBPMUX
+ if (ret_size > 0) {
+ PyObject *ret = PyBytes_FromStringAndSize((char *)output, ret_size);
+ free(output);
+ return ret;
+ }
+#else
+ {
+ /* I want to truncate the *_size items that get passed into WebP
+ data. Pypy2.1.0 had some issues where the Py_ssize_t items had
+ data in the upper byte. (Not sure why, it shouldn't have been there)
+ */
+ int i_icc_size = (int)icc_size;
+ int i_exif_size = (int)exif_size;
+ int i_xmp_size = (int)xmp_size;
+ WebPData output_data = {0};
+ WebPData image = {output, ret_size};
+ WebPData icc_profile = {icc_bytes, i_icc_size};
+ WebPData exif = {exif_bytes, i_exif_size};
+ WebPData xmp = {xmp_bytes, i_xmp_size};
+ WebPMuxError err;
+ int dbg = 0;
+
+ int copy_data = 0; // value 1 indicates given data WILL be copied to the mux
+ // and value 0 indicates data will NOT be copied.
+
+ WebPMux *mux = WebPMuxNew();
+ WebPMuxSetImage(mux, &image, copy_data);
+
+ if (dbg) {
+ /* was getting %ld icc_size == 0, icc_size>0 was true */
+ fprintf(stderr, "icc size %d, %d \n", i_icc_size, i_icc_size > 0);
+ }
+
+ if (i_icc_size > 0) {
+ if (dbg) {
+ fprintf(stderr, "Adding ICC Profile\n");
+ }
+ err = WebPMuxSetChunk(mux, "ICCP", &icc_profile, copy_data);
+ if (err != WEBP_MUX_OK) {
+ return HandleMuxError(err, "ICCP");
+ }
+ }
+
+ if (dbg) {
+ fprintf(stderr, "exif size %d \n", i_exif_size);
+ }
+ if (i_exif_size > 0) {
+ if (dbg) {
+ fprintf(stderr, "Adding Exif Data\n");
+ }
+ err = WebPMuxSetChunk(mux, "EXIF", &exif, copy_data);
+ if (err != WEBP_MUX_OK) {
+ return HandleMuxError(err, "EXIF");
+ }
+ }
+
+ if (dbg) {
+ fprintf(stderr, "xmp size %d \n", i_xmp_size);
+ }
+ if (i_xmp_size > 0) {
+ if (dbg) {
+ fprintf(stderr, "Adding XMP Data\n");
+ }
+ err = WebPMuxSetChunk(mux, "XMP ", &xmp, copy_data);
+ if (err != WEBP_MUX_OK) {
+ return HandleMuxError(err, "XMP ");
+ }
+ }
+
+ WebPMuxAssemble(mux, &output_data);
+ WebPMuxDelete(mux);
+ free(output);
+
+ ret_size = output_data.size;
+ if (ret_size > 0) {
+ PyObject *ret =
+ PyBytes_FromStringAndSize((char *)output_data.bytes, ret_size);
+ WebPDataClear(&output_data);
+ return ret;
+ }
+ }
+#endif
+ Py_RETURN_NONE;
+}
+
+PyObject *
+WebPDecode_wrapper(PyObject *self, PyObject *args) {
+ PyBytesObject *webp_string;
+ const uint8_t *webp;
+ Py_ssize_t size;
+ PyObject *ret = Py_None, *bytes = NULL, *pymode = NULL, *icc_profile = NULL,
+ *exif = NULL;
+ WebPDecoderConfig config;
+ VP8StatusCode vp8_status_code = VP8_STATUS_OK;
+ char *mode = "RGB";
+
+ if (!PyArg_ParseTuple(args, "S", &webp_string)) {
+ return NULL;
+ }
+
+ if (!WebPInitDecoderConfig(&config)) {
+ Py_RETURN_NONE;
+ }
+
+ PyBytes_AsStringAndSize((PyObject *)webp_string, (char **)&webp, &size);
+
+ vp8_status_code = WebPGetFeatures(webp, size, &config.input);
+ if (vp8_status_code == VP8_STATUS_OK) {
+ // If we don't set it, we don't get alpha.
+ // Initialized to MODE_RGB
+ if (config.input.has_alpha) {
+ config.output.colorspace = MODE_RGBA;
+ mode = "RGBA";
+ }
+
+#ifndef HAVE_WEBPMUX
+ vp8_status_code = WebPDecode(webp, size, &config);
+#else
+ {
+ int copy_data = 0;
+ WebPData data = {webp, size};
+ WebPMuxFrameInfo image;
+ WebPData icc_profile_data = {0};
+ WebPData exif_data = {0};
+
+ WebPMux *mux = WebPMuxCreate(&data, copy_data);
+ if (NULL == mux) {
+ goto end;
+ }
+
+ if (WEBP_MUX_OK != WebPMuxGetFrame(mux, 1, &image)) {
+ WebPMuxDelete(mux);
+ goto end;
+ }
+
+ webp = image.bitstream.bytes;
+ size = image.bitstream.size;
+
+ vp8_status_code = WebPDecode(webp, size, &config);
+
+ if (WEBP_MUX_OK == WebPMuxGetChunk(mux, "ICCP", &icc_profile_data)) {
+ icc_profile = PyBytes_FromStringAndSize(
+ (const char *)icc_profile_data.bytes, icc_profile_data.size);
+ }
+
+ if (WEBP_MUX_OK == WebPMuxGetChunk(mux, "EXIF", &exif_data)) {
+ exif = PyBytes_FromStringAndSize(
+ (const char *)exif_data.bytes, exif_data.size);
+ }
+
+ WebPDataClear(&image.bitstream);
+ WebPMuxDelete(mux);
+ }
+#endif
+ }
+
+ if (vp8_status_code != VP8_STATUS_OK) {
+ goto end;
+ }
+
+ if (config.output.colorspace < MODE_YUV) {
+ bytes = PyBytes_FromStringAndSize(
+ (char *)config.output.u.RGBA.rgba, config.output.u.RGBA.size);
+ } else {
+ // Skipping YUV for now. Need Test Images.
+ // UNDONE -- unclear if we'll ever get here if we set mode_rgb*
+ bytes = PyBytes_FromStringAndSize(
+ (char *)config.output.u.YUVA.y, config.output.u.YUVA.y_size);
+ }
+
+ pymode = PyUnicode_FromString(mode);
+ ret = Py_BuildValue(
+ "SiiSSS",
+ bytes,
+ config.output.width,
+ config.output.height,
+ pymode,
+ NULL == icc_profile ? Py_None : icc_profile,
+ NULL == exif ? Py_None : exif);
+
+end:
+ WebPFreeDecBuffer(&config.output);
+
+ Py_XDECREF(bytes);
+ Py_XDECREF(pymode);
+ Py_XDECREF(icc_profile);
+ Py_XDECREF(exif);
+
+ if (Py_None == ret) {
+ Py_RETURN_NONE;
+ }
+
+ return ret;
+}
+
+// Return the decoder's version number, packed in hexadecimal using 8bits for
+// each of major/minor/revision. E.g: v2.5.7 is 0x020507.
+PyObject *
+WebPDecoderVersion_wrapper() {
+ return Py_BuildValue("i", WebPGetDecoderVersion());
+}
+
+// Version as string
+const char *
+WebPDecoderVersion_str(void) {
+ static char version[20];
+ int version_number = WebPGetDecoderVersion();
+ sprintf(
+ version,
+ "%d.%d.%d",
+ version_number >> 16,
+ (version_number >> 8) % 0x100,
+ version_number % 0x100);
+ return version;
+}
+
+/*
+ * The version of webp that ships with (0.1.3) Ubuntu 12.04 doesn't handle alpha well.
+ * Files that are valid with 0.3 are reported as being invalid.
+ */
+int
+WebPDecoderBuggyAlpha(void) {
+ return WebPGetDecoderVersion() == 0x0103;
+}
+
+PyObject *
+WebPDecoderBuggyAlpha_wrapper() {
+ return Py_BuildValue("i", WebPDecoderBuggyAlpha());
+}
+
+/* -------------------------------------------------------------------- */
+/* Module Setup */
+/* -------------------------------------------------------------------- */
+
+static PyMethodDef webpMethods[] = {
+#ifdef HAVE_WEBPANIM
+ {"WebPAnimDecoder", _anim_decoder_new, METH_VARARGS, "WebPAnimDecoder"},
+ {"WebPAnimEncoder", _anim_encoder_new, METH_VARARGS, "WebPAnimEncoder"},
+#endif
+ {"WebPEncode", WebPEncode_wrapper, METH_VARARGS, "WebPEncode"},
+ {"WebPDecode", WebPDecode_wrapper, METH_VARARGS, "WebPDecode"},
+ {"WebPDecoderVersion", WebPDecoderVersion_wrapper, METH_NOARGS, "WebPVersion"},
+ {"WebPDecoderBuggyAlpha",
+ WebPDecoderBuggyAlpha_wrapper,
+ METH_NOARGS,
+ "WebPDecoderBuggyAlpha"},
+ {NULL, NULL}};
+
+void
+addMuxFlagToModule(PyObject *m) {
+ PyObject *have_webpmux;
+#ifdef HAVE_WEBPMUX
+ have_webpmux = Py_True;
+#else
+ have_webpmux = Py_False;
+#endif
+ Py_INCREF(have_webpmux);
+ PyModule_AddObject(m, "HAVE_WEBPMUX", have_webpmux);
+}
+
+void
+addAnimFlagToModule(PyObject *m) {
+ PyObject *have_webpanim;
+#ifdef HAVE_WEBPANIM
+ have_webpanim = Py_True;
+#else
+ have_webpanim = Py_False;
+#endif
+ Py_INCREF(have_webpanim);
+ PyModule_AddObject(m, "HAVE_WEBPANIM", have_webpanim);
+}
+
+void
+addTransparencyFlagToModule(PyObject *m) {
+ PyObject *have_transparency = PyBool_FromLong(!WebPDecoderBuggyAlpha());
+ if (PyModule_AddObject(m, "HAVE_TRANSPARENCY", have_transparency)) {
+ Py_DECREF(have_transparency);
+ }
+}
+
+static int
+setup_module(PyObject *m) {
+#ifdef HAVE_WEBPANIM
+ /* Ready object types */
+ if (PyType_Ready(&WebPAnimDecoder_Type) < 0 ||
+ PyType_Ready(&WebPAnimEncoder_Type) < 0) {
+ return -1;
+ }
+#endif
+ PyObject *d = PyModule_GetDict(m);
+ addMuxFlagToModule(m);
+ addAnimFlagToModule(m);
+ addTransparencyFlagToModule(m);
+
+ PyObject *v = PyUnicode_FromString(WebPDecoderVersion_str());
+ PyDict_SetItemString(d, "webpdecoder_version", v ? v : Py_None);
+ Py_XDECREF(v);
+
+ return 0;
+}
+
+PyMODINIT_FUNC
+PyInit__webp(void) {
+ PyObject *m;
+
+ static PyModuleDef module_def = {
+ PyModuleDef_HEAD_INIT,
+ "_webp", /* m_name */
+ NULL, /* m_doc */
+ -1, /* m_size */
+ webpMethods, /* m_methods */
+ };
+
+ m = PyModule_Create(&module_def);
+ if (setup_module(m) < 0) {
+ Py_DECREF(m);
+ return NULL;
+ }
+
+ return m;
+}
diff --git a/contrib/python/Pillow/py3/decode.c b/contrib/python/Pillow/py3/decode.c
new file mode 100644
index 00000000000..ea2f3af8012
--- /dev/null
+++ b/contrib/python/Pillow/py3/decode.c
@@ -0,0 +1,925 @@
+/*
+ * The Python Imaging Library.
+ *
+ * standard decoder interfaces for the Imaging library
+ *
+ * history:
+ * 1996-03-28 fl Moved from _imagingmodule.c
+ * 1996-04-15 fl Support subregions in setimage
+ * 1996-04-19 fl Allocate decoder buffer (where appropriate)
+ * 1996-05-02 fl Added jpeg decoder
+ * 1996-05-12 fl Compile cleanly as C++
+ * 1996-05-16 fl Added hex decoder
+ * 1996-05-26 fl Added jpeg configuration parameters
+ * 1996-12-14 fl Added zip decoder
+ * 1996-12-30 fl Plugged potential memory leak for tiled images
+ * 1997-01-03 fl Added fli and msp decoders
+ * 1997-01-04 fl Added sun_rle and tga_rle decoders
+ * 1997-05-31 fl Added bitfield decoder
+ * 1998-09-11 fl Added orientation and pixelsize fields to tga_rle decoder
+ * 1998-12-29 fl Added mode/rawmode argument to decoders
+ * 1998-12-30 fl Added mode argument to *all* decoders
+ * 2002-06-09 fl Added stride argument to pcx decoder
+ *
+ * Copyright (c) 1997-2002 by Secret Labs AB.
+ * Copyright (c) 1995-2002 by Fredrik Lundh.
+ *
+ * See the README file for information on usage and redistribution.
+ */
+
+/* FIXME: make these pluggable! */
+
+#define PY_SSIZE_T_CLEAN
+#include "Python.h"
+
+#include "libImaging/Imaging.h"
+
+#include "libImaging/Bit.h"
+#include "libImaging/Bcn.h"
+#include "libImaging/Gif.h"
+#include "libImaging/Raw.h"
+#include "libImaging/Sgi.h"
+
+/* -------------------------------------------------------------------- */
+/* Common */
+/* -------------------------------------------------------------------- */
+
+typedef struct {
+ PyObject_HEAD int (*decode)(
+ Imaging im, ImagingCodecState state, UINT8 *buffer, Py_ssize_t bytes);
+ int (*cleanup)(ImagingCodecState state);
+ struct ImagingCodecStateInstance state;
+ Imaging im;
+ PyObject *lock;
+ int pulls_fd;
+} ImagingDecoderObject;
+
+static PyTypeObject ImagingDecoderType;
+
+static ImagingDecoderObject *
+PyImaging_DecoderNew(int contextsize) {
+ ImagingDecoderObject *decoder;
+ void *context;
+
+ if (PyType_Ready(&ImagingDecoderType) < 0) {
+ return NULL;
+ }
+
+ decoder = PyObject_New(ImagingDecoderObject, &ImagingDecoderType);
+ if (decoder == NULL) {
+ return NULL;
+ }
+
+ /* Clear the decoder state */
+ memset(&decoder->state, 0, sizeof(decoder->state));
+
+ /* Allocate decoder context */
+ if (contextsize > 0) {
+ context = (void *)calloc(1, contextsize);
+ if (!context) {
+ Py_DECREF(decoder);
+ (void)ImagingError_MemoryError();
+ return NULL;
+ }
+ } else {
+ context = 0;
+ }
+
+ /* Initialize decoder context */
+ decoder->state.context = context;
+
+ /* Target image */
+ decoder->lock = NULL;
+ decoder->im = NULL;
+
+ /* Initialize the cleanup function pointer */
+ decoder->cleanup = NULL;
+
+ /* set if the decoder needs to pull data from the fd, instead of
+ having it pushed */
+ decoder->pulls_fd = 0;
+
+ return decoder;
+}
+
+static void
+_dealloc(ImagingDecoderObject *decoder) {
+ if (decoder->cleanup) {
+ decoder->cleanup(&decoder->state);
+ }
+ free(decoder->state.buffer);
+ free(decoder->state.context);
+ Py_XDECREF(decoder->lock);
+ Py_XDECREF(decoder->state.fd);
+ PyObject_Del(decoder);
+}
+
+static PyObject *
+_decode(ImagingDecoderObject *decoder, PyObject *args) {
+ Py_buffer buffer;
+ int status;
+ ImagingSectionCookie cookie;
+
+ if (!PyArg_ParseTuple(args, "y*", &buffer)) {
+ return NULL;
+ }
+
+ if (!decoder->pulls_fd) {
+ ImagingSectionEnter(&cookie);
+ }
+
+ status = decoder->decode(decoder->im, &decoder->state, buffer.buf, buffer.len);
+
+ if (!decoder->pulls_fd) {
+ ImagingSectionLeave(&cookie);
+ }
+
+ PyBuffer_Release(&buffer);
+ return Py_BuildValue("ii", status, decoder->state.errcode);
+}
+
+static PyObject *
+_decode_cleanup(ImagingDecoderObject *decoder, PyObject *args) {
+ int status = 0;
+
+ if (decoder->cleanup) {
+ status = decoder->cleanup(&decoder->state);
+ }
+
+ return Py_BuildValue("i", status);
+}
+
+extern Imaging
+PyImaging_AsImaging(PyObject *op);
+
+static PyObject *
+_setimage(ImagingDecoderObject *decoder, PyObject *args) {
+ PyObject *op;
+ Imaging im;
+ ImagingCodecState state;
+ int x0, y0, x1, y1;
+
+ x0 = y0 = x1 = y1 = 0;
+
+ /* FIXME: should publish the ImagingType descriptor */
+ if (!PyArg_ParseTuple(args, "O|(iiii)", &op, &x0, &y0, &x1, &y1)) {
+ return NULL;
+ }
+ im = PyImaging_AsImaging(op);
+ if (!im) {
+ return NULL;
+ }
+
+ decoder->im = im;
+
+ state = &decoder->state;
+
+ /* Setup decoding tile extent */
+ if (x0 == 0 && x1 == 0) {
+ state->xsize = im->xsize;
+ state->ysize = im->ysize;
+ } else {
+ state->xoff = x0;
+ state->yoff = y0;
+ state->xsize = x1 - x0;
+ state->ysize = y1 - y0;
+ }
+
+ if (state->xsize <= 0 || state->xsize + state->xoff > (int)im->xsize ||
+ state->ysize <= 0 || state->ysize + state->yoff > (int)im->ysize) {
+ PyErr_SetString(PyExc_ValueError, "tile cannot extend outside image");
+ return NULL;
+ }
+
+ /* Allocate memory buffer (if bits field is set) */
+ if (state->bits > 0) {
+ if (!state->bytes) {
+ if (state->xsize > ((INT_MAX / state->bits) - 7)) {
+ return ImagingError_MemoryError();
+ }
+ state->bytes = (state->bits * state->xsize + 7) / 8;
+ }
+ /* malloc check ok, overflow checked above */
+ state->buffer = (UINT8 *)calloc(1, state->bytes);
+ if (!state->buffer) {
+ return ImagingError_MemoryError();
+ }
+ }
+
+ /* Keep a reference to the image object, to make sure it doesn't
+ go away before we do */
+ Py_INCREF(op);
+ Py_XDECREF(decoder->lock);
+ decoder->lock = op;
+
+ Py_INCREF(Py_None);
+ return Py_None;
+}
+
+static PyObject *
+_setfd(ImagingDecoderObject *decoder, PyObject *args) {
+ PyObject *fd;
+ ImagingCodecState state;
+
+ if (!PyArg_ParseTuple(args, "O", &fd)) {
+ return NULL;
+ }
+
+ state = &decoder->state;
+
+ Py_XINCREF(fd);
+ state->fd = fd;
+
+ Py_INCREF(Py_None);
+ return Py_None;
+}
+
+static PyObject *
+_get_pulls_fd(ImagingDecoderObject *decoder, void *closure) {
+ return PyBool_FromLong(decoder->pulls_fd);
+}
+
+static struct PyMethodDef methods[] = {
+ {"decode", (PyCFunction)_decode, METH_VARARGS},
+ {"cleanup", (PyCFunction)_decode_cleanup, METH_VARARGS},
+ {"setimage", (PyCFunction)_setimage, METH_VARARGS},
+ {"setfd", (PyCFunction)_setfd, METH_VARARGS},
+ {NULL, NULL} /* sentinel */
+};
+
+static struct PyGetSetDef getseters[] = {
+ {"pulls_fd",
+ (getter)_get_pulls_fd,
+ NULL,
+ "True if this decoder expects to pull from self.fd itself.",
+ NULL},
+ {NULL, NULL, NULL, NULL, NULL} /* sentinel */
+};
+
+static PyTypeObject ImagingDecoderType = {
+ PyVarObject_HEAD_INIT(NULL, 0) "ImagingDecoder", /*tp_name*/
+ sizeof(ImagingDecoderObject), /*tp_basicsize*/
+ 0, /*tp_itemsize*/
+ /* methods */
+ (destructor)_dealloc, /*tp_dealloc*/
+ 0, /*tp_vectorcall_offset*/
+ 0, /*tp_getattr*/
+ 0, /*tp_setattr*/
+ 0, /*tp_as_async*/
+ 0, /*tp_repr*/
+ 0, /*tp_as_number*/
+ 0, /*tp_as_sequence*/
+ 0, /*tp_as_mapping*/
+ 0, /*tp_hash*/
+ 0, /*tp_call*/
+ 0, /*tp_str*/
+ 0, /*tp_getattro*/
+ 0, /*tp_setattro*/
+ 0, /*tp_as_buffer*/
+ Py_TPFLAGS_DEFAULT, /*tp_flags*/
+ 0, /*tp_doc*/
+ 0, /*tp_traverse*/
+ 0, /*tp_clear*/
+ 0, /*tp_richcompare*/
+ 0, /*tp_weaklistoffset*/
+ 0, /*tp_iter*/
+ 0, /*tp_iternext*/
+ methods, /*tp_methods*/
+ 0, /*tp_members*/
+ getseters, /*tp_getset*/
+};
+
+/* -------------------------------------------------------------------- */
+
+int
+get_unpacker(ImagingDecoderObject *decoder, const char *mode, const char *rawmode) {
+ int bits;
+ ImagingShuffler unpack;
+
+ unpack = ImagingFindUnpacker(mode, rawmode, &bits);
+ if (!unpack) {
+ Py_DECREF(decoder);
+ PyErr_SetString(PyExc_ValueError, "unknown raw mode for given image mode");
+ return -1;
+ }
+
+ decoder->state.shuffle = unpack;
+ decoder->state.bits = bits;
+
+ return 0;
+}
+
+/* -------------------------------------------------------------------- */
+/* BIT (packed fields) */
+/* -------------------------------------------------------------------- */
+
+PyObject *
+PyImaging_BitDecoderNew(PyObject *self, PyObject *args) {
+ ImagingDecoderObject *decoder;
+
+ char *mode;
+ int bits = 8;
+ int pad = 8;
+ int fill = 0;
+ int sign = 0;
+ int ystep = 1;
+ if (!PyArg_ParseTuple(args, "s|iiiii", &mode, &bits, &pad, &fill, &sign, &ystep)) {
+ return NULL;
+ }
+
+ if (strcmp(mode, "F") != 0) {
+ PyErr_SetString(PyExc_ValueError, "bad image mode");
+ return NULL;
+ }
+
+ decoder = PyImaging_DecoderNew(sizeof(BITSTATE));
+ if (decoder == NULL) {
+ return NULL;
+ }
+
+ decoder->decode = ImagingBitDecode;
+
+ decoder->state.ystep = ystep;
+
+ ((BITSTATE *)decoder->state.context)->bits = bits;
+ ((BITSTATE *)decoder->state.context)->pad = pad;
+ ((BITSTATE *)decoder->state.context)->fill = fill;
+ ((BITSTATE *)decoder->state.context)->sign = sign;
+
+ return (PyObject *)decoder;
+}
+
+/* -------------------------------------------------------------------- */
+/* BCn: GPU block-compressed texture formats */
+/* -------------------------------------------------------------------- */
+
+PyObject *
+PyImaging_BcnDecoderNew(PyObject *self, PyObject *args) {
+ ImagingDecoderObject *decoder;
+
+ char *mode;
+ char *actual;
+ int n = 0;
+ char *pixel_format = "";
+ if (!PyArg_ParseTuple(args, "si|s", &mode, &n, &pixel_format)) {
+ return NULL;
+ }
+
+ switch (n) {
+ case 1: /* BC1: 565 color, 1-bit alpha */
+ case 2: /* BC2: 565 color, 4-bit alpha */
+ case 3: /* BC3: 565 color, 2-endpoint 8-bit interpolated alpha */
+ case 7: /* BC7: 4-channel 8-bit via everything */
+ actual = "RGBA";
+ break;
+ case 4: /* BC4: 1-channel 8-bit via 1 BC3 alpha block */
+ actual = "L";
+ break;
+ case 5: /* BC5: 2-channel 8-bit via 2 BC3 alpha blocks */
+ case 6: /* BC6: 3-channel 16-bit float */
+ actual = "RGB";
+ break;
+ default:
+ PyErr_SetString(PyExc_ValueError, "block compression type unknown");
+ return NULL;
+ }
+
+ if (strcmp(mode, actual) != 0) {
+ PyErr_SetString(PyExc_ValueError, "bad image mode");
+ return NULL;
+ }
+
+ decoder = PyImaging_DecoderNew(sizeof(char *));
+ if (decoder == NULL) {
+ return NULL;
+ }
+
+ decoder->decode = ImagingBcnDecode;
+ decoder->state.state = n;
+ ((BCNSTATE *)decoder->state.context)->pixel_format = pixel_format;
+
+ return (PyObject *)decoder;
+}
+
+/* -------------------------------------------------------------------- */
+/* FLI */
+/* -------------------------------------------------------------------- */
+
+PyObject *
+PyImaging_FliDecoderNew(PyObject *self, PyObject *args) {
+ ImagingDecoderObject *decoder;
+
+ decoder = PyImaging_DecoderNew(0);
+ if (decoder == NULL) {
+ return NULL;
+ }
+
+ decoder->decode = ImagingFliDecode;
+
+ return (PyObject *)decoder;
+}
+
+/* -------------------------------------------------------------------- */
+/* GIF */
+/* -------------------------------------------------------------------- */
+
+PyObject *
+PyImaging_GifDecoderNew(PyObject *self, PyObject *args) {
+ ImagingDecoderObject *decoder;
+
+ char *mode;
+ int bits = 8;
+ int interlace = 0;
+ int transparency = -1;
+ if (!PyArg_ParseTuple(args, "s|iii", &mode, &bits, &interlace, &transparency)) {
+ return NULL;
+ }
+
+ if (strcmp(mode, "L") != 0 && strcmp(mode, "P") != 0) {
+ PyErr_SetString(PyExc_ValueError, "bad image mode");
+ return NULL;
+ }
+
+ decoder = PyImaging_DecoderNew(sizeof(GIFDECODERSTATE));
+ if (decoder == NULL) {
+ return NULL;
+ }
+
+ decoder->decode = ImagingGifDecode;
+
+ ((GIFDECODERSTATE *)decoder->state.context)->bits = bits;
+ ((GIFDECODERSTATE *)decoder->state.context)->interlace = interlace;
+ ((GIFDECODERSTATE *)decoder->state.context)->transparency = transparency;
+
+ return (PyObject *)decoder;
+}
+
+/* -------------------------------------------------------------------- */
+/* HEX */
+/* -------------------------------------------------------------------- */
+
+PyObject *
+PyImaging_HexDecoderNew(PyObject *self, PyObject *args) {
+ ImagingDecoderObject *decoder;
+
+ char *mode;
+ char *rawmode;
+ if (!PyArg_ParseTuple(args, "ss", &mode, &rawmode)) {
+ return NULL;
+ }
+
+ decoder = PyImaging_DecoderNew(0);
+ if (decoder == NULL) {
+ return NULL;
+ }
+
+ if (get_unpacker(decoder, mode, rawmode) < 0) {
+ return NULL;
+ }
+
+ decoder->decode = ImagingHexDecode;
+
+ return (PyObject *)decoder;
+}
+
+/* -------------------------------------------------------------------- */
+/* LibTiff */
+/* -------------------------------------------------------------------- */
+
+#ifdef HAVE_LIBTIFF
+
+#include "libImaging/TiffDecode.h"
+
+#include <string.h>
+
+PyObject *
+PyImaging_LibTiffDecoderNew(PyObject *self, PyObject *args) {
+ ImagingDecoderObject *decoder;
+ char *mode;
+ char *rawmode;
+ char *compname;
+ int fp;
+ uint32_t ifdoffset;
+
+ if (!PyArg_ParseTuple(args, "sssiI", &mode, &rawmode, &compname, &fp, &ifdoffset)) {
+ return NULL;
+ }
+
+ TRACE(("new tiff decoder %s\n", compname));
+
+ decoder = PyImaging_DecoderNew(sizeof(TIFFSTATE));
+ if (decoder == NULL) {
+ return NULL;
+ }
+
+ if (get_unpacker(decoder, mode, rawmode) < 0) {
+ return NULL;
+ }
+
+ if (!ImagingLibTiffInit(&decoder->state, fp, ifdoffset)) {
+ Py_DECREF(decoder);
+ PyErr_SetString(PyExc_RuntimeError, "tiff codec initialization failed");
+ return NULL;
+ }
+
+ decoder->decode = ImagingLibTiffDecode;
+
+ return (PyObject *)decoder;
+}
+
+#endif
+
+/* -------------------------------------------------------------------- */
+/* PackBits */
+/* -------------------------------------------------------------------- */
+
+PyObject *
+PyImaging_PackbitsDecoderNew(PyObject *self, PyObject *args) {
+ ImagingDecoderObject *decoder;
+
+ char *mode;
+ char *rawmode;
+ if (!PyArg_ParseTuple(args, "ss", &mode, &rawmode)) {
+ return NULL;
+ }
+
+ decoder = PyImaging_DecoderNew(0);
+ if (decoder == NULL) {
+ return NULL;
+ }
+
+ if (get_unpacker(decoder, mode, rawmode) < 0) {
+ return NULL;
+ }
+
+ decoder->decode = ImagingPackbitsDecode;
+
+ return (PyObject *)decoder;
+}
+
+/* -------------------------------------------------------------------- */
+/* PCD */
+/* -------------------------------------------------------------------- */
+
+PyObject *
+PyImaging_PcdDecoderNew(PyObject *self, PyObject *args) {
+ ImagingDecoderObject *decoder;
+
+ decoder = PyImaging_DecoderNew(0);
+ if (decoder == NULL) {
+ return NULL;
+ }
+
+ /* Unpack from PhotoYCC to RGB */
+ if (get_unpacker(decoder, "RGB", "YCC;P") < 0) {
+ return NULL;
+ }
+
+ decoder->decode = ImagingPcdDecode;
+
+ return (PyObject *)decoder;
+}
+
+/* -------------------------------------------------------------------- */
+/* PCX */
+/* -------------------------------------------------------------------- */
+
+PyObject *
+PyImaging_PcxDecoderNew(PyObject *self, PyObject *args) {
+ ImagingDecoderObject *decoder;
+
+ char *mode;
+ char *rawmode;
+ int stride;
+ if (!PyArg_ParseTuple(args, "ssi", &mode, &rawmode, &stride)) {
+ return NULL;
+ }
+
+ decoder = PyImaging_DecoderNew(0);
+ if (decoder == NULL) {
+ return NULL;
+ }
+
+ if (get_unpacker(decoder, mode, rawmode) < 0) {
+ return NULL;
+ }
+
+ decoder->state.bytes = stride;
+
+ decoder->decode = ImagingPcxDecode;
+
+ return (PyObject *)decoder;
+}
+
+/* -------------------------------------------------------------------- */
+/* RAW */
+/* -------------------------------------------------------------------- */
+
+PyObject *
+PyImaging_RawDecoderNew(PyObject *self, PyObject *args) {
+ ImagingDecoderObject *decoder;
+
+ char *mode;
+ char *rawmode;
+ int stride = 0;
+ int ystep = 1;
+ if (!PyArg_ParseTuple(args, "ss|ii", &mode, &rawmode, &stride, &ystep)) {
+ return NULL;
+ }
+
+ decoder = PyImaging_DecoderNew(sizeof(RAWSTATE));
+ if (decoder == NULL) {
+ return NULL;
+ }
+
+ if (get_unpacker(decoder, mode, rawmode) < 0) {
+ return NULL;
+ }
+
+ decoder->decode = ImagingRawDecode;
+
+ decoder->state.ystep = ystep;
+
+ ((RAWSTATE *)decoder->state.context)->stride = stride;
+
+ return (PyObject *)decoder;
+}
+
+/* -------------------------------------------------------------------- */
+/* SGI RLE */
+/* -------------------------------------------------------------------- */
+
+PyObject *
+PyImaging_SgiRleDecoderNew(PyObject *self, PyObject *args) {
+ ImagingDecoderObject *decoder;
+
+ char *mode;
+ char *rawmode;
+ int ystep = 1;
+ int bpc = 1;
+ if (!PyArg_ParseTuple(args, "ss|ii", &mode, &rawmode, &ystep, &bpc)) {
+ return NULL;
+ }
+
+ decoder = PyImaging_DecoderNew(sizeof(SGISTATE));
+ if (decoder == NULL) {
+ return NULL;
+ }
+
+ if (get_unpacker(decoder, mode, rawmode) < 0) {
+ return NULL;
+ }
+
+ decoder->pulls_fd = 1;
+ decoder->decode = ImagingSgiRleDecode;
+ decoder->state.ystep = ystep;
+
+ ((SGISTATE *)decoder->state.context)->bpc = bpc;
+
+ return (PyObject *)decoder;
+}
+
+/* -------------------------------------------------------------------- */
+/* SUN RLE */
+/* -------------------------------------------------------------------- */
+
+PyObject *
+PyImaging_SunRleDecoderNew(PyObject *self, PyObject *args) {
+ ImagingDecoderObject *decoder;
+
+ char *mode;
+ char *rawmode;
+ if (!PyArg_ParseTuple(args, "ss", &mode, &rawmode)) {
+ return NULL;
+ }
+
+ decoder = PyImaging_DecoderNew(0);
+ if (decoder == NULL) {
+ return NULL;
+ }
+
+ if (get_unpacker(decoder, mode, rawmode) < 0) {
+ return NULL;
+ }
+
+ decoder->decode = ImagingSunRleDecode;
+
+ return (PyObject *)decoder;
+}
+
+/* -------------------------------------------------------------------- */
+/* TGA RLE */
+/* -------------------------------------------------------------------- */
+
+PyObject *
+PyImaging_TgaRleDecoderNew(PyObject *self, PyObject *args) {
+ ImagingDecoderObject *decoder;
+
+ char *mode;
+ char *rawmode;
+ int ystep = 1;
+ int depth = 8;
+ if (!PyArg_ParseTuple(args, "ss|ii", &mode, &rawmode, &ystep, &depth)) {
+ return NULL;
+ }
+
+ decoder = PyImaging_DecoderNew(0);
+ if (decoder == NULL) {
+ return NULL;
+ }
+
+ if (get_unpacker(decoder, mode, rawmode) < 0) {
+ return NULL;
+ }
+
+ decoder->decode = ImagingTgaRleDecode;
+
+ decoder->state.ystep = ystep;
+ decoder->state.count = depth / 8;
+
+ return (PyObject *)decoder;
+}
+
+/* -------------------------------------------------------------------- */
+/* XBM */
+/* -------------------------------------------------------------------- */
+
+PyObject *
+PyImaging_XbmDecoderNew(PyObject *self, PyObject *args) {
+ ImagingDecoderObject *decoder;
+
+ decoder = PyImaging_DecoderNew(0);
+ if (decoder == NULL) {
+ return NULL;
+ }
+
+ if (get_unpacker(decoder, "1", "1;R") < 0) {
+ return NULL;
+ }
+
+ decoder->decode = ImagingXbmDecode;
+
+ return (PyObject *)decoder;
+}
+
+/* -------------------------------------------------------------------- */
+/* ZIP */
+/* -------------------------------------------------------------------- */
+
+#ifdef HAVE_LIBZ
+
+#include "libImaging/ZipCodecs.h"
+
+PyObject *
+PyImaging_ZipDecoderNew(PyObject *self, PyObject *args) {
+ ImagingDecoderObject *decoder;
+
+ char *mode;
+ char *rawmode;
+ int interlaced = 0;
+ if (!PyArg_ParseTuple(args, "ss|i", &mode, &rawmode, &interlaced)) {
+ return NULL;
+ }
+
+ decoder = PyImaging_DecoderNew(sizeof(ZIPSTATE));
+ if (decoder == NULL) {
+ return NULL;
+ }
+
+ if (get_unpacker(decoder, mode, rawmode) < 0) {
+ return NULL;
+ }
+
+ decoder->decode = ImagingZipDecode;
+ decoder->cleanup = ImagingZipDecodeCleanup;
+
+ ((ZIPSTATE *)decoder->state.context)->interlaced = interlaced;
+
+ return (PyObject *)decoder;
+}
+#endif
+
+/* -------------------------------------------------------------------- */
+/* JPEG */
+/* -------------------------------------------------------------------- */
+
+#ifdef HAVE_LIBJPEG
+
+/* We better define this decoder last in this file, so the following
+ undef's won't mess things up for the Imaging library proper. */
+
+#undef HAVE_PROTOTYPES
+#undef HAVE_STDDEF_H
+#undef HAVE_STDLIB_H
+#undef UINT8
+#undef UINT16
+#undef UINT32
+#undef INT8
+#undef INT16
+#undef INT32
+
+#include "libImaging/Jpeg.h"
+
+PyObject *
+PyImaging_JpegDecoderNew(PyObject *self, PyObject *args) {
+ ImagingDecoderObject *decoder;
+
+ char *mode;
+ char *rawmode; /* what we want from the decoder */
+ char *jpegmode; /* what's in the file */
+ int scale = 1;
+ int draft = 0;
+
+ if (!PyArg_ParseTuple(args, "ssz|ii", &mode, &rawmode, &jpegmode, &scale, &draft)) {
+ return NULL;
+ }
+
+ if (!jpegmode) {
+ jpegmode = "";
+ }
+
+ decoder = PyImaging_DecoderNew(sizeof(JPEGSTATE));
+ if (decoder == NULL) {
+ return NULL;
+ }
+
+ // libjpeg-turbo supports different output formats.
+ // We are choosing Pillow's native format (3 color bytes + 1 padding)
+ // to avoid extra conversion in Unpack.c.
+ if (ImagingJpegUseJCSExtensions() && strcmp(rawmode, "RGB") == 0) {
+ rawmode = "RGBX";
+ }
+
+ if (get_unpacker(decoder, mode, rawmode) < 0) {
+ return NULL;
+ }
+
+ decoder->decode = ImagingJpegDecode;
+ decoder->cleanup = ImagingJpegDecodeCleanup;
+
+ strncpy(((JPEGSTATE *)decoder->state.context)->rawmode, rawmode, 8);
+ strncpy(((JPEGSTATE *)decoder->state.context)->jpegmode, jpegmode, 8);
+
+ ((JPEGSTATE *)decoder->state.context)->scale = scale;
+ ((JPEGSTATE *)decoder->state.context)->draft = draft;
+
+ return (PyObject *)decoder;
+}
+#endif
+
+/* -------------------------------------------------------------------- */
+/* JPEG 2000 */
+/* -------------------------------------------------------------------- */
+
+#ifdef HAVE_OPENJPEG
+
+#include "libImaging/Jpeg2K.h"
+
+PyObject *
+PyImaging_Jpeg2KDecoderNew(PyObject *self, PyObject *args) {
+ ImagingDecoderObject *decoder;
+ JPEG2KDECODESTATE *context;
+
+ char *mode;
+ char *format;
+ OPJ_CODEC_FORMAT codec_format;
+ int reduce = 0;
+ int layers = 0;
+ int fd = -1;
+ PY_LONG_LONG length = -1;
+
+ if (!PyArg_ParseTuple(
+ args, "ss|iiiL", &mode, &format, &reduce, &layers, &fd, &length)) {
+ return NULL;
+ }
+
+ if (strcmp(format, "j2k") == 0) {
+ codec_format = OPJ_CODEC_J2K;
+ } else if (strcmp(format, "jpt") == 0) {
+ codec_format = OPJ_CODEC_JPT;
+ } else if (strcmp(format, "jp2") == 0) {
+ codec_format = OPJ_CODEC_JP2;
+ } else {
+ return NULL;
+ }
+
+ decoder = PyImaging_DecoderNew(sizeof(JPEG2KDECODESTATE));
+ if (decoder == NULL) {
+ return NULL;
+ }
+
+ decoder->pulls_fd = 1;
+ decoder->decode = ImagingJpeg2KDecode;
+ decoder->cleanup = ImagingJpeg2KDecodeCleanup;
+
+ context = (JPEG2KDECODESTATE *)decoder->state.context;
+
+ context->fd = fd;
+ context->length = (off_t)length;
+ context->format = codec_format;
+ context->reduce = reduce;
+ context->layers = layers;
+
+ return (PyObject *)decoder;
+}
+#endif /* HAVE_OPENJPEG */
diff --git a/contrib/python/Pillow/py3/display.c b/contrib/python/Pillow/py3/display.c
new file mode 100644
index 00000000000..e49d224a77c
--- /dev/null
+++ b/contrib/python/Pillow/py3/display.c
@@ -0,0 +1,916 @@
+/*
+ * The Python Imaging Library.
+ *
+ * display support (and other windows-related stuff)
+ *
+ * History:
+ * 1996-05-13 fl Windows DIB support
+ * 1996-05-21 fl Added palette stuff
+ * 1996-05-28 fl Added display_mode stuff
+ * 1997-09-21 fl Added draw primitive
+ * 2001-09-17 fl Added ImagingGrabScreen (from _grabscreen.c)
+ * 2002-05-12 fl Added ImagingListWindows
+ * 2002-11-19 fl Added clipboard support
+ * 2002-11-25 fl Added GetDC/ReleaseDC helpers
+ * 2003-05-21 fl Added create window support (including window callback)
+ * 2003-09-05 fl Added fromstring/tostring methods
+ * 2009-03-14 fl Added WMF support (from pilwmf)
+ *
+ * Copyright (c) 1997-2003 by Secret Labs AB.
+ * Copyright (c) 1996-1997 by Fredrik Lundh.
+ *
+ * See the README file for information on usage and redistribution.
+ */
+
+#define PY_SSIZE_T_CLEAN
+#include "Python.h"
+
+#include "libImaging/Imaging.h"
+
+/* -------------------------------------------------------------------- */
+/* Windows DIB support */
+
+#ifdef _WIN32
+
+#include "libImaging/ImDib.h"
+
+#if SIZEOF_VOID_P == 8
+#define F_HANDLE "K"
+#else
+#define F_HANDLE "k"
+#endif
+
+typedef struct {
+ PyObject_HEAD ImagingDIB dib;
+} ImagingDisplayObject;
+
+static PyTypeObject ImagingDisplayType;
+
+static ImagingDisplayObject *
+_new(const char *mode, int xsize, int ysize) {
+ ImagingDisplayObject *display;
+
+ if (PyType_Ready(&ImagingDisplayType) < 0) {
+ return NULL;
+ }
+
+ display = PyObject_New(ImagingDisplayObject, &ImagingDisplayType);
+ if (display == NULL) {
+ return NULL;
+ }
+
+ display->dib = ImagingNewDIB(mode, xsize, ysize);
+ if (!display->dib) {
+ Py_DECREF(display);
+ return NULL;
+ }
+
+ return display;
+}
+
+static void
+_delete(ImagingDisplayObject *display) {
+ if (display->dib) {
+ ImagingDeleteDIB(display->dib);
+ }
+ PyObject_Del(display);
+}
+
+static PyObject *
+_expose(ImagingDisplayObject *display, PyObject *args) {
+ HDC hdc;
+ if (!PyArg_ParseTuple(args, F_HANDLE, &hdc)) {
+ return NULL;
+ }
+
+ ImagingExposeDIB(display->dib, hdc);
+
+ Py_INCREF(Py_None);
+ return Py_None;
+}
+
+static PyObject *
+_draw(ImagingDisplayObject *display, PyObject *args) {
+ HDC hdc;
+ int dst[4];
+ int src[4];
+ if (!PyArg_ParseTuple(
+ args,
+ F_HANDLE "(iiii)(iiii)",
+ &hdc,
+ dst + 0,
+ dst + 1,
+ dst + 2,
+ dst + 3,
+ src + 0,
+ src + 1,
+ src + 2,
+ src + 3)) {
+ return NULL;
+ }
+
+ ImagingDrawDIB(display->dib, hdc, dst, src);
+
+ Py_INCREF(Py_None);
+ return Py_None;
+}
+
+extern Imaging
+PyImaging_AsImaging(PyObject *op);
+
+static PyObject *
+_paste(ImagingDisplayObject *display, PyObject *args) {
+ Imaging im;
+
+ PyObject *op;
+ int xy[4];
+ xy[0] = xy[1] = xy[2] = xy[3] = 0;
+ if (!PyArg_ParseTuple(args, "O|(iiii)", &op, xy + 0, xy + 1, xy + 2, xy + 3)) {
+ return NULL;
+ }
+ im = PyImaging_AsImaging(op);
+ if (!im) {
+ return NULL;
+ }
+
+ if (xy[2] <= xy[0]) {
+ xy[2] = xy[0] + im->xsize;
+ }
+ if (xy[3] <= xy[1]) {
+ xy[3] = xy[1] + im->ysize;
+ }
+
+ ImagingPasteDIB(display->dib, im, xy);
+
+ Py_INCREF(Py_None);
+ return Py_None;
+}
+
+static PyObject *
+_query_palette(ImagingDisplayObject *display, PyObject *args) {
+ HDC hdc;
+ int status;
+
+ if (!PyArg_ParseTuple(args, F_HANDLE, &hdc)) {
+ return NULL;
+ }
+
+ status = ImagingQueryPaletteDIB(display->dib, hdc);
+
+ return Py_BuildValue("i", status);
+}
+
+static PyObject *
+_getdc(ImagingDisplayObject *display, PyObject *args) {
+ HWND window;
+ HDC dc;
+
+ if (!PyArg_ParseTuple(args, F_HANDLE, &window)) {
+ return NULL;
+ }
+
+ dc = GetDC(window);
+ if (!dc) {
+ PyErr_SetString(PyExc_OSError, "cannot create dc");
+ return NULL;
+ }
+
+ return Py_BuildValue(F_HANDLE, dc);
+}
+
+static PyObject *
+_releasedc(ImagingDisplayObject *display, PyObject *args) {
+ HWND window;
+ HDC dc;
+
+ if (!PyArg_ParseTuple(args, F_HANDLE F_HANDLE, &window, &dc)) {
+ return NULL;
+ }
+
+ ReleaseDC(window, dc);
+
+ Py_INCREF(Py_None);
+ return Py_None;
+}
+
+static PyObject *
+_frombytes(ImagingDisplayObject *display, PyObject *args) {
+ Py_buffer buffer;
+
+ if (!PyArg_ParseTuple(args, "y*:frombytes", &buffer)) {
+ return NULL;
+ }
+
+ if (display->dib->ysize * display->dib->linesize != buffer.len) {
+ PyBuffer_Release(&buffer);
+ PyErr_SetString(PyExc_ValueError, "wrong size");
+ return NULL;
+ }
+
+ memcpy(display->dib->bits, buffer.buf, buffer.len);
+
+ PyBuffer_Release(&buffer);
+ Py_INCREF(Py_None);
+ return Py_None;
+}
+
+static PyObject *
+_tobytes(ImagingDisplayObject *display, PyObject *args) {
+ if (!PyArg_ParseTuple(args, ":tobytes")) {
+ return NULL;
+ }
+
+ return PyBytes_FromStringAndSize(
+ display->dib->bits, display->dib->ysize * display->dib->linesize);
+}
+
+static struct PyMethodDef methods[] = {
+ {"draw", (PyCFunction)_draw, METH_VARARGS},
+ {"expose", (PyCFunction)_expose, METH_VARARGS},
+ {"paste", (PyCFunction)_paste, METH_VARARGS},
+ {"query_palette", (PyCFunction)_query_palette, METH_VARARGS},
+ {"getdc", (PyCFunction)_getdc, METH_VARARGS},
+ {"releasedc", (PyCFunction)_releasedc, METH_VARARGS},
+ {"frombytes", (PyCFunction)_frombytes, METH_VARARGS},
+ {"tobytes", (PyCFunction)_tobytes, METH_VARARGS},
+ {NULL, NULL} /* sentinel */
+};
+
+static PyObject *
+_getattr_mode(ImagingDisplayObject *self, void *closure) {
+ return Py_BuildValue("s", self->dib->mode);
+}
+
+static PyObject *
+_getattr_size(ImagingDisplayObject *self, void *closure) {
+ return Py_BuildValue("ii", self->dib->xsize, self->dib->ysize);
+}
+
+static struct PyGetSetDef getsetters[] = {
+ {"mode", (getter)_getattr_mode}, {"size", (getter)_getattr_size}, {NULL}};
+
+static PyTypeObject ImagingDisplayType = {
+ PyVarObject_HEAD_INIT(NULL, 0) "ImagingDisplay", /*tp_name*/
+ sizeof(ImagingDisplayObject), /*tp_basicsize*/
+ 0, /*tp_itemsize*/
+ /* methods */
+ (destructor)_delete, /*tp_dealloc*/
+ 0, /*tp_vectorcall_offset*/
+ 0, /*tp_getattr*/
+ 0, /*tp_setattr*/
+ 0, /*tp_as_async*/
+ 0, /*tp_repr*/
+ 0, /*tp_as_number*/
+ 0, /*tp_as_sequence*/
+ 0, /*tp_as_mapping*/
+ 0, /*tp_hash*/
+ 0, /*tp_call*/
+ 0, /*tp_str*/
+ 0, /*tp_getattro*/
+ 0, /*tp_setattro*/
+ 0, /*tp_as_buffer*/
+ Py_TPFLAGS_DEFAULT, /*tp_flags*/
+ 0, /*tp_doc*/
+ 0, /*tp_traverse*/
+ 0, /*tp_clear*/
+ 0, /*tp_richcompare*/
+ 0, /*tp_weaklistoffset*/
+ 0, /*tp_iter*/
+ 0, /*tp_iternext*/
+ methods, /*tp_methods*/
+ 0, /*tp_members*/
+ getsetters, /*tp_getset*/
+};
+
+PyObject *
+PyImaging_DisplayWin32(PyObject *self, PyObject *args) {
+ ImagingDisplayObject *display;
+ char *mode;
+ int xsize, ysize;
+
+ if (!PyArg_ParseTuple(args, "s(ii)", &mode, &xsize, &ysize)) {
+ return NULL;
+ }
+
+ display = _new(mode, xsize, ysize);
+ if (display == NULL) {
+ return NULL;
+ }
+
+ return (PyObject *)display;
+}
+
+PyObject *
+PyImaging_DisplayModeWin32(PyObject *self, PyObject *args) {
+ char *mode;
+ int size[2];
+
+ mode = ImagingGetModeDIB(size);
+
+ return Py_BuildValue("s(ii)", mode, size[0], size[1]);
+}
+
+/* -------------------------------------------------------------------- */
+/* Windows screen grabber */
+
+typedef HANDLE(__stdcall *Func_SetThreadDpiAwarenessContext)(HANDLE);
+
+PyObject *
+PyImaging_GrabScreenWin32(PyObject *self, PyObject *args) {
+ int x = 0, y = 0, width, height;
+ int includeLayeredWindows = 0, all_screens = 0;
+ HBITMAP bitmap;
+ BITMAPCOREHEADER core;
+ HDC screen, screen_copy;
+ DWORD rop;
+ PyObject *buffer;
+ HANDLE dpiAwareness;
+ HMODULE user32;
+ Func_SetThreadDpiAwarenessContext SetThreadDpiAwarenessContext_function;
+
+ if (!PyArg_ParseTuple(args, "|ii", &includeLayeredWindows, &all_screens)) {
+ return NULL;
+ }
+
+ /* step 1: create a memory DC large enough to hold the
+ entire screen */
+
+ screen = CreateDC("DISPLAY", NULL, NULL, NULL);
+ screen_copy = CreateCompatibleDC(screen);
+
+ // added in Windows 10 (1607)
+ // loaded dynamically to avoid link errors
+ user32 = LoadLibraryA("User32.dll");
+ SetThreadDpiAwarenessContext_function =
+ (Func_SetThreadDpiAwarenessContext)GetProcAddress(
+ user32, "SetThreadDpiAwarenessContext");
+ if (SetThreadDpiAwarenessContext_function != NULL) {
+ // DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE = ((DPI_CONTEXT_HANDLE)-3)
+ dpiAwareness = SetThreadDpiAwarenessContext_function((HANDLE)-3);
+ }
+
+ if (all_screens) {
+ x = GetSystemMetrics(SM_XVIRTUALSCREEN);
+ y = GetSystemMetrics(SM_YVIRTUALSCREEN);
+ width = GetSystemMetrics(SM_CXVIRTUALSCREEN);
+ height = GetSystemMetrics(SM_CYVIRTUALSCREEN);
+ } else {
+ width = GetDeviceCaps(screen, HORZRES);
+ height = GetDeviceCaps(screen, VERTRES);
+ }
+
+ if (SetThreadDpiAwarenessContext_function != NULL) {
+ SetThreadDpiAwarenessContext_function(dpiAwareness);
+ }
+
+ FreeLibrary(user32);
+
+ bitmap = CreateCompatibleBitmap(screen, width, height);
+ if (!bitmap) {
+ goto error;
+ }
+
+ if (!SelectObject(screen_copy, bitmap)) {
+ goto error;
+ }
+
+ /* step 2: copy bits into memory DC bitmap */
+
+ rop = SRCCOPY;
+ if (includeLayeredWindows) {
+ rop |= CAPTUREBLT;
+ }
+ if (!BitBlt(screen_copy, 0, 0, width, height, screen, x, y, rop)) {
+ goto error;
+ }
+
+ /* step 3: extract bits from bitmap */
+
+ buffer = PyBytes_FromStringAndSize(NULL, height * ((width * 3 + 3) & -4));
+ if (!buffer) {
+ return NULL;
+ }
+
+ core.bcSize = sizeof(core);
+ core.bcWidth = width;
+ core.bcHeight = height;
+ core.bcPlanes = 1;
+ core.bcBitCount = 24;
+ if (!GetDIBits(
+ screen_copy,
+ bitmap,
+ 0,
+ height,
+ PyBytes_AS_STRING(buffer),
+ (BITMAPINFO *)&core,
+ DIB_RGB_COLORS)) {
+ goto error;
+ }
+
+ DeleteObject(bitmap);
+ DeleteDC(screen_copy);
+ DeleteDC(screen);
+
+ return Py_BuildValue("(ii)(ii)N", x, y, width, height, buffer);
+
+error:
+ PyErr_SetString(PyExc_OSError, "screen grab failed");
+
+ DeleteDC(screen_copy);
+ DeleteDC(screen);
+
+ return NULL;
+}
+
+/* -------------------------------------------------------------------- */
+/* Windows clipboard grabber */
+
+PyObject *
+PyImaging_GrabClipboardWin32(PyObject *self, PyObject *args) {
+ int clip;
+ HANDLE handle = NULL;
+ int size;
+ void *data;
+ PyObject *result;
+ UINT format;
+ UINT formats[] = {CF_DIB, CF_DIBV5, CF_HDROP, RegisterClipboardFormatA("PNG"), 0};
+ LPCSTR format_names[] = {"DIB", "DIB", "file", "png", NULL};
+
+ if (!OpenClipboard(NULL)) {
+ // Maybe the clipboard is temporarily in use by another process.
+ // Wait and try again
+ Sleep(500);
+
+ if (!OpenClipboard(NULL)) {
+ PyErr_SetString(PyExc_OSError, "failed to open clipboard");
+ return NULL;
+ }
+ }
+
+ // find best format as set by clipboard owner
+ format = 0;
+ while (!handle && (format = EnumClipboardFormats(format))) {
+ for (UINT i = 0; formats[i] != 0; i++) {
+ if (format == formats[i]) {
+ handle = GetClipboardData(format);
+ format = i;
+ break;
+ }
+ }
+ }
+
+ if (!handle) {
+ CloseClipboard();
+ return Py_BuildValue("zO", NULL, Py_None);
+ }
+
+ data = GlobalLock(handle);
+ size = GlobalSize(handle);
+
+ result = PyBytes_FromStringAndSize(data, size);
+
+ GlobalUnlock(handle);
+ CloseClipboard();
+
+ return Py_BuildValue("zN", format_names[format], result);
+}
+
+/* -------------------------------------------------------------------- */
+/* Windows class */
+
+#ifndef WM_MOUSEWHEEL
+#define WM_MOUSEWHEEL 522
+#endif
+
+static int mainloop = 0;
+
+static void
+callback_error(const char *handler) {
+ PyObject *sys_stderr;
+
+ sys_stderr = PySys_GetObject("stderr");
+
+ if (sys_stderr) {
+ PyFile_WriteString("*** ImageWin: error in ", sys_stderr);
+ PyFile_WriteString((char *)handler, sys_stderr);
+ PyFile_WriteString(":\n", sys_stderr);
+ }
+
+ PyErr_Print();
+ PyErr_Clear();
+}
+
+static LRESULT CALLBACK
+windowCallback(HWND wnd, UINT message, WPARAM wParam, LPARAM lParam) {
+ PAINTSTRUCT ps;
+ PyObject *callback = NULL;
+ PyObject *result;
+ PyThreadState *threadstate;
+ PyThreadState *current_threadstate;
+ HDC dc;
+ RECT rect;
+ LRESULT status = 0;
+
+ /* set up threadstate for messages that calls back into python */
+ switch (message) {
+ case WM_CREATE:
+ mainloop++;
+ break;
+ case WM_DESTROY:
+ mainloop--;
+ /* fall through... */
+ case WM_PAINT:
+ case WM_SIZE:
+ callback = (PyObject *)GetWindowLongPtr(wnd, 0);
+ if (callback) {
+ threadstate =
+ (PyThreadState *)GetWindowLongPtr(wnd, sizeof(PyObject *));
+ current_threadstate = PyThreadState_Swap(NULL);
+ PyEval_RestoreThread(threadstate);
+ } else {
+ return DefWindowProc(wnd, message, wParam, lParam);
+ }
+ }
+
+ /* process message */
+ switch (message) {
+ case WM_PAINT:
+ /* redraw (part of) window. this generates a WCK-style
+ damage/clear/repair cascade */
+ BeginPaint(wnd, &ps);
+ dc = GetDC(wnd);
+ GetWindowRect(wnd, &rect); /* in screen coordinates */
+
+ result = PyObject_CallFunction(
+ callback,
+ "siiii",
+ "damage",
+ ps.rcPaint.left,
+ ps.rcPaint.top,
+ ps.rcPaint.right,
+ ps.rcPaint.bottom);
+ if (result) {
+ Py_DECREF(result);
+ } else {
+ callback_error("window damage callback");
+ }
+
+ result = PyObject_CallFunction(
+ callback,
+ "s" F_HANDLE "iiii",
+ "clear",
+ dc,
+ 0,
+ 0,
+ rect.right - rect.left,
+ rect.bottom - rect.top);
+ if (result) {
+ Py_DECREF(result);
+ } else {
+ callback_error("window clear callback");
+ }
+
+ result = PyObject_CallFunction(
+ callback,
+ "s" F_HANDLE "iiii",
+ "repair",
+ dc,
+ 0,
+ 0,
+ rect.right - rect.left,
+ rect.bottom - rect.top);
+ if (result) {
+ Py_DECREF(result);
+ } else {
+ callback_error("window repair callback");
+ }
+
+ ReleaseDC(wnd, dc);
+ EndPaint(wnd, &ps);
+ break;
+
+ case WM_SIZE:
+ /* resize window */
+ result = PyObject_CallFunction(
+ callback, "sii", "resize", LOWORD(lParam), HIWORD(lParam));
+ if (result) {
+ InvalidateRect(wnd, NULL, 1);
+ Py_DECREF(result);
+ } else {
+ callback_error("window resize callback");
+ }
+ break;
+
+ case WM_DESTROY:
+ /* destroy window */
+ result = PyObject_CallFunction(callback, "s", "destroy");
+ if (result) {
+ Py_DECREF(result);
+ } else {
+ callback_error("window destroy callback");
+ }
+ Py_DECREF(callback);
+ break;
+
+ default:
+ status = DefWindowProc(wnd, message, wParam, lParam);
+ }
+
+ if (callback) {
+ /* restore thread state */
+ PyEval_SaveThread();
+ PyThreadState_Swap(threadstate);
+ }
+
+ return status;
+}
+
+PyObject *
+PyImaging_CreateWindowWin32(PyObject *self, PyObject *args) {
+ HWND wnd;
+ WNDCLASS windowClass;
+
+ char *title;
+ PyObject *callback;
+ int width = 0, height = 0;
+ if (!PyArg_ParseTuple(args, "sO|ii", &title, &callback, &width, &height)) {
+ return NULL;
+ }
+
+ if (width <= 0) {
+ width = CW_USEDEFAULT;
+ }
+ if (height <= 0) {
+ height = CW_USEDEFAULT;
+ }
+
+ /* register toplevel window class */
+ windowClass.style = CS_CLASSDC;
+ windowClass.cbClsExtra = 0;
+ windowClass.cbWndExtra = sizeof(PyObject *) + sizeof(PyThreadState *);
+ windowClass.hInstance = GetModuleHandle(NULL);
+ /* windowClass.hbrBackground = (HBRUSH) (COLOR_BTNFACE + 1); */
+ windowClass.hbrBackground = NULL;
+ windowClass.lpszMenuName = NULL;
+ windowClass.lpszClassName = "pilWindow";
+ windowClass.lpfnWndProc = windowCallback;
+ windowClass.hIcon = LoadIcon(GetModuleHandle(NULL), MAKEINTRESOURCE(1));
+ windowClass.hCursor = LoadCursor(NULL, IDC_ARROW); /* CROSS? */
+
+ RegisterClass(&windowClass); /* FIXME: check return status */
+
+ wnd = CreateWindowEx(
+ 0,
+ windowClass.lpszClassName,
+ title,
+ WS_OVERLAPPEDWINDOW,
+ CW_USEDEFAULT,
+ CW_USEDEFAULT,
+ width,
+ height,
+ HWND_DESKTOP,
+ NULL,
+ NULL,
+ NULL);
+
+ if (!wnd) {
+ PyErr_SetString(PyExc_OSError, "failed to create window");
+ return NULL;
+ }
+
+ /* register window callback */
+ Py_INCREF(callback);
+ SetWindowLongPtr(wnd, 0, (LONG_PTR)callback);
+ SetWindowLongPtr(wnd, sizeof(callback), (LONG_PTR)PyThreadState_Get());
+
+ Py_BEGIN_ALLOW_THREADS ShowWindow(wnd, SW_SHOWNORMAL);
+ SetForegroundWindow(wnd); /* to make sure it's visible */
+ Py_END_ALLOW_THREADS
+
+ return Py_BuildValue(F_HANDLE, wnd);
+}
+
+PyObject *
+PyImaging_EventLoopWin32(PyObject *self, PyObject *args) {
+ MSG msg;
+
+ Py_BEGIN_ALLOW_THREADS while (mainloop && GetMessage(&msg, NULL, 0, 0)) {
+ TranslateMessage(&msg);
+ DispatchMessage(&msg);
+ }
+ Py_END_ALLOW_THREADS
+
+ Py_INCREF(Py_None);
+ return Py_None;
+}
+
+/* -------------------------------------------------------------------- */
+/* windows WMF renderer */
+
+#define GET32(p, o) ((DWORD *)(p + o))[0]
+
+PyObject *
+PyImaging_DrawWmf(PyObject *self, PyObject *args) {
+ HBITMAP bitmap;
+ HENHMETAFILE meta;
+ BITMAPCOREHEADER core;
+ HDC dc;
+ RECT rect;
+ PyObject *buffer = NULL;
+ char *ptr;
+
+ char *data;
+ Py_ssize_t datasize;
+ int width, height;
+ int x0, y0, x1, y1;
+ if (!PyArg_ParseTuple(
+ args,
+ "y#(ii)(iiii):_load",
+ &data,
+ &datasize,
+ &width,
+ &height,
+ &x0,
+ &x1,
+ &y0,
+ &y1)) {
+ return NULL;
+ }
+
+ /* step 1: copy metafile contents into METAFILE object */
+
+ if (datasize > 22 && GET32(data, 0) == 0x9ac6cdd7) {
+ /* placeable windows metafile (22-byte aldus header) */
+ meta = SetWinMetaFileBits(datasize - 22, data + 22, NULL, NULL);
+
+ } else if (datasize > 80 && GET32(data, 0) == 1 && GET32(data, 40) == 0x464d4520) {
+ /* enhanced metafile */
+ meta = SetEnhMetaFileBits(datasize, data);
+
+ } else {
+ /* unknown meta format */
+ meta = NULL;
+ }
+
+ if (!meta) {
+ PyErr_SetString(PyExc_OSError, "cannot load metafile");
+ return NULL;
+ }
+
+ /* step 2: create bitmap */
+
+ core.bcSize = sizeof(core);
+ core.bcWidth = width;
+ core.bcHeight = height;
+ core.bcPlanes = 1;
+ core.bcBitCount = 24;
+
+ dc = CreateCompatibleDC(NULL);
+
+ bitmap = CreateDIBSection(dc, (BITMAPINFO *)&core, DIB_RGB_COLORS, &ptr, NULL, 0);
+
+ if (!bitmap) {
+ PyErr_SetString(PyExc_OSError, "cannot create bitmap");
+ goto error;
+ }
+
+ if (!SelectObject(dc, bitmap)) {
+ PyErr_SetString(PyExc_OSError, "cannot select bitmap");
+ goto error;
+ }
+
+ /* step 3: render metafile into bitmap */
+
+ rect.left = rect.top = 0;
+ rect.right = width;
+ rect.bottom = height;
+
+ /* FIXME: make background transparent? configurable? */
+ FillRect(dc, &rect, GetStockObject(WHITE_BRUSH));
+
+ if (!PlayEnhMetaFile(dc, meta, &rect)) {
+ PyErr_SetString(PyExc_OSError, "cannot render metafile");
+ goto error;
+ }
+
+ /* step 4: extract bits from bitmap */
+
+ GdiFlush();
+
+ buffer = PyBytes_FromStringAndSize(ptr, height * ((width * 3 + 3) & -4));
+
+error:
+ DeleteEnhMetaFile(meta);
+
+ if (bitmap) {
+ DeleteObject(bitmap);
+ }
+
+ DeleteDC(dc);
+
+ return buffer;
+}
+
+#endif /* _WIN32 */
+
+/* -------------------------------------------------------------------- */
+/* X11 support */
+
+#ifdef HAVE_XCB
+#error #include <xcb/xcb.h>
+
+/* -------------------------------------------------------------------- */
+/* X11 screen grabber */
+
+PyObject *
+PyImaging_GrabScreenX11(PyObject *self, PyObject *args) {
+ int width, height;
+ char *display_name;
+ xcb_connection_t *connection;
+ int screen_number;
+ xcb_screen_iterator_t iter;
+ xcb_screen_t *screen = NULL;
+ xcb_get_image_reply_t *reply;
+ xcb_generic_error_t *error;
+ PyObject *buffer = NULL;
+
+ if (!PyArg_ParseTuple(args, "|z", &display_name)) {
+ return NULL;
+ }
+
+ /* connect to X and get screen data */
+
+ connection = xcb_connect(display_name, &screen_number);
+ if (xcb_connection_has_error(connection)) {
+ PyErr_Format(
+ PyExc_OSError,
+ "X connection failed: error %i",
+ xcb_connection_has_error(connection));
+ xcb_disconnect(connection);
+ return NULL;
+ }
+
+ iter = xcb_setup_roots_iterator(xcb_get_setup(connection));
+ for (; iter.rem; --screen_number, xcb_screen_next(&iter)) {
+ if (screen_number == 0) {
+ screen = iter.data;
+ break;
+ }
+ }
+ if (screen == NULL || screen->root == 0) {
+ // this case is usually caught with "X connection failed: error 6" above
+ xcb_disconnect(connection);
+ PyErr_SetString(PyExc_OSError, "X screen not found");
+ return NULL;
+ }
+
+ width = screen->width_in_pixels;
+ height = screen->height_in_pixels;
+
+ /* get image data */
+
+ reply = xcb_get_image_reply(
+ connection,
+ xcb_get_image(
+ connection,
+ XCB_IMAGE_FORMAT_Z_PIXMAP,
+ screen->root,
+ 0,
+ 0,
+ width,
+ height,
+ 0x00ffffff),
+ &error);
+ if (reply == NULL) {
+ PyErr_Format(
+ PyExc_OSError,
+ "X get_image failed: error %i (%i, %i, %i)",
+ error->error_code,
+ error->major_code,
+ error->minor_code,
+ error->resource_id);
+ free(error);
+ xcb_disconnect(connection);
+ return NULL;
+ }
+
+ /* store data in Python buffer */
+
+ if (reply->depth == 24) {
+ buffer = PyBytes_FromStringAndSize(
+ (char *)xcb_get_image_data(reply), xcb_get_image_data_length(reply));
+ } else {
+ PyErr_Format(PyExc_OSError, "unsupported bit depth: %i", reply->depth);
+ }
+
+ free(reply);
+ xcb_disconnect(connection);
+
+ if (!buffer) {
+ return NULL;
+ }
+
+ return Py_BuildValue("(ii)N", width, height, buffer);
+}
+
+#endif /* HAVE_XCB */
diff --git a/contrib/python/Pillow/py3/encode.c b/contrib/python/Pillow/py3/encode.c
new file mode 100644
index 00000000000..08544aedeb0
--- /dev/null
+++ b/contrib/python/Pillow/py3/encode.c
@@ -0,0 +1,1373 @@
+/*
+ * The Python Imaging Library.
+ *
+ * standard encoder interfaces for the Imaging library
+ *
+ * History:
+ * 1996-04-19 fl Based on decoders.c
+ * 1996-05-12 fl Compile cleanly as C++
+ * 1996-12-30 fl Plugged potential memory leak for tiled images
+ * 1997-01-03 fl Added GIF encoder
+ * 1997-01-05 fl Plugged encoder buffer leaks
+ * 1997-01-11 fl Added encode_to_file method
+ * 1998-03-09 fl Added mode/rawmode argument to encoders
+ * 1998-07-09 fl Added interlace argument to GIF encoder
+ * 1999-02-07 fl Added PCX encoder
+ *
+ * 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.
+ */
+
+/* FIXME: make these pluggable! */
+
+#define PY_SSIZE_T_CLEAN
+#include "Python.h"
+
+#include "libImaging/Imaging.h"
+#include "libImaging/Gif.h"
+
+#ifdef HAVE_UNISTD_H
+#include <unistd.h> /* write */
+#endif
+
+/* -------------------------------------------------------------------- */
+/* Common */
+/* -------------------------------------------------------------------- */
+
+typedef struct {
+ PyObject_HEAD int (*encode)(
+ Imaging im, ImagingCodecState state, UINT8 *buffer, int bytes);
+ int (*cleanup)(ImagingCodecState state);
+ struct ImagingCodecStateInstance state;
+ Imaging im;
+ PyObject *lock;
+ int pushes_fd;
+} ImagingEncoderObject;
+
+static PyTypeObject ImagingEncoderType;
+
+static ImagingEncoderObject *
+PyImaging_EncoderNew(int contextsize) {
+ ImagingEncoderObject *encoder;
+ void *context;
+
+ if (PyType_Ready(&ImagingEncoderType) < 0) {
+ return NULL;
+ }
+
+ encoder = PyObject_New(ImagingEncoderObject, &ImagingEncoderType);
+ if (encoder == NULL) {
+ return NULL;
+ }
+
+ /* Clear the encoder state */
+ memset(&encoder->state, 0, sizeof(encoder->state));
+
+ /* Allocate encoder context */
+ if (contextsize > 0) {
+ context = (void *)calloc(1, contextsize);
+ if (!context) {
+ Py_DECREF(encoder);
+ (void)ImagingError_MemoryError();
+ return NULL;
+ }
+ } else {
+ context = 0;
+ }
+
+ /* Initialize encoder context */
+ encoder->state.context = context;
+
+ /* Most encoders don't need this */
+ encoder->cleanup = NULL;
+
+ /* Target image */
+ encoder->lock = NULL;
+ encoder->im = NULL;
+ encoder->pushes_fd = 0;
+
+ return encoder;
+}
+
+static void
+_dealloc(ImagingEncoderObject *encoder) {
+ if (encoder->cleanup) {
+ encoder->cleanup(&encoder->state);
+ }
+ free(encoder->state.buffer);
+ free(encoder->state.context);
+ Py_XDECREF(encoder->lock);
+ Py_XDECREF(encoder->state.fd);
+ PyObject_Del(encoder);
+}
+
+static PyObject *
+_encode_cleanup(ImagingEncoderObject *encoder, PyObject *args) {
+ int status = 0;
+
+ if (encoder->cleanup) {
+ status = encoder->cleanup(&encoder->state);
+ }
+
+ return Py_BuildValue("i", status);
+}
+
+static PyObject *
+_encode(ImagingEncoderObject *encoder, PyObject *args) {
+ PyObject *buf;
+ PyObject *result;
+ int status;
+
+ /* Encode to a Python string (allocated by this method) */
+
+ Py_ssize_t bufsize = 16384;
+
+ if (!PyArg_ParseTuple(args, "|n", &bufsize)) {
+ return NULL;
+ }
+
+ buf = PyBytes_FromStringAndSize(NULL, bufsize);
+ if (!buf) {
+ return NULL;
+ }
+
+ status = encoder->encode(
+ encoder->im, &encoder->state, (UINT8 *)PyBytes_AsString(buf), bufsize);
+
+ /* adjust string length to avoid slicing in encoder */
+ if (_PyBytes_Resize(&buf, (status > 0) ? status : 0) < 0) {
+ return NULL;
+ }
+
+ result = Py_BuildValue("iiO", status, encoder->state.errcode, buf);
+
+ Py_DECREF(buf); /* must release buffer!!! */
+
+ return result;
+}
+
+static PyObject *
+_encode_to_pyfd(ImagingEncoderObject *encoder) {
+ PyObject *result;
+ int status;
+
+ if (!encoder->pushes_fd) {
+ // UNDONE, appropriate errcode???
+ result = Py_BuildValue("ii", 0, IMAGING_CODEC_CONFIG);
+ return result;
+ }
+
+ status = encoder->encode(encoder->im, &encoder->state, (UINT8 *)NULL, 0);
+
+ result = Py_BuildValue("ii", status, encoder->state.errcode);
+
+ return result;
+}
+
+static PyObject *
+_encode_to_file(ImagingEncoderObject *encoder, PyObject *args) {
+ UINT8 *buf;
+ int status;
+ ImagingSectionCookie cookie;
+
+ /* Encode to a file handle */
+
+ Py_ssize_t fh;
+ Py_ssize_t bufsize = 16384;
+
+ if (!PyArg_ParseTuple(args, "n|n", &fh, &bufsize)) {
+ return NULL;
+ }
+
+ /* Allocate an encoder buffer */
+ /* malloc check ok, either constant int, or checked by PyArg_ParseTuple */
+ buf = (UINT8 *)malloc(bufsize);
+ if (!buf) {
+ return ImagingError_MemoryError();
+ }
+
+ ImagingSectionEnter(&cookie);
+
+ do {
+ /* This replaces the inner loop in the ImageFile _save
+ function. */
+
+ status = encoder->encode(encoder->im, &encoder->state, buf, bufsize);
+
+ if (status > 0) {
+ if (write(fh, buf, status) < 0) {
+ ImagingSectionLeave(&cookie);
+ free(buf);
+ return PyErr_SetFromErrno(PyExc_OSError);
+ }
+ }
+
+ } while (encoder->state.errcode == 0);
+
+ ImagingSectionLeave(&cookie);
+
+ free(buf);
+
+ return Py_BuildValue("i", encoder->state.errcode);
+}
+
+extern Imaging
+PyImaging_AsImaging(PyObject *op);
+
+static PyObject *
+_setimage(ImagingEncoderObject *encoder, PyObject *args) {
+ PyObject *op;
+ Imaging im;
+ ImagingCodecState state;
+ Py_ssize_t x0, y0, x1, y1;
+
+ /* Define where image data should be stored */
+
+ x0 = y0 = x1 = y1 = 0;
+
+ /* FIXME: should publish the ImagingType descriptor */
+ if (!PyArg_ParseTuple(args, "O|(nnnn)", &op, &x0, &y0, &x1, &y1)) {
+ return NULL;
+ }
+ im = PyImaging_AsImaging(op);
+ if (!im) {
+ return NULL;
+ }
+
+ encoder->im = im;
+
+ state = &encoder->state;
+
+ if (x0 == 0 && x1 == 0) {
+ state->xsize = im->xsize;
+ state->ysize = im->ysize;
+ } else {
+ state->xoff = x0;
+ state->yoff = y0;
+ state->xsize = x1 - x0;
+ state->ysize = y1 - y0;
+ }
+
+ if (state->xsize <= 0 || state->xsize + state->xoff > im->xsize ||
+ state->ysize <= 0 || state->ysize + state->yoff > im->ysize) {
+ PyErr_SetString(PyExc_SystemError, "tile cannot extend outside image");
+ return NULL;
+ }
+
+ /* Allocate memory buffer (if bits field is set) */
+ if (state->bits > 0) {
+ if (state->xsize > ((INT_MAX / state->bits) - 7)) {
+ return ImagingError_MemoryError();
+ }
+ state->bytes = (state->bits * state->xsize + 7) / 8;
+ /* malloc check ok, overflow checked above */
+ state->buffer = (UINT8 *)calloc(1, state->bytes);
+ if (!state->buffer) {
+ return ImagingError_MemoryError();
+ }
+ }
+
+ /* Keep a reference to the image object, to make sure it doesn't
+ go away before we do */
+ Py_INCREF(op);
+ Py_XDECREF(encoder->lock);
+ encoder->lock = op;
+
+ Py_INCREF(Py_None);
+ return Py_None;
+}
+
+static PyObject *
+_setfd(ImagingEncoderObject *encoder, PyObject *args) {
+ PyObject *fd;
+ ImagingCodecState state;
+
+ if (!PyArg_ParseTuple(args, "O", &fd)) {
+ return NULL;
+ }
+
+ state = &encoder->state;
+
+ Py_XINCREF(fd);
+ state->fd = fd;
+
+ Py_INCREF(Py_None);
+ return Py_None;
+}
+
+static PyObject *
+_get_pushes_fd(ImagingEncoderObject *encoder, void *closure) {
+ return PyBool_FromLong(encoder->pushes_fd);
+}
+
+static struct PyMethodDef methods[] = {
+ {"encode", (PyCFunction)_encode, METH_VARARGS},
+ {"cleanup", (PyCFunction)_encode_cleanup, METH_VARARGS},
+ {"encode_to_file", (PyCFunction)_encode_to_file, METH_VARARGS},
+ {"encode_to_pyfd", (PyCFunction)_encode_to_pyfd, METH_NOARGS},
+ {"setimage", (PyCFunction)_setimage, METH_VARARGS},
+ {"setfd", (PyCFunction)_setfd, METH_VARARGS},
+ {NULL, NULL} /* sentinel */
+};
+
+static struct PyGetSetDef getseters[] = {
+ {"pushes_fd",
+ (getter)_get_pushes_fd,
+ NULL,
+ "True if this decoder expects to push directly to self.fd",
+ NULL},
+ {NULL, NULL, NULL, NULL, NULL} /* sentinel */
+};
+
+static PyTypeObject ImagingEncoderType = {
+ PyVarObject_HEAD_INIT(NULL, 0) "ImagingEncoder", /*tp_name*/
+ sizeof(ImagingEncoderObject), /*tp_basicsize*/
+ 0, /*tp_itemsize*/
+ /* methods */
+ (destructor)_dealloc, /*tp_dealloc*/
+ 0, /*tp_vectorcall_offset*/
+ 0, /*tp_getattr*/
+ 0, /*tp_setattr*/
+ 0, /*tp_as_async*/
+ 0, /*tp_repr*/
+ 0, /*tp_as_number*/
+ 0, /*tp_as_sequence*/
+ 0, /*tp_as_mapping*/
+ 0, /*tp_hash*/
+ 0, /*tp_call*/
+ 0, /*tp_str*/
+ 0, /*tp_getattro*/
+ 0, /*tp_setattro*/
+ 0, /*tp_as_buffer*/
+ Py_TPFLAGS_DEFAULT, /*tp_flags*/
+ 0, /*tp_doc*/
+ 0, /*tp_traverse*/
+ 0, /*tp_clear*/
+ 0, /*tp_richcompare*/
+ 0, /*tp_weaklistoffset*/
+ 0, /*tp_iter*/
+ 0, /*tp_iternext*/
+ methods, /*tp_methods*/
+ 0, /*tp_members*/
+ getseters, /*tp_getset*/
+};
+
+/* -------------------------------------------------------------------- */
+
+int
+get_packer(ImagingEncoderObject *encoder, const char *mode, const char *rawmode) {
+ int bits;
+ ImagingShuffler pack;
+
+ pack = ImagingFindPacker(mode, rawmode, &bits);
+ if (!pack) {
+ Py_DECREF(encoder);
+ PyErr_Format(PyExc_ValueError, "No packer found from %s to %s", mode, rawmode);
+ return -1;
+ }
+
+ encoder->state.shuffle = pack;
+ encoder->state.bits = bits;
+
+ return 0;
+}
+
+/* -------------------------------------------------------------------- */
+/* EPS */
+/* -------------------------------------------------------------------- */
+
+PyObject *
+PyImaging_EpsEncoderNew(PyObject *self, PyObject *args) {
+ ImagingEncoderObject *encoder;
+
+ encoder = PyImaging_EncoderNew(0);
+ if (encoder == NULL) {
+ return NULL;
+ }
+
+ encoder->encode = ImagingEpsEncode;
+
+ return (PyObject *)encoder;
+}
+
+/* -------------------------------------------------------------------- */
+/* GIF */
+/* -------------------------------------------------------------------- */
+
+PyObject *
+PyImaging_GifEncoderNew(PyObject *self, PyObject *args) {
+ ImagingEncoderObject *encoder;
+
+ char *mode;
+ char *rawmode;
+ Py_ssize_t bits = 8;
+ Py_ssize_t interlace = 0;
+ if (!PyArg_ParseTuple(args, "ss|nn", &mode, &rawmode, &bits, &interlace)) {
+ return NULL;
+ }
+
+ encoder = PyImaging_EncoderNew(sizeof(GIFENCODERSTATE));
+ if (encoder == NULL) {
+ return NULL;
+ }
+
+ if (get_packer(encoder, mode, rawmode) < 0) {
+ return NULL;
+ }
+
+ encoder->encode = ImagingGifEncode;
+
+ ((GIFENCODERSTATE *)encoder->state.context)->bits = bits;
+ ((GIFENCODERSTATE *)encoder->state.context)->interlace = interlace;
+
+ return (PyObject *)encoder;
+}
+
+/* -------------------------------------------------------------------- */
+/* PCX */
+/* -------------------------------------------------------------------- */
+
+PyObject *
+PyImaging_PcxEncoderNew(PyObject *self, PyObject *args) {
+ ImagingEncoderObject *encoder;
+
+ char *mode;
+ char *rawmode;
+ Py_ssize_t bits = 8;
+
+ if (!PyArg_ParseTuple(args, "ss|n", &mode, &rawmode, &bits)) {
+ return NULL;
+ }
+
+ encoder = PyImaging_EncoderNew(0);
+ if (encoder == NULL) {
+ return NULL;
+ }
+
+ if (get_packer(encoder, mode, rawmode) < 0) {
+ return NULL;
+ }
+
+ encoder->encode = ImagingPcxEncode;
+
+ return (PyObject *)encoder;
+}
+
+/* -------------------------------------------------------------------- */
+/* RAW */
+/* -------------------------------------------------------------------- */
+
+PyObject *
+PyImaging_RawEncoderNew(PyObject *self, PyObject *args) {
+ ImagingEncoderObject *encoder;
+
+ char *mode;
+ char *rawmode;
+ Py_ssize_t stride = 0;
+ Py_ssize_t ystep = 1;
+
+ if (!PyArg_ParseTuple(args, "ss|nn", &mode, &rawmode, &stride, &ystep)) {
+ return NULL;
+ }
+
+ encoder = PyImaging_EncoderNew(0);
+ if (encoder == NULL) {
+ return NULL;
+ }
+
+ if (get_packer(encoder, mode, rawmode) < 0) {
+ return NULL;
+ }
+
+ encoder->encode = ImagingRawEncode;
+
+ encoder->state.ystep = ystep;
+ encoder->state.count = stride;
+
+ return (PyObject *)encoder;
+}
+
+/* -------------------------------------------------------------------- */
+/* TGA */
+/* -------------------------------------------------------------------- */
+
+PyObject *
+PyImaging_TgaRleEncoderNew(PyObject *self, PyObject *args) {
+ ImagingEncoderObject *encoder;
+
+ char *mode;
+ char *rawmode;
+ Py_ssize_t ystep = 1;
+
+ if (!PyArg_ParseTuple(args, "ss|n", &mode, &rawmode, &ystep)) {
+ return NULL;
+ }
+
+ encoder = PyImaging_EncoderNew(0);
+ if (encoder == NULL) {
+ return NULL;
+ }
+
+ if (get_packer(encoder, mode, rawmode) < 0) {
+ return NULL;
+ }
+
+ encoder->encode = ImagingTgaRleEncode;
+
+ encoder->state.ystep = ystep;
+
+ return (PyObject *)encoder;
+}
+
+/* -------------------------------------------------------------------- */
+/* XBM */
+/* -------------------------------------------------------------------- */
+
+PyObject *
+PyImaging_XbmEncoderNew(PyObject *self, PyObject *args) {
+ ImagingEncoderObject *encoder;
+
+ encoder = PyImaging_EncoderNew(0);
+ if (encoder == NULL) {
+ return NULL;
+ }
+
+ if (get_packer(encoder, "1", "1;R") < 0) {
+ return NULL;
+ }
+
+ encoder->encode = ImagingXbmEncode;
+
+ return (PyObject *)encoder;
+}
+
+/* -------------------------------------------------------------------- */
+/* ZIP */
+/* -------------------------------------------------------------------- */
+
+#ifdef HAVE_LIBZ
+
+#include "libImaging/ZipCodecs.h"
+
+PyObject *
+PyImaging_ZipEncoderNew(PyObject *self, PyObject *args) {
+ ImagingEncoderObject *encoder;
+
+ char *mode;
+ char *rawmode;
+ Py_ssize_t optimize = 0;
+ Py_ssize_t compress_level = -1;
+ Py_ssize_t compress_type = -1;
+ char *dictionary = NULL;
+ Py_ssize_t dictionary_size = 0;
+ if (!PyArg_ParseTuple(
+ args,
+ "ss|nnny#",
+ &mode,
+ &rawmode,
+ &optimize,
+ &compress_level,
+ &compress_type,
+ &dictionary,
+ &dictionary_size)) {
+ return NULL;
+ }
+
+ /* Copy to avoid referencing Python's memory */
+ if (dictionary && dictionary_size > 0) {
+ /* malloc check ok, size comes from PyArg_ParseTuple */
+ char *p = malloc(dictionary_size);
+ if (!p) {
+ return ImagingError_MemoryError();
+ }
+ memcpy(p, dictionary, dictionary_size);
+ dictionary = p;
+ } else {
+ dictionary = NULL;
+ }
+
+ encoder = PyImaging_EncoderNew(sizeof(ZIPSTATE));
+ if (encoder == NULL) {
+ free(dictionary);
+ return NULL;
+ }
+
+ if (get_packer(encoder, mode, rawmode) < 0) {
+ free(dictionary);
+ return NULL;
+ }
+
+ encoder->encode = ImagingZipEncode;
+ encoder->cleanup = ImagingZipEncodeCleanup;
+
+ if (rawmode[0] == 'P') {
+ /* disable filtering */
+ ((ZIPSTATE *)encoder->state.context)->mode = ZIP_PNG_PALETTE;
+ }
+
+ ((ZIPSTATE *)encoder->state.context)->optimize = optimize;
+ ((ZIPSTATE *)encoder->state.context)->compress_level = compress_level;
+ ((ZIPSTATE *)encoder->state.context)->compress_type = compress_type;
+ ((ZIPSTATE *)encoder->state.context)->dictionary = dictionary;
+ ((ZIPSTATE *)encoder->state.context)->dictionary_size = dictionary_size;
+
+ return (PyObject *)encoder;
+}
+#endif
+
+/* -------------------------------------------------------------------- */
+/* LibTiff */
+/* -------------------------------------------------------------------- */
+
+#ifdef HAVE_LIBTIFF
+
+#include "libImaging/TiffDecode.h"
+
+#include <string.h>
+
+PyObject *
+PyImaging_LibTiffEncoderNew(PyObject *self, PyObject *args) {
+ ImagingEncoderObject *encoder;
+
+ char *mode;
+ char *rawmode;
+ char *compname;
+ char *filename;
+ Py_ssize_t fp;
+
+ PyObject *tags, *types;
+ PyObject *key, *value;
+ Py_ssize_t pos = 0;
+ int key_int, status, is_core_tag, is_var_length, num_core_tags, i;
+ TIFFDataType type = TIFF_NOTYPE;
+ // This list also exists in TiffTags.py
+ const int core_tags[] = {256, 257, 258, 259, 262, 263, 266, 269, 274,
+ 277, 278, 280, 281, 340, 341, 282, 283, 284,
+ 286, 287, 296, 297, 320, 321, 338, 32995, 32998,
+ 32996, 339, 32997, 330, 531, 530, 65537, 301, 532};
+
+ Py_ssize_t tags_size;
+ PyObject *item;
+
+ if (!PyArg_ParseTuple(
+ args,
+ "sssnsOO",
+ &mode,
+ &rawmode,
+ &compname,
+ &fp,
+ &filename,
+ &tags,
+ &types)) {
+ return NULL;
+ }
+
+ if (!PyList_Check(tags)) {
+ PyErr_SetString(PyExc_ValueError, "Invalid tags list");
+ return NULL;
+ } else {
+ tags_size = PyList_Size(tags);
+ TRACE(("tags size: %d\n", (int)tags_size));
+ for (pos = 0; pos < tags_size; pos++) {
+ item = PyList_GetItem(tags, pos);
+ if (!PyTuple_Check(item) || PyTuple_Size(item) != 2) {
+ PyErr_SetString(PyExc_ValueError, "Invalid tags list");
+ return NULL;
+ }
+ }
+ pos = 0;
+ }
+ if (!PyDict_Check(types)) {
+ PyErr_SetString(PyExc_ValueError, "Invalid types dictionary");
+ return NULL;
+ }
+
+ TRACE(("new tiff encoder %s fp: %d, filename: %s \n", compname, fp, filename));
+
+ encoder = PyImaging_EncoderNew(sizeof(TIFFSTATE));
+ if (encoder == NULL) {
+ return NULL;
+ }
+
+ if (get_packer(encoder, mode, rawmode) < 0) {
+ return NULL;
+ }
+
+ if (!ImagingLibTiffEncodeInit(&encoder->state, filename, fp)) {
+ Py_DECREF(encoder);
+ PyErr_SetString(PyExc_RuntimeError, "tiff codec initialization failed");
+ return NULL;
+ }
+
+ num_core_tags = sizeof(core_tags) / sizeof(int);
+ for (pos = 0; pos < tags_size; pos++) {
+ item = PyList_GetItem(tags, pos);
+ // We already checked that tags is a 2-tuple list.
+ key = PyTuple_GetItem(item, 0);
+ key_int = (int)PyLong_AsLong(key);
+ value = PyTuple_GetItem(item, 1);
+ status = 0;
+ is_core_tag = 0;
+ is_var_length = 0;
+ type = TIFF_NOTYPE;
+
+ for (i = 0; i < num_core_tags; i++) {
+ if (core_tags[i] == key_int) {
+ is_core_tag = 1;
+ break;
+ }
+ }
+
+ if (!is_core_tag) {
+ PyObject *tag_type = PyDict_GetItem(types, key);
+ if (tag_type) {
+ int type_int = PyLong_AsLong(tag_type);
+ if (type_int >= TIFF_BYTE && type_int <= TIFF_DOUBLE) {
+ type = (TIFFDataType)type_int;
+ }
+ }
+ }
+
+ if (type == TIFF_NOTYPE) {
+ // Autodetect type. Types should not be changed for backwards
+ // compatibility.
+ if (PyLong_Check(value)) {
+ type = TIFF_LONG;
+ } else if (PyFloat_Check(value)) {
+ type = TIFF_DOUBLE;
+ } else if (PyBytes_Check(value)) {
+ type = TIFF_ASCII;
+ }
+ }
+
+ if (PyTuple_Check(value)) {
+ Py_ssize_t len;
+ len = PyTuple_Size(value);
+
+ is_var_length = 1;
+
+ if (!len) {
+ continue;
+ }
+
+ if (type == TIFF_NOTYPE) {
+ // Autodetect type based on first item. Types should not be
+ // changed for backwards compatibility.
+ if (PyLong_Check(PyTuple_GetItem(value, 0))) {
+ type = TIFF_LONG;
+ } else if (PyFloat_Check(PyTuple_GetItem(value, 0))) {
+ type = TIFF_FLOAT;
+ }
+ }
+ }
+
+ if (!is_core_tag) {
+ // Register field for non core tags.
+ if (type == TIFF_BYTE) {
+ is_var_length = 1;
+ }
+ if (ImagingLibTiffMergeFieldInfo(
+ &encoder->state, type, key_int, is_var_length)) {
+ continue;
+ }
+ }
+
+ if (type == TIFF_BYTE || type == TIFF_UNDEFINED) {
+ status = ImagingLibTiffSetField(
+ &encoder->state,
+ (ttag_t)key_int,
+ PyBytes_Size(value),
+ PyBytes_AsString(value));
+ } else if (is_var_length) {
+ Py_ssize_t len, i;
+ TRACE(("Setting from Tuple: %d \n", key_int));
+ len = PyTuple_Size(value);
+
+ if (key_int == TIFFTAG_COLORMAP) {
+ int stride = 256;
+ if (len != 768) {
+ PyErr_SetString(
+ PyExc_ValueError, "Requiring 768 items for Colormap");
+ return NULL;
+ }
+ UINT16 *av;
+ /* malloc check ok, calloc checks for overflow */
+ av = calloc(len, sizeof(UINT16));
+ if (av) {
+ for (i = 0; i < len; i++) {
+ av[i] = (UINT16)PyLong_AsLong(PyTuple_GetItem(value, i));
+ }
+ status = ImagingLibTiffSetField(
+ &encoder->state,
+ (ttag_t)key_int,
+ av,
+ av + stride,
+ av + stride * 2);
+ free(av);
+ }
+ } else if (key_int == TIFFTAG_YCBCRSUBSAMPLING) {
+ status = ImagingLibTiffSetField(
+ &encoder->state,
+ (ttag_t)key_int,
+ (UINT16)PyLong_AsLong(PyTuple_GetItem(value, 0)),
+ (UINT16)PyLong_AsLong(PyTuple_GetItem(value, 1)));
+ } else if (type == TIFF_SHORT) {
+ UINT16 *av;
+ /* malloc check ok, calloc checks for overflow */
+ av = calloc(len, sizeof(UINT16));
+ if (av) {
+ for (i = 0; i < len; i++) {
+ av[i] = (UINT16)PyLong_AsLong(PyTuple_GetItem(value, i));
+ }
+ status = ImagingLibTiffSetField(
+ &encoder->state, (ttag_t)key_int, len, av);
+ free(av);
+ }
+ } else if (type == TIFF_LONG) {
+ UINT32 *av;
+ /* malloc check ok, calloc checks for overflow */
+ av = calloc(len, sizeof(UINT32));
+ if (av) {
+ for (i = 0; i < len; i++) {
+ av[i] = (UINT32)PyLong_AsLong(PyTuple_GetItem(value, i));
+ }
+ status = ImagingLibTiffSetField(
+ &encoder->state, (ttag_t)key_int, len, av);
+ free(av);
+ }
+ } else if (type == TIFF_SBYTE) {
+ INT8 *av;
+ /* malloc check ok, calloc checks for overflow */
+ av = calloc(len, sizeof(INT8));
+ if (av) {
+ for (i = 0; i < len; i++) {
+ av[i] = (INT8)PyLong_AsLong(PyTuple_GetItem(value, i));
+ }
+ status = ImagingLibTiffSetField(
+ &encoder->state, (ttag_t)key_int, len, av);
+ free(av);
+ }
+ } else if (type == TIFF_SSHORT) {
+ INT16 *av;
+ /* malloc check ok, calloc checks for overflow */
+ av = calloc(len, sizeof(INT16));
+ if (av) {
+ for (i = 0; i < len; i++) {
+ av[i] = (INT16)PyLong_AsLong(PyTuple_GetItem(value, i));
+ }
+ status = ImagingLibTiffSetField(
+ &encoder->state, (ttag_t)key_int, len, av);
+ free(av);
+ }
+ } else if (type == TIFF_SLONG) {
+ INT32 *av;
+ /* malloc check ok, calloc checks for overflow */
+ av = calloc(len, sizeof(INT32));
+ if (av) {
+ for (i = 0; i < len; i++) {
+ av[i] = (INT32)PyLong_AsLong(PyTuple_GetItem(value, i));
+ }
+ status = ImagingLibTiffSetField(
+ &encoder->state, (ttag_t)key_int, len, av);
+ free(av);
+ }
+ } else if (type == TIFF_FLOAT) {
+ FLOAT32 *av;
+ /* malloc check ok, calloc checks for overflow */
+ av = calloc(len, sizeof(FLOAT32));
+ if (av) {
+ for (i = 0; i < len; i++) {
+ av[i] = (FLOAT32)PyFloat_AsDouble(PyTuple_GetItem(value, i));
+ }
+ status = ImagingLibTiffSetField(
+ &encoder->state, (ttag_t)key_int, len, av);
+ free(av);
+ }
+ } else if (type == TIFF_DOUBLE) {
+ FLOAT64 *av;
+ /* malloc check ok, calloc checks for overflow */
+ av = calloc(len, sizeof(FLOAT64));
+ if (av) {
+ for (i = 0; i < len; i++) {
+ av[i] = PyFloat_AsDouble(PyTuple_GetItem(value, i));
+ }
+ status = ImagingLibTiffSetField(
+ &encoder->state, (ttag_t)key_int, len, av);
+ free(av);
+ }
+ }
+ } else {
+ if (type == TIFF_SHORT) {
+ status = ImagingLibTiffSetField(
+ &encoder->state, (ttag_t)key_int, (UINT16)PyLong_AsLong(value));
+ } else if (type == TIFF_LONG) {
+ status = ImagingLibTiffSetField(
+ &encoder->state, (ttag_t)key_int, PyLong_AsLongLong(value));
+ } else if (type == TIFF_SSHORT) {
+ status = ImagingLibTiffSetField(
+ &encoder->state, (ttag_t)key_int, (INT16)PyLong_AsLong(value));
+ } else if (type == TIFF_SLONG) {
+ status = ImagingLibTiffSetField(
+ &encoder->state, (ttag_t)key_int, (INT32)PyLong_AsLong(value));
+ } else if (type == TIFF_FLOAT) {
+ status = ImagingLibTiffSetField(
+ &encoder->state, (ttag_t)key_int, (FLOAT32)PyFloat_AsDouble(value));
+ } else if (type == TIFF_DOUBLE) {
+ status = ImagingLibTiffSetField(
+ &encoder->state, (ttag_t)key_int, (FLOAT64)PyFloat_AsDouble(value));
+ } else if (type == TIFF_SBYTE) {
+ status = ImagingLibTiffSetField(
+ &encoder->state, (ttag_t)key_int, (INT8)PyLong_AsLong(value));
+ } else if (type == TIFF_ASCII) {
+ status = ImagingLibTiffSetField(
+ &encoder->state, (ttag_t)key_int, PyBytes_AsString(value));
+ } else if (type == TIFF_RATIONAL) {
+ status = ImagingLibTiffSetField(
+ &encoder->state, (ttag_t)key_int, (FLOAT64)PyFloat_AsDouble(value));
+ } else {
+ TRACE(
+ ("Unhandled type for key %d : %s \n",
+ key_int,
+ PyBytes_AsString(PyObject_Str(value))));
+ }
+ }
+ if (!status) {
+ TRACE(("Error setting Field\n"));
+ Py_DECREF(encoder);
+ PyErr_SetString(PyExc_RuntimeError, "Error setting from dictionary");
+ return NULL;
+ }
+ }
+
+ encoder->encode = ImagingLibTiffEncode;
+
+ return (PyObject *)encoder;
+}
+
+#endif
+
+/* -------------------------------------------------------------------- */
+/* JPEG */
+/* -------------------------------------------------------------------- */
+
+#ifdef HAVE_LIBJPEG
+
+/* We better define this encoder last in this file, so the following
+ undef's won't mess things up for the Imaging library proper. */
+
+#undef HAVE_PROTOTYPES
+#undef HAVE_STDDEF_H
+#undef HAVE_STDLIB_H
+#undef UINT8
+#undef UINT16
+#undef UINT32
+#undef INT8
+#undef INT16
+#undef INT32
+
+#include "libImaging/Jpeg.h"
+
+static unsigned int *
+get_qtables_arrays(PyObject *qtables, int *qtablesLen) {
+ PyObject *tables;
+ PyObject *table;
+ PyObject *table_data;
+ int i, j, num_tables;
+ unsigned int *qarrays;
+
+ if ((qtables == NULL) || (qtables == Py_None)) {
+ return NULL;
+ }
+
+ if (!PySequence_Check(qtables)) {
+ PyErr_SetString(PyExc_ValueError, "Invalid quantization tables");
+ return NULL;
+ }
+
+ tables = PySequence_Fast(qtables, "expected a sequence");
+ num_tables = PySequence_Size(qtables);
+ if (num_tables < 1 || num_tables > NUM_QUANT_TBLS) {
+ PyErr_SetString(
+ PyExc_ValueError,
+ "Not a valid number of quantization tables. Should be between 1 and 4.");
+ Py_DECREF(tables);
+ return NULL;
+ }
+ /* malloc check ok, num_tables <4, DCTSIZE2 == 64 from jpeglib.h */
+ qarrays = (unsigned int *)malloc(num_tables * DCTSIZE2 * sizeof(unsigned int));
+ if (!qarrays) {
+ Py_DECREF(tables);
+ return ImagingError_MemoryError();
+ }
+ for (i = 0; i < num_tables; i++) {
+ table = PySequence_Fast_GET_ITEM(tables, i);
+ if (!PySequence_Check(table)) {
+ PyErr_SetString(PyExc_ValueError, "Invalid quantization tables");
+ goto JPEG_QTABLES_ERR;
+ }
+ if (PySequence_Size(table) != DCTSIZE2) {
+ PyErr_SetString(PyExc_ValueError, "Invalid quantization table size");
+ goto JPEG_QTABLES_ERR;
+ }
+ table_data = PySequence_Fast(table, "expected a sequence");
+ for (j = 0; j < DCTSIZE2; j++) {
+ qarrays[i * DCTSIZE2 + j] =
+ PyLong_AS_LONG(PySequence_Fast_GET_ITEM(table_data, j));
+ }
+ Py_DECREF(table_data);
+ }
+
+ *qtablesLen = num_tables;
+
+JPEG_QTABLES_ERR:
+ Py_DECREF(tables); // Run on both error and not error
+ if (PyErr_Occurred()) {
+ free(qarrays);
+ qarrays = NULL;
+ return NULL;
+ }
+
+ return qarrays;
+}
+
+PyObject *
+PyImaging_JpegEncoderNew(PyObject *self, PyObject *args) {
+ ImagingEncoderObject *encoder;
+
+ char *mode;
+ char *rawmode;
+ Py_ssize_t quality = 0;
+ Py_ssize_t progressive = 0;
+ Py_ssize_t smooth = 0;
+ Py_ssize_t optimize = 0;
+ Py_ssize_t streamtype = 0; /* 0=interchange, 1=tables only, 2=image only */
+ Py_ssize_t xdpi = 0, ydpi = 0;
+ Py_ssize_t subsampling = -1; /* -1=default, 0=none, 1=medium, 2=high */
+ PyObject *qtables = NULL;
+ unsigned int *qarrays = NULL;
+ int qtablesLen = 0;
+ char *comment = NULL;
+ Py_ssize_t comment_size;
+ char *extra = NULL;
+ Py_ssize_t extra_size;
+ char *rawExif = NULL;
+ Py_ssize_t rawExifLen = 0;
+
+ if (!PyArg_ParseTuple(
+ args,
+ "ss|nnnnnnnnOz#y#y#",
+ &mode,
+ &rawmode,
+ &quality,
+ &progressive,
+ &smooth,
+ &optimize,
+ &streamtype,
+ &xdpi,
+ &ydpi,
+ &subsampling,
+ &qtables,
+ &comment,
+ &comment_size,
+ &extra,
+ &extra_size,
+ &rawExif,
+ &rawExifLen)) {
+ return NULL;
+ }
+
+ encoder = PyImaging_EncoderNew(sizeof(JPEGENCODERSTATE));
+ if (encoder == NULL) {
+ return NULL;
+ }
+
+ // libjpeg-turbo supports different output formats.
+ // We are choosing Pillow's native format (3 color bytes + 1 padding)
+ // to avoid extra conversion in Pack.c.
+ if (ImagingJpegUseJCSExtensions() && strcmp(rawmode, "RGB") == 0) {
+ rawmode = "RGBX";
+ }
+
+ if (get_packer(encoder, mode, rawmode) < 0) {
+ return NULL;
+ }
+
+ // Freed in JpegEncode, Case 6
+ qarrays = get_qtables_arrays(qtables, &qtablesLen);
+
+ if (comment && comment_size > 0) {
+ /* malloc check ok, length is from python parsearg */
+ char *p = malloc(comment_size); // Freed in JpegEncode, Case 6
+ if (!p) {
+ return ImagingError_MemoryError();
+ }
+ memcpy(p, comment, comment_size);
+ comment = p;
+ } else {
+ comment = NULL;
+ }
+
+ if (extra && extra_size > 0) {
+ /* malloc check ok, length is from python parsearg */
+ char *p = malloc(extra_size); // Freed in JpegEncode, Case 6
+ if (!p) {
+ if (comment) {
+ free(comment);
+ }
+ return ImagingError_MemoryError();
+ }
+ memcpy(p, extra, extra_size);
+ extra = p;
+ } else {
+ extra = NULL;
+ }
+
+ if (rawExif && rawExifLen > 0) {
+ /* malloc check ok, length is from python parsearg */
+ char *pp = malloc(rawExifLen); // Freed in JpegEncode, Case 6
+ if (!pp) {
+ if (comment) {
+ free(comment);
+ }
+ if (extra) {
+ free(extra);
+ }
+ return ImagingError_MemoryError();
+ }
+ memcpy(pp, rawExif, rawExifLen);
+ rawExif = pp;
+ } else {
+ rawExif = NULL;
+ }
+
+ encoder->encode = ImagingJpegEncode;
+
+ strncpy(((JPEGENCODERSTATE *)encoder->state.context)->rawmode, rawmode, 8);
+
+ ((JPEGENCODERSTATE *)encoder->state.context)->quality = quality;
+ ((JPEGENCODERSTATE *)encoder->state.context)->qtables = qarrays;
+ ((JPEGENCODERSTATE *)encoder->state.context)->qtablesLen = qtablesLen;
+ ((JPEGENCODERSTATE *)encoder->state.context)->subsampling = subsampling;
+ ((JPEGENCODERSTATE *)encoder->state.context)->progressive = progressive;
+ ((JPEGENCODERSTATE *)encoder->state.context)->smooth = smooth;
+ ((JPEGENCODERSTATE *)encoder->state.context)->optimize = optimize;
+ ((JPEGENCODERSTATE *)encoder->state.context)->streamtype = streamtype;
+ ((JPEGENCODERSTATE *)encoder->state.context)->xdpi = xdpi;
+ ((JPEGENCODERSTATE *)encoder->state.context)->ydpi = ydpi;
+ ((JPEGENCODERSTATE *)encoder->state.context)->comment = comment;
+ ((JPEGENCODERSTATE *)encoder->state.context)->comment_size = comment_size;
+ ((JPEGENCODERSTATE *)encoder->state.context)->extra = extra;
+ ((JPEGENCODERSTATE *)encoder->state.context)->extra_size = extra_size;
+ ((JPEGENCODERSTATE *)encoder->state.context)->rawExif = rawExif;
+ ((JPEGENCODERSTATE *)encoder->state.context)->rawExifLen = rawExifLen;
+
+ return (PyObject *)encoder;
+}
+
+#endif
+
+/* -------------------------------------------------------------------- */
+/* JPEG 2000 */
+/* -------------------------------------------------------------------- */
+
+#ifdef HAVE_OPENJPEG
+
+#include "libImaging/Jpeg2K.h"
+
+static void
+j2k_decode_coord_tuple(PyObject *tuple, int *x, int *y) {
+ *x = *y = 0;
+
+ if (tuple && PyTuple_Check(tuple) && PyTuple_GET_SIZE(tuple) == 2) {
+ *x = (int)PyLong_AsLong(PyTuple_GET_ITEM(tuple, 0));
+ *y = (int)PyLong_AsLong(PyTuple_GET_ITEM(tuple, 1));
+
+ if (*x < 0) {
+ *x = 0;
+ }
+ if (*y < 0) {
+ *y = 0;
+ }
+ }
+}
+
+PyObject *
+PyImaging_Jpeg2KEncoderNew(PyObject *self, PyObject *args) {
+ ImagingEncoderObject *encoder;
+ JPEG2KENCODESTATE *context;
+
+ char *mode;
+ char *format;
+ OPJ_CODEC_FORMAT codec_format;
+ PyObject *offset = NULL, *tile_offset = NULL, *tile_size = NULL;
+ char *quality_mode = "rates";
+ PyObject *quality_layers = NULL;
+ Py_ssize_t num_resolutions = 0;
+ PyObject *cblk_size = NULL, *precinct_size = NULL;
+ PyObject *irreversible = NULL;
+ char *progression = "LRCP";
+ OPJ_PROG_ORDER prog_order;
+ char *cinema_mode = "no";
+ OPJ_CINEMA_MODE cine_mode;
+ char mct = 0;
+ int sgnd = 0;
+ Py_ssize_t fd = -1;
+ char *comment;
+ Py_ssize_t comment_size;
+ int plt = 0;
+
+ if (!PyArg_ParseTuple(
+ args,
+ "ss|OOOsOnOOOssbbnz#p",
+ &mode,
+ &format,
+ &offset,
+ &tile_offset,
+ &tile_size,
+ &quality_mode,
+ &quality_layers,
+ &num_resolutions,
+ &cblk_size,
+ &precinct_size,
+ &irreversible,
+ &progression,
+ &cinema_mode,
+ &mct,
+ &sgnd,
+ &fd,
+ &comment,
+ &comment_size,
+ &plt)) {
+ return NULL;
+ }
+
+ if (strcmp(format, "j2k") == 0) {
+ codec_format = OPJ_CODEC_J2K;
+ } else if (strcmp(format, "jpt") == 0) {
+ codec_format = OPJ_CODEC_JPT;
+ } else if (strcmp(format, "jp2") == 0) {
+ codec_format = OPJ_CODEC_JP2;
+ } else {
+ return NULL;
+ }
+
+ if (strcmp(progression, "LRCP") == 0) {
+ prog_order = OPJ_LRCP;
+ } else if (strcmp(progression, "RLCP") == 0) {
+ prog_order = OPJ_RLCP;
+ } else if (strcmp(progression, "RPCL") == 0) {
+ prog_order = OPJ_RPCL;
+ } else if (strcmp(progression, "PCRL") == 0) {
+ prog_order = OPJ_PCRL;
+ } else if (strcmp(progression, "CPRL") == 0) {
+ prog_order = OPJ_CPRL;
+ } else {
+ return NULL;
+ }
+
+ if (strcmp(cinema_mode, "no") == 0) {
+ cine_mode = OPJ_OFF;
+ } else if (strcmp(cinema_mode, "cinema2k-24") == 0) {
+ cine_mode = OPJ_CINEMA2K_24;
+ } else if (strcmp(cinema_mode, "cinema2k-48") == 0) {
+ cine_mode = OPJ_CINEMA2K_48;
+ } else if (strcmp(cinema_mode, "cinema4k-24") == 0) {
+ cine_mode = OPJ_CINEMA4K_24;
+ } else {
+ return NULL;
+ }
+
+ encoder = PyImaging_EncoderNew(sizeof(JPEG2KENCODESTATE));
+ if (!encoder) {
+ return NULL;
+ }
+
+ encoder->encode = ImagingJpeg2KEncode;
+ encoder->cleanup = ImagingJpeg2KEncodeCleanup;
+ encoder->pushes_fd = 1;
+
+ context = (JPEG2KENCODESTATE *)encoder->state.context;
+
+ context->fd = fd;
+ context->format = codec_format;
+ context->offset_x = context->offset_y = 0;
+
+ j2k_decode_coord_tuple(offset, &context->offset_x, &context->offset_y);
+ j2k_decode_coord_tuple(
+ tile_offset, &context->tile_offset_x, &context->tile_offset_y);
+ j2k_decode_coord_tuple(tile_size, &context->tile_size_x, &context->tile_size_y);
+
+ /* Error on illegal tile offsets */
+ if (context->tile_size_x && context->tile_size_y) {
+ if (context->tile_offset_x <= context->offset_x - context->tile_size_x ||
+ context->tile_offset_y <= context->offset_y - context->tile_size_y) {
+ PyErr_SetString(
+ PyExc_ValueError,
+ "JPEG 2000 tile offset too small; top left tile must "
+ "intersect image area");
+ Py_DECREF(encoder);
+ return NULL;
+ }
+
+ if (context->tile_offset_x > context->offset_x ||
+ context->tile_offset_y > context->offset_y) {
+ PyErr_SetString(
+ PyExc_ValueError,
+ "JPEG 2000 tile offset too large to cover image area");
+ Py_DECREF(encoder);
+ return NULL;
+ }
+ }
+
+ if (comment && comment_size > 0) {
+ /* Size is stored as as an uint16, subtract 4 bytes for the header */
+ if (comment_size >= 65532) {
+ PyErr_SetString(
+ PyExc_ValueError,
+ "JPEG 2000 comment is too long");
+ Py_DECREF(encoder);
+ return NULL;
+ }
+
+ char *p = malloc(comment_size + 1);
+ if (!p) {
+ Py_DECREF(encoder);
+ return ImagingError_MemoryError();
+ }
+ memcpy(p, comment, comment_size);
+ p[comment_size] = '\0';
+ context->comment = p;
+ }
+
+ if (quality_layers && PySequence_Check(quality_layers)) {
+ context->quality_is_in_db = strcmp(quality_mode, "dB") == 0;
+ context->quality_layers = quality_layers;
+ Py_INCREF(quality_layers);
+ }
+
+ context->num_resolutions = num_resolutions;
+
+ j2k_decode_coord_tuple(cblk_size, &context->cblk_width, &context->cblk_height);
+ j2k_decode_coord_tuple(
+ precinct_size, &context->precinct_width, &context->precinct_height);
+
+ context->irreversible = PyObject_IsTrue(irreversible);
+ context->progression = prog_order;
+ context->cinema_mode = cine_mode;
+ context->mct = mct;
+ context->sgnd = sgnd;
+ context->plt = plt;
+
+ return (PyObject *)encoder;
+}
+
+#endif
+
+/*
+ * Local Variables:
+ * c-basic-offset: 4
+ * End:
+ *
+ */
diff --git a/contrib/python/Pillow/py3/libImaging/Access.c b/contrib/python/Pillow/py3/libImaging/Access.c
new file mode 100644
index 00000000000..091c84e18fa
--- /dev/null
+++ b/contrib/python/Pillow/py3/libImaging/Access.c
@@ -0,0 +1,239 @@
+/*
+ * The Python Imaging Library
+ * $Id$
+ *
+ * imaging access objects
+ *
+ * Copyright (c) Fredrik Lundh 2009.
+ *
+ * See the README file for information on usage and redistribution.
+ */
+
+#include "Imaging.h"
+
+/* use make_hash.py from the pillow-scripts repository to calculate these values */
+#define ACCESS_TABLE_SIZE 35
+#define ACCESS_TABLE_HASH 8940
+
+static struct ImagingAccessInstance access_table[ACCESS_TABLE_SIZE];
+
+static inline UINT32
+hash(const char *mode) {
+ UINT32 i = ACCESS_TABLE_HASH;
+ while (*mode) {
+ i = ((i << 5) + i) ^ (UINT8)*mode++;
+ }
+ return i % ACCESS_TABLE_SIZE;
+}
+
+static ImagingAccess
+add_item(const char *mode) {
+ UINT32 i = hash(mode);
+ /* printf("hash %s => %d\n", mode, i); */
+ if (access_table[i].mode && strcmp(access_table[i].mode, mode) != 0) {
+ fprintf(
+ stderr,
+ "AccessInit: hash collision: %d for both %s and %s\n",
+ i,
+ mode,
+ access_table[i].mode);
+ exit(1);
+ }
+ access_table[i].mode = mode;
+ return &access_table[i];
+}
+
+/* fetch individual pixel */
+
+static void
+get_pixel_32_2bands(Imaging im, int x, int y, void *color) {
+ char *out = color;
+ UINT8 *p = (UINT8 *)&im->image32[y][x];
+ out[0] = p[0];
+ out[1] = p[3];
+}
+
+static void
+get_pixel_8(Imaging im, int x, int y, void *color) {
+ char *out = color;
+ out[0] = im->image8[y][x];
+}
+
+static void
+get_pixel_16L(Imaging im, int x, int y, void *color) {
+ UINT8 *in = (UINT8 *)&im->image[y][x + x];
+#ifdef WORDS_BIGENDIAN
+ UINT16 out = in[0] + (in[1] << 8);
+ memcpy(color, &out, sizeof(out));
+#else
+ memcpy(color, in, sizeof(UINT16));
+#endif
+}
+
+static void
+get_pixel_16B(Imaging im, int x, int y, void *color) {
+ UINT8 *in = (UINT8 *)&im->image[y][x + x];
+#ifdef WORDS_BIGENDIAN
+ memcpy(color, in, sizeof(UINT16));
+#else
+ UINT16 out = in[1] + (in[0] << 8);
+ memcpy(color, &out, sizeof(out));
+#endif
+}
+
+static void
+get_pixel_16(Imaging im, int x, int y, void *color) {
+ UINT8 *in = (UINT8 *)&im->image[y][x + x];
+ memcpy(color, in, sizeof(UINT16));
+}
+
+static void
+get_pixel_BGR15(Imaging im, int x, int y, void *color) {
+ UINT8 *in = (UINT8 *)&im->image8[y][x * 2];
+ UINT16 pixel = in[0] + (in[1] << 8);
+ char *out = color;
+ out[0] = (pixel & 31) * 255 / 31;
+ out[1] = ((pixel >> 5) & 31) * 255 / 31;
+ out[2] = ((pixel >> 10) & 31) * 255 / 31;
+}
+
+static void
+get_pixel_BGR16(Imaging im, int x, int y, void *color) {
+ UINT8 *in = (UINT8 *)&im->image8[y][x * 2];
+ UINT16 pixel = in[0] + (in[1] << 8);
+ char *out = color;
+ out[0] = (pixel & 31) * 255 / 31;
+ out[1] = ((pixel >> 5) & 63) * 255 / 63;
+ out[2] = ((pixel >> 11) & 31) * 255 / 31;
+}
+
+static void
+get_pixel_BGR24(Imaging im, int x, int y, void *color) {
+ memcpy(color, &im->image8[y][x * 3], sizeof(UINT8) * 3);
+}
+
+static void
+get_pixel_32(Imaging im, int x, int y, void *color) {
+ memcpy(color, &im->image32[y][x], sizeof(INT32));
+}
+
+static void
+get_pixel_32L(Imaging im, int x, int y, void *color) {
+ UINT8 *in = (UINT8 *)&im->image[y][x * 4];
+#ifdef WORDS_BIGENDIAN
+ INT32 out = in[0] + (in[1] << 8) + (in[2] << 16) + (in[3] << 24);
+ memcpy(color, &out, sizeof(out));
+#else
+ memcpy(color, in, sizeof(INT32));
+#endif
+}
+
+static void
+get_pixel_32B(Imaging im, int x, int y, void *color) {
+ UINT8 *in = (UINT8 *)&im->image[y][x * 4];
+#ifdef WORDS_BIGENDIAN
+ memcpy(color, in, sizeof(INT32));
+#else
+ INT32 out = in[3] + (in[2] << 8) + (in[1] << 16) + (in[0] << 24);
+ memcpy(color, &out, sizeof(out));
+#endif
+}
+
+/* store individual pixel */
+
+static void
+put_pixel_8(Imaging im, int x, int y, const void *color) {
+ im->image8[y][x] = *((UINT8 *)color);
+}
+
+static void
+put_pixel_16L(Imaging im, int x, int y, const void *color) {
+ memcpy(&im->image8[y][x + x], color, 2);
+}
+
+static void
+put_pixel_16B(Imaging im, int x, int y, const void *color) {
+ const char *in = color;
+ UINT8 *out = (UINT8 *)&im->image8[y][x + x];
+ out[0] = in[1];
+ out[1] = in[0];
+}
+
+static void
+put_pixel_BGR1516(Imaging im, int x, int y, const void *color) {
+ memcpy(&im->image8[y][x * 2], color, 2);
+}
+
+static void
+put_pixel_BGR24(Imaging im, int x, int y, const void *color) {
+ memcpy(&im->image8[y][x * 3], color, 3);
+}
+
+static void
+put_pixel_32L(Imaging im, int x, int y, const void *color) {
+ memcpy(&im->image8[y][x * 4], color, 4);
+}
+
+static void
+put_pixel_32B(Imaging im, int x, int y, const void *color) {
+ const char *in = color;
+ UINT8 *out = (UINT8 *)&im->image8[y][x * 4];
+ out[0] = in[3];
+ out[1] = in[2];
+ out[2] = in[1];
+ out[3] = in[0];
+}
+
+static void
+put_pixel_32(Imaging im, int x, int y, const void *color) {
+ memcpy(&im->image32[y][x], color, sizeof(INT32));
+}
+
+void
+ImagingAccessInit() {
+#define ADD(mode_, get_pixel_, put_pixel_) \
+ { \
+ ImagingAccess access = add_item(mode_); \
+ access->get_pixel = get_pixel_; \
+ access->put_pixel = put_pixel_; \
+ }
+
+ /* populate access table */
+ ADD("1", get_pixel_8, put_pixel_8);
+ ADD("L", get_pixel_8, put_pixel_8);
+ ADD("LA", get_pixel_32_2bands, put_pixel_32);
+ ADD("La", get_pixel_32_2bands, put_pixel_32);
+ ADD("I", get_pixel_32, put_pixel_32);
+ ADD("I;16", get_pixel_16L, put_pixel_16L);
+ ADD("I;16L", get_pixel_16L, put_pixel_16L);
+ ADD("I;16B", get_pixel_16B, put_pixel_16B);
+ ADD("I;16N", get_pixel_16, put_pixel_16L);
+ ADD("I;32L", get_pixel_32L, put_pixel_32L);
+ ADD("I;32B", get_pixel_32B, put_pixel_32B);
+ ADD("F", get_pixel_32, put_pixel_32);
+ ADD("P", get_pixel_8, put_pixel_8);
+ ADD("PA", get_pixel_32_2bands, put_pixel_32);
+ ADD("BGR;15", get_pixel_BGR15, put_pixel_BGR1516);
+ ADD("BGR;16", get_pixel_BGR16, put_pixel_BGR1516);
+ ADD("BGR;24", get_pixel_BGR24, put_pixel_BGR24);
+ ADD("RGB", get_pixel_32, put_pixel_32);
+ ADD("RGBA", get_pixel_32, put_pixel_32);
+ ADD("RGBa", get_pixel_32, put_pixel_32);
+ ADD("RGBX", get_pixel_32, put_pixel_32);
+ ADD("CMYK", get_pixel_32, put_pixel_32);
+ ADD("YCbCr", get_pixel_32, put_pixel_32);
+ ADD("LAB", get_pixel_32, put_pixel_32);
+ ADD("HSV", get_pixel_32, put_pixel_32);
+}
+
+ImagingAccess
+ImagingAccessNew(Imaging im) {
+ ImagingAccess access = &access_table[hash(im->mode)];
+ if (im->mode[0] != access->mode[0] || strcmp(im->mode, access->mode) != 0) {
+ return NULL;
+ }
+ return access;
+}
+
+void
+_ImagingAccessDelete(Imaging im, ImagingAccess access) {}
diff --git a/contrib/python/Pillow/py3/libImaging/AlphaComposite.c b/contrib/python/Pillow/py3/libImaging/AlphaComposite.c
new file mode 100644
index 00000000000..6d728f9088b
--- /dev/null
+++ b/contrib/python/Pillow/py3/libImaging/AlphaComposite.c
@@ -0,0 +1,85 @@
+/*
+ * The Python Imaging Library
+ * $Id$
+ *
+ * Alpha composite imSrc over imDst.
+ * https://en.wikipedia.org/wiki/Alpha_compositing
+ *
+ * See the README file for details on usage and redistribution.
+ */
+
+#include "Imaging.h"
+
+#define PRECISION_BITS 7
+
+typedef struct {
+ UINT8 r;
+ UINT8 g;
+ UINT8 b;
+ UINT8 a;
+} rgba8;
+
+Imaging
+ImagingAlphaComposite(Imaging imDst, Imaging imSrc) {
+ Imaging imOut;
+ int x, y;
+
+ /* Check arguments */
+ if (!imDst || !imSrc || strcmp(imDst->mode, "RGBA") ||
+ imDst->type != IMAGING_TYPE_UINT8 || imDst->bands != 4) {
+ return ImagingError_ModeError();
+ }
+
+ if (strcmp(imDst->mode, imSrc->mode) || imDst->type != imSrc->type ||
+ imDst->bands != imSrc->bands || imDst->xsize != imSrc->xsize ||
+ imDst->ysize != imSrc->ysize) {
+ return ImagingError_Mismatch();
+ }
+
+ imOut = ImagingNewDirty(imDst->mode, imDst->xsize, imDst->ysize);
+ if (!imOut) {
+ return NULL;
+ }
+
+ for (y = 0; y < imDst->ysize; y++) {
+ rgba8 *dst = (rgba8 *)imDst->image[y];
+ rgba8 *src = (rgba8 *)imSrc->image[y];
+ rgba8 *out = (rgba8 *)imOut->image[y];
+
+ for (x = 0; x < imDst->xsize; x++) {
+ if (src->a == 0) {
+ // Copy 4 bytes at once.
+ *out = *dst;
+ } else {
+ // Integer implementation with increased precision.
+ // Each variable has extra meaningful bits.
+ // Divisions are rounded.
+
+ UINT32 tmpr, tmpg, tmpb;
+ UINT32 blend = dst->a * (255 - src->a);
+ UINT32 outa255 = src->a * 255 + blend;
+ // There we use 7 bits for precision.
+ // We could use more, but we go beyond 32 bits.
+ UINT32 coef1 = src->a * 255 * 255 * (1 << PRECISION_BITS) / outa255;
+ UINT32 coef2 = 255 * (1 << PRECISION_BITS) - coef1;
+
+ tmpr = src->r * coef1 + dst->r * coef2;
+ tmpg = src->g * coef1 + dst->g * coef2;
+ tmpb = src->b * coef1 + dst->b * coef2;
+ out->r =
+ SHIFTFORDIV255(tmpr + (0x80 << PRECISION_BITS)) >> PRECISION_BITS;
+ out->g =
+ SHIFTFORDIV255(tmpg + (0x80 << PRECISION_BITS)) >> PRECISION_BITS;
+ out->b =
+ SHIFTFORDIV255(tmpb + (0x80 << PRECISION_BITS)) >> PRECISION_BITS;
+ out->a = SHIFTFORDIV255(outa255 + 0x80);
+ }
+
+ dst++;
+ src++;
+ out++;
+ }
+ }
+
+ return imOut;
+}
diff --git a/contrib/python/Pillow/py3/libImaging/Bands.c b/contrib/python/Pillow/py3/libImaging/Bands.c
new file mode 100644
index 00000000000..e1b16b34ac0
--- /dev/null
+++ b/contrib/python/Pillow/py3/libImaging/Bands.c
@@ -0,0 +1,315 @@
+/*
+ * The Python Imaging Library
+ * $Id$
+ *
+ * stuff to extract and paste back individual bands
+ *
+ * history:
+ * 1996-03-20 fl Created
+ * 1997-08-27 fl Fixed putband for single band targets.
+ * 2003-09-26 fl Fixed getband/putband for 2-band images (LA, PA).
+ *
+ * Copyright (c) 1997-2003 by Secret Labs AB.
+ * Copyright (c) 1996-1997 by Fredrik Lundh.
+ *
+ * See the README file for details on usage and redistribution.
+ */
+
+#include "Imaging.h"
+
+Imaging
+ImagingGetBand(Imaging imIn, int band) {
+ Imaging imOut;
+ int x, y;
+
+ /* Check arguments */
+ if (!imIn || imIn->type != IMAGING_TYPE_UINT8) {
+ return (Imaging)ImagingError_ModeError();
+ }
+
+ if (band < 0 || band >= imIn->bands) {
+ return (Imaging)ImagingError_ValueError("band index out of range");
+ }
+
+ /* Shortcuts */
+ if (imIn->bands == 1) {
+ return ImagingCopy(imIn);
+ }
+
+ /* Special case for LXXA etc */
+ if (imIn->bands == 2 && band == 1) {
+ band = 3;
+ }
+
+ imOut = ImagingNewDirty("L", imIn->xsize, imIn->ysize);
+ if (!imOut) {
+ return NULL;
+ }
+
+ /* Extract band from image */
+ for (y = 0; y < imIn->ysize; y++) {
+ UINT8 *in = (UINT8 *)imIn->image[y] + band;
+ UINT8 *out = imOut->image8[y];
+ x = 0;
+ for (; x < imIn->xsize - 3; x += 4) {
+ UINT32 v = MAKE_UINT32(in[0], in[4], in[8], in[12]);
+ memcpy(out + x, &v, sizeof(v));
+ in += 16;
+ }
+ for (; x < imIn->xsize; x++) {
+ out[x] = *in;
+ in += 4;
+ }
+ }
+
+ return imOut;
+}
+
+int
+ImagingSplit(Imaging imIn, Imaging bands[4]) {
+ int i, j, x, y;
+
+ /* Check arguments */
+ if (!imIn || imIn->type != IMAGING_TYPE_UINT8) {
+ (void)ImagingError_ModeError();
+ return 0;
+ }
+
+ /* Shortcuts */
+ if (imIn->bands == 1) {
+ bands[0] = ImagingCopy(imIn);
+ return imIn->bands;
+ }
+
+ for (i = 0; i < imIn->bands; i++) {
+ bands[i] = ImagingNewDirty("L", imIn->xsize, imIn->ysize);
+ if (!bands[i]) {
+ for (j = 0; j < i; ++j) {
+ ImagingDelete(bands[j]);
+ }
+ return 0;
+ }
+ }
+
+ /* Extract bands from image */
+ if (imIn->bands == 2) {
+ for (y = 0; y < imIn->ysize; y++) {
+ UINT8 *in = (UINT8 *)imIn->image[y];
+ UINT8 *out0 = bands[0]->image8[y];
+ UINT8 *out1 = bands[1]->image8[y];
+ x = 0;
+ for (; x < imIn->xsize - 3; x += 4) {
+ UINT32 v = MAKE_UINT32(in[0], in[4], in[8], in[12]);
+ memcpy(out0 + x, &v, sizeof(v));
+ v = MAKE_UINT32(in[0 + 3], in[4 + 3], in[8 + 3], in[12 + 3]);
+ memcpy(out1 + x, &v, sizeof(v));
+ in += 16;
+ }
+ for (; x < imIn->xsize; x++) {
+ out0[x] = in[0];
+ out1[x] = in[3];
+ in += 4;
+ }
+ }
+ } else if (imIn->bands == 3) {
+ for (y = 0; y < imIn->ysize; y++) {
+ UINT8 *in = (UINT8 *)imIn->image[y];
+ UINT8 *out0 = bands[0]->image8[y];
+ UINT8 *out1 = bands[1]->image8[y];
+ UINT8 *out2 = bands[2]->image8[y];
+ x = 0;
+ for (; x < imIn->xsize - 3; x += 4) {
+ UINT32 v = MAKE_UINT32(in[0], in[4], in[8], in[12]);
+ memcpy(out0 + x, &v, sizeof(v));
+ v = MAKE_UINT32(in[0 + 1], in[4 + 1], in[8 + 1], in[12 + 1]);
+ memcpy(out1 + x, &v, sizeof(v));
+ v = MAKE_UINT32(in[0 + 2], in[4 + 2], in[8 + 2], in[12 + 2]);
+ memcpy(out2 + x, &v, sizeof(v));
+ in += 16;
+ }
+ for (; x < imIn->xsize; x++) {
+ out0[x] = in[0];
+ out1[x] = in[1];
+ out2[x] = in[2];
+ in += 4;
+ }
+ }
+ } else {
+ for (y = 0; y < imIn->ysize; y++) {
+ UINT8 *in = (UINT8 *)imIn->image[y];
+ UINT8 *out0 = bands[0]->image8[y];
+ UINT8 *out1 = bands[1]->image8[y];
+ UINT8 *out2 = bands[2]->image8[y];
+ UINT8 *out3 = bands[3]->image8[y];
+ x = 0;
+ for (; x < imIn->xsize - 3; x += 4) {
+ UINT32 v = MAKE_UINT32(in[0], in[4], in[8], in[12]);
+ memcpy(out0 + x, &v, sizeof(v));
+ v = MAKE_UINT32(in[0 + 1], in[4 + 1], in[8 + 1], in[12 + 1]);
+ memcpy(out1 + x, &v, sizeof(v));
+ v = MAKE_UINT32(in[0 + 2], in[4 + 2], in[8 + 2], in[12 + 2]);
+ memcpy(out2 + x, &v, sizeof(v));
+ v = MAKE_UINT32(in[0 + 3], in[4 + 3], in[8 + 3], in[12 + 3]);
+ memcpy(out3 + x, &v, sizeof(v));
+ in += 16;
+ }
+ for (; x < imIn->xsize; x++) {
+ out0[x] = in[0];
+ out1[x] = in[1];
+ out2[x] = in[2];
+ out3[x] = in[3];
+ in += 4;
+ }
+ }
+ }
+
+ return imIn->bands;
+}
+
+Imaging
+ImagingPutBand(Imaging imOut, Imaging imIn, int band) {
+ int x, y;
+
+ /* Check arguments */
+ if (!imIn || imIn->bands != 1 || !imOut) {
+ return (Imaging)ImagingError_ModeError();
+ }
+
+ if (band < 0 || band >= imOut->bands) {
+ return (Imaging)ImagingError_ValueError("band index out of range");
+ }
+
+ if (imIn->type != imOut->type || imIn->xsize != imOut->xsize ||
+ imIn->ysize != imOut->ysize) {
+ return (Imaging)ImagingError_Mismatch();
+ }
+
+ /* Shortcuts */
+ if (imOut->bands == 1) {
+ return ImagingCopy2(imOut, imIn);
+ }
+
+ /* Special case for LXXA etc */
+ if (imOut->bands == 2 && band == 1) {
+ band = 3;
+ }
+
+ /* Insert band into image */
+ for (y = 0; y < imIn->ysize; y++) {
+ UINT8 *in = imIn->image8[y];
+ UINT8 *out = (UINT8 *)imOut->image[y] + band;
+ for (x = 0; x < imIn->xsize; x++) {
+ *out = in[x];
+ out += 4;
+ }
+ }
+
+ return imOut;
+}
+
+Imaging
+ImagingFillBand(Imaging imOut, int band, int color) {
+ int x, y;
+
+ /* Check arguments */
+ if (!imOut || imOut->type != IMAGING_TYPE_UINT8) {
+ return (Imaging)ImagingError_ModeError();
+ }
+
+ if (band < 0 || band >= imOut->bands) {
+ return (Imaging)ImagingError_ValueError("band index out of range");
+ }
+
+ /* Special case for LXXA etc */
+ if (imOut->bands == 2 && band == 1) {
+ band = 3;
+ }
+
+ color = CLIP8(color);
+
+ /* Insert color into image */
+ for (y = 0; y < imOut->ysize; y++) {
+ UINT8 *out = (UINT8 *)imOut->image[y] + band;
+ for (x = 0; x < imOut->xsize; x++) {
+ *out = (UINT8)color;
+ out += 4;
+ }
+ }
+
+ return imOut;
+}
+
+Imaging
+ImagingMerge(const char *mode, Imaging bands[4]) {
+ int i, x, y;
+ int bandsCount = 0;
+ Imaging imOut;
+ Imaging firstBand;
+
+ firstBand = bands[0];
+ if (!firstBand) {
+ return (Imaging)ImagingError_ValueError("wrong number of bands");
+ }
+
+ for (i = 0; i < 4; ++i) {
+ if (!bands[i]) {
+ break;
+ }
+ if (bands[i]->bands != 1) {
+ return (Imaging)ImagingError_ModeError();
+ }
+ if (bands[i]->xsize != firstBand->xsize ||
+ bands[i]->ysize != firstBand->ysize) {
+ return (Imaging)ImagingError_Mismatch();
+ }
+ }
+ bandsCount = i;
+
+ imOut = ImagingNewDirty(mode, firstBand->xsize, firstBand->ysize);
+ if (!imOut) {
+ return NULL;
+ }
+
+ if (imOut->bands != bandsCount) {
+ ImagingDelete(imOut);
+ return (Imaging)ImagingError_ValueError("wrong number of bands");
+ }
+
+ if (imOut->bands == 1) {
+ return ImagingCopy2(imOut, firstBand);
+ }
+
+ if (imOut->bands == 2) {
+ for (y = 0; y < imOut->ysize; y++) {
+ UINT8 *in0 = bands[0]->image8[y];
+ UINT8 *in1 = bands[1]->image8[y];
+ UINT32 *out = (UINT32 *)imOut->image32[y];
+ for (x = 0; x < imOut->xsize; x++) {
+ out[x] = MAKE_UINT32(in0[x], 0, 0, in1[x]);
+ }
+ }
+ } else if (imOut->bands == 3) {
+ for (y = 0; y < imOut->ysize; y++) {
+ UINT8 *in0 = bands[0]->image8[y];
+ UINT8 *in1 = bands[1]->image8[y];
+ UINT8 *in2 = bands[2]->image8[y];
+ UINT32 *out = (UINT32 *)imOut->image32[y];
+ for (x = 0; x < imOut->xsize; x++) {
+ out[x] = MAKE_UINT32(in0[x], in1[x], in2[x], 0);
+ }
+ }
+ } else if (imOut->bands == 4) {
+ for (y = 0; y < imOut->ysize; y++) {
+ UINT8 *in0 = bands[0]->image8[y];
+ UINT8 *in1 = bands[1]->image8[y];
+ UINT8 *in2 = bands[2]->image8[y];
+ UINT8 *in3 = bands[3]->image8[y];
+ UINT32 *out = (UINT32 *)imOut->image32[y];
+ for (x = 0; x < imOut->xsize; x++) {
+ out[x] = MAKE_UINT32(in0[x], in1[x], in2[x], in3[x]);
+ }
+ }
+ }
+
+ return imOut;
+}
diff --git a/contrib/python/Pillow/py3/libImaging/Bcn.h b/contrib/python/Pillow/py3/libImaging/Bcn.h
new file mode 100644
index 00000000000..1a6fbee4576
--- /dev/null
+++ b/contrib/python/Pillow/py3/libImaging/Bcn.h
@@ -0,0 +1,3 @@
+typedef struct {
+ char *pixel_format;
+} BCNSTATE;
diff --git a/contrib/python/Pillow/py3/libImaging/BcnDecode.c b/contrib/python/Pillow/py3/libImaging/BcnDecode.c
new file mode 100644
index 00000000000..5e4296eeba1
--- /dev/null
+++ b/contrib/python/Pillow/py3/libImaging/BcnDecode.c
@@ -0,0 +1,897 @@
+/*
+ * The Python Imaging Library
+ *
+ * decoder for DXTn-compressed data
+ *
+ * Format 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 in the public domain (CC0)
+ * Full text of the CC0 license:
+ * https://creativecommons.org/publicdomain/zero/1.0/
+ */
+
+#include "Imaging.h"
+
+#include "Bcn.h"
+
+typedef struct {
+ UINT8 r, g, b, a;
+} rgba;
+
+typedef struct {
+ UINT8 l;
+} lum;
+
+typedef struct {
+ UINT16 c0, c1;
+ UINT32 lut;
+} bc1_color;
+
+typedef struct {
+ UINT8 a0, a1;
+ UINT8 lut[6];
+} bc3_alpha;
+
+typedef struct {
+ INT8 a0, a1;
+ UINT8 lut[6];
+} bc5s_alpha;
+
+#define LOAD16(p) (p)[0] | ((p)[1] << 8)
+
+#define LOAD32(p) (p)[0] | ((p)[1] << 8) | ((p)[2] << 16) | ((p)[3] << 24)
+
+static void
+bc1_color_load(bc1_color *dst, const UINT8 *src) {
+ dst->c0 = LOAD16(src);
+ dst->c1 = LOAD16(src + 2);
+ dst->lut = LOAD32(src + 4);
+}
+
+static rgba
+decode_565(UINT16 x) {
+ rgba c;
+ int r, g, b;
+ r = (x & 0xf800) >> 8;
+ r |= r >> 5;
+ c.r = r;
+ g = (x & 0x7e0) >> 3;
+ g |= g >> 6;
+ c.g = g;
+ b = (x & 0x1f) << 3;
+ b |= b >> 5;
+ c.b = b;
+ c.a = 0xff;
+ return c;
+}
+
+static void
+decode_bc1_color(rgba *dst, const UINT8 *src, int separate_alpha) {
+ bc1_color col;
+ rgba p[4];
+ int n, cw;
+ UINT16 r0, g0, b0, r1, g1, b1;
+ bc1_color_load(&col, src);
+
+ p[0] = decode_565(col.c0);
+ r0 = p[0].r;
+ g0 = p[0].g;
+ b0 = p[0].b;
+ p[1] = decode_565(col.c1);
+ r1 = p[1].r;
+ g1 = p[1].g;
+ b1 = p[1].b;
+
+
+ /* NOTE: BC2 and BC3 reuse BC1 color blocks but always act like c0 > c1 */
+ if (col.c0 > col.c1 || separate_alpha) {
+ p[2].r = (2 * r0 + 1 * r1) / 3;
+ p[2].g = (2 * g0 + 1 * g1) / 3;
+ p[2].b = (2 * b0 + 1 * b1) / 3;
+ p[2].a = 0xff;
+ p[3].r = (1 * r0 + 2 * r1) / 3;
+ p[3].g = (1 * g0 + 2 * g1) / 3;
+ p[3].b = (1 * b0 + 2 * b1) / 3;
+ p[3].a = 0xff;
+ } else {
+ p[2].r = (r0 + r1) / 2;
+ p[2].g = (g0 + g1) / 2;
+ p[2].b = (b0 + b1) / 2;
+ p[2].a = 0xff;
+ p[3].r = 0;
+ p[3].g = 0;
+ p[3].b = 0;
+ p[3].a = 0;
+ }
+ for (n = 0; n < 16; n++) {
+ cw = 3 & (col.lut >> (2 * n));
+ dst[n] = p[cw];
+ }
+}
+
+static void
+decode_bc3_alpha(char *dst, const UINT8 *src, int stride, int o, int sign) {
+ UINT16 a0, a1;
+ UINT8 a[8];
+ int n, lut1, lut2, aw;
+ if (sign == 1) {
+ bc5s_alpha b;
+ memcpy(&b, src, sizeof(bc5s_alpha));
+ a0 = b.a0 + 128;
+ a1 = b.a1 + 128;
+ lut1 = b.lut[0] | (b.lut[1] << 8) | (b.lut[2] << 16);
+ lut2 = b.lut[3] | (b.lut[4] << 8) | (b.lut[5] << 16);
+ } else {
+ bc3_alpha b;
+ memcpy(&b, src, sizeof(bc3_alpha));
+ a0 = b.a0;
+ a1 = b.a1;
+ lut1 = b.lut[0] | (b.lut[1] << 8) | (b.lut[2] << 16);
+ lut2 = b.lut[3] | (b.lut[4] << 8) | (b.lut[5] << 16);
+ }
+
+ a[0] = (UINT8)a0;
+ a[1] = (UINT8)a1;
+ if (a0 > a1) {
+ a[2] = (6 * a0 + 1 * a1) / 7;
+ a[3] = (5 * a0 + 2 * a1) / 7;
+ a[4] = (4 * a0 + 3 * a1) / 7;
+ a[5] = (3 * a0 + 4 * a1) / 7;
+ a[6] = (2 * a0 + 5 * a1) / 7;
+ a[7] = (1 * a0 + 6 * a1) / 7;
+ } else {
+ a[2] = (4 * a0 + 1 * a1) / 5;
+ a[3] = (3 * a0 + 2 * a1) / 5;
+ a[4] = (2 * a0 + 3 * a1) / 5;
+ a[5] = (1 * a0 + 4 * a1) / 5;
+ a[6] = 0;
+ a[7] = 0xff;
+ }
+ for (n = 0; n < 8; n++) {
+ aw = 7 & (lut1 >> (3 * n));
+ dst[stride * n + o] = a[aw];
+ }
+ for (n = 0; n < 8; n++) {
+ aw = 7 & (lut2 >> (3 * n));
+ dst[stride * (8 + n) + o] = a[aw];
+ }
+}
+
+static void
+decode_bc1_block(rgba *col, const UINT8 *src) {
+ decode_bc1_color(col, src, 0);
+}
+
+static void
+decode_bc2_block(rgba *col, const UINT8 *src) {
+ int n, bitI, byI, av;
+ decode_bc1_color(col, src + 8, 1);
+ for (n = 0; n < 16; n++) {
+ bitI = n * 4;
+ byI = bitI >> 3;
+ av = 0xf & (src[byI] >> (bitI & 7));
+ av = (av << 4) | av;
+ col[n].a = av;
+ }
+}
+
+static void
+decode_bc3_block(rgba *col, const UINT8 *src) {
+ decode_bc1_color(col, src + 8, 1);
+ decode_bc3_alpha((char *)col, src, sizeof(col[0]), 3, 0);
+}
+
+static void
+decode_bc4_block(lum *col, const UINT8 *src) {
+ decode_bc3_alpha((char *)col, src, sizeof(col[0]), 0, 0);
+}
+
+static void
+decode_bc5_block(rgba *col, const UINT8 *src, int sign) {
+ decode_bc3_alpha((char *)col, src, sizeof(col[0]), 0, sign);
+ decode_bc3_alpha((char *)col, src + 8, sizeof(col[0]), 1, sign);
+}
+
+/* BC6 and BC7 are described here:
+ https://www.khronos.org/registry/OpenGL/extensions/ARB/ARB_texture_compression_bptc.txt
+ */
+
+static UINT8
+get_bit(const UINT8 *src, int bit) {
+ int by = bit >> 3;
+ bit &= 7;
+ return (src[by] >> bit) & 1;
+}
+
+static UINT8
+get_bits(const UINT8 *src, int bit, int count) {
+ UINT8 v;
+ int x;
+ int by = bit >> 3;
+ bit &= 7;
+ if (!count) {
+ return 0;
+ }
+ if (bit + count <= 8) {
+ v = (src[by] >> bit) & ((1 << count) - 1);
+ } else {
+ x = src[by] | (src[by + 1] << 8);
+ v = (x >> bit) & ((1 << count) - 1);
+ }
+ return v;
+}
+
+/* BC7 */
+typedef struct {
+ char ns;
+ char pb;
+ char rb;
+ char isb;
+ char cb;
+ char ab;
+ char epb;
+ char spb;
+ char ib;
+ char ib2;
+} bc7_mode_info;
+
+static const bc7_mode_info bc7_modes[] = {
+ {3, 4, 0, 0, 4, 0, 1, 0, 3, 0},
+ {2, 6, 0, 0, 6, 0, 0, 1, 3, 0},
+ {3, 6, 0, 0, 5, 0, 0, 0, 2, 0},
+ {2, 6, 0, 0, 7, 0, 1, 0, 2, 0},
+ {1, 0, 2, 1, 5, 6, 0, 0, 2, 3},
+ {1, 0, 2, 0, 7, 8, 0, 0, 2, 2},
+ {1, 0, 0, 0, 7, 7, 1, 0, 4, 0},
+ {2, 6, 0, 0, 5, 5, 1, 0, 2, 0}};
+
+/* Subset indices:
+ Table.P2, 1 bit per index */
+static const UINT16 bc7_si2[] = {
+ 0xcccc, 0x8888, 0xeeee, 0xecc8, 0xc880, 0xfeec, 0xfec8, 0xec80, 0xc800, 0xffec,
+ 0xfe80, 0xe800, 0xffe8, 0xff00, 0xfff0, 0xf000, 0xf710, 0x008e, 0x7100, 0x08ce,
+ 0x008c, 0x7310, 0x3100, 0x8cce, 0x088c, 0x3110, 0x6666, 0x366c, 0x17e8, 0x0ff0,
+ 0x718e, 0x399c, 0xaaaa, 0xf0f0, 0x5a5a, 0x33cc, 0x3c3c, 0x55aa, 0x9696, 0xa55a,
+ 0x73ce, 0x13c8, 0x324c, 0x3bdc, 0x6996, 0xc33c, 0x9966, 0x0660, 0x0272, 0x04e4,
+ 0x4e40, 0x2720, 0xc936, 0x936c, 0x39c6, 0x639c, 0x9336, 0x9cc6, 0x817e, 0xe718,
+ 0xccf0, 0x0fcc, 0x7744, 0xee22};
+
+/* Table.P3, 2 bits per index */
+static const UINT32 bc7_si3[] = {
+ 0xaa685050, 0x6a5a5040, 0x5a5a4200, 0x5450a0a8, 0xa5a50000, 0xa0a05050, 0x5555a0a0,
+ 0x5a5a5050, 0xaa550000, 0xaa555500, 0xaaaa5500, 0x90909090, 0x94949494, 0xa4a4a4a4,
+ 0xa9a59450, 0x2a0a4250, 0xa5945040, 0x0a425054, 0xa5a5a500, 0x55a0a0a0, 0xa8a85454,
+ 0x6a6a4040, 0xa4a45000, 0x1a1a0500, 0x0050a4a4, 0xaaa59090, 0x14696914, 0x69691400,
+ 0xa08585a0, 0xaa821414, 0x50a4a450, 0x6a5a0200, 0xa9a58000, 0x5090a0a8, 0xa8a09050,
+ 0x24242424, 0x00aa5500, 0x24924924, 0x24499224, 0x50a50a50, 0x500aa550, 0xaaaa4444,
+ 0x66660000, 0xa5a0a5a0, 0x50a050a0, 0x69286928, 0x44aaaa44, 0x66666600, 0xaa444444,
+ 0x54a854a8, 0x95809580, 0x96969600, 0xa85454a8, 0x80959580, 0xaa141414, 0x96960000,
+ 0xaaaa1414, 0xa05050a0, 0xa0a5a5a0, 0x96000000, 0x40804080, 0xa9a8a9a8, 0xaaaaaa44,
+ 0x2a4a5254};
+
+/* Anchor indices:
+ Table.A2 */
+static const char bc7_ai0[] = {
+ 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 2, 8, 2, 2, 8,
+ 8, 15, 2, 8, 2, 2, 8, 8, 2, 2, 15, 15, 6, 8, 2, 8, 15, 15, 2, 8, 2, 2,
+ 2, 15, 15, 6, 6, 2, 6, 8, 15, 15, 2, 2, 15, 15, 15, 15, 15, 2, 2, 15};
+
+/* Table.A3a */
+static const char bc7_ai1[] = {
+ 3, 3, 15, 15, 8, 3, 15, 15, 8, 8, 6, 6, 6, 5, 3, 3, 3, 3, 8, 15, 3, 3,
+ 6, 10, 5, 8, 8, 6, 8, 5, 15, 15, 8, 15, 3, 5, 6, 10, 8, 15, 15, 3, 15, 5,
+ 15, 15, 15, 15, 3, 15, 5, 5, 5, 8, 5, 10, 5, 10, 8, 13, 15, 12, 3, 3};
+
+/* Table.A3b */
+static const char bc7_ai2[] = {15, 8, 8, 3, 15, 15, 3, 8, 15, 15, 15, 15, 15,
+ 15, 15, 8, 15, 8, 15, 3, 15, 8, 15, 8, 3, 15,
+ 6, 10, 15, 15, 10, 8, 15, 3, 15, 10, 10, 8, 9,
+ 10, 6, 15, 8, 15, 3, 6, 6, 8, 15, 3, 15, 15,
+ 15, 15, 15, 15, 15, 15, 15, 15, 3, 15, 15, 8};
+
+/* Interpolation weights */
+static const char bc7_weights2[] = {0, 21, 43, 64};
+static const char bc7_weights3[] = {0, 9, 18, 27, 37, 46, 55, 64};
+static const char bc7_weights4[] = {
+ 0, 4, 9, 13, 17, 21, 26, 30, 34, 38, 43, 47, 51, 55, 60, 64};
+
+static const char *
+bc7_get_weights(int n) {
+ if (n == 2) {
+ return bc7_weights2;
+ }
+ if (n == 3) {
+ return bc7_weights3;
+ }
+ return bc7_weights4;
+}
+
+static int
+bc7_get_subset(int ns, int partition, int n) {
+ if (ns == 2) {
+ return 1 & (bc7_si2[partition] >> n);
+ }
+ if (ns == 3) {
+ return 3 & (bc7_si3[partition] >> (2 * n));
+ }
+ return 0;
+}
+
+static UINT8
+expand_quantized(UINT8 v, int bits) {
+ v = v << (8 - bits);
+ return v | (v >> bits);
+}
+
+static void
+bc7_lerp(rgba *dst, const rgba *e, int s0, int s1) {
+ int t0 = 64 - s0;
+ int t1 = 64 - s1;
+ dst->r = (UINT8)((t0 * e[0].r + s0 * e[1].r + 32) >> 6);
+ dst->g = (UINT8)((t0 * e[0].g + s0 * e[1].g + 32) >> 6);
+ dst->b = (UINT8)((t0 * e[0].b + s0 * e[1].b + 32) >> 6);
+ dst->a = (UINT8)((t1 * e[0].a + s1 * e[1].a + 32) >> 6);
+}
+
+static void
+decode_bc7_block(rgba *col, const UINT8 *src) {
+ rgba endpoints[6];
+ int bit = 0, cibit, aibit;
+ int mode = src[0];
+ int i, j;
+ int numep, cb, ab, ib, ib2, i0, i1, s;
+ UINT8 index_sel, partition, rotation, val;
+ const char *cw, *aw;
+ const bc7_mode_info *info;
+
+ /* mode is the number of unset bits before the first set bit: */
+ if (!mode) {
+ /* degenerate case when no bits set */
+ for (i = 0; i < 16; i++) {
+ col[i].r = col[i].g = col[i].b = 0;
+ col[i].a = 255;
+ }
+ return;
+ }
+ while (!(mode & (1 << bit++)))
+ ;
+ mode = bit - 1;
+ info = &bc7_modes[mode];
+ /* color selection bits: {subset}{endpoint} */
+ cb = info->cb;
+ ab = info->ab;
+ cw = bc7_get_weights(info->ib);
+ aw = bc7_get_weights((ab && info->ib2) ? info->ib2 : info->ib);
+
+#define LOAD(DST, N) \
+ DST = get_bits(src, bit, N); \
+ bit += N;
+ LOAD(partition, info->pb);
+ LOAD(rotation, info->rb);
+ LOAD(index_sel, info->isb);
+ numep = info->ns << 1;
+
+ /* red */
+ for (i = 0; i < numep; i++) {
+ LOAD(val, cb);
+ endpoints[i].r = val;
+ }
+
+ /* green */
+ for (i = 0; i < numep; i++) {
+ LOAD(val, cb);
+ endpoints[i].g = val;
+ }
+
+ /* blue */
+ for (i = 0; i < numep; i++) {
+ LOAD(val, cb);
+ endpoints[i].b = val;
+ }
+
+ /* alpha */
+ for (i = 0; i < numep; i++) {
+ if (ab) {
+ LOAD(val, ab);
+ } else {
+ val = 255;
+ }
+ endpoints[i].a = val;
+ }
+
+ /* p-bits */
+#define ASSIGN_P(x) x = (x << 1) | val
+ if (info->epb) {
+ /* per endpoint */
+ cb++;
+ if (ab) {
+ ab++;
+ }
+ for (i = 0; i < numep; i++) {
+ LOAD(val, 1);
+ ASSIGN_P(endpoints[i].r);
+ ASSIGN_P(endpoints[i].g);
+ ASSIGN_P(endpoints[i].b);
+ if (ab) {
+ ASSIGN_P(endpoints[i].a);
+ }
+ }
+ }
+ if (info->spb) {
+ /* per subset */
+ cb++;
+ if (ab) {
+ ab++;
+ }
+ for (i = 0; i < numep; i += 2) {
+ LOAD(val, 1);
+ for (j = 0; j < 2; j++) {
+ ASSIGN_P(endpoints[i + j].r);
+ ASSIGN_P(endpoints[i + j].g);
+ ASSIGN_P(endpoints[i + j].b);
+ if (ab) {
+ ASSIGN_P(endpoints[i + j].a);
+ }
+ }
+ }
+ }
+#undef ASSIGN_P
+#define EXPAND(x, b) x = expand_quantized(x, b)
+ for (i = 0; i < numep; i++) {
+ EXPAND(endpoints[i].r, cb);
+ EXPAND(endpoints[i].g, cb);
+ EXPAND(endpoints[i].b, cb);
+ if (ab) {
+ EXPAND(endpoints[i].a, ab);
+ }
+ }
+#undef EXPAND
+#undef LOAD
+ cibit = bit;
+ aibit = cibit + 16 * info->ib - info->ns;
+ for (i = 0; i < 16; i++) {
+ s = bc7_get_subset(info->ns, partition, i) << 1;
+ ib = info->ib;
+ if (i == 0) {
+ ib--;
+ } else if (info->ns == 2) {
+ if (i == bc7_ai0[partition]) {
+ ib--;
+ }
+ } else if (info->ns == 3) {
+ if (i == bc7_ai1[partition]) {
+ ib--;
+ } else if (i == bc7_ai2[partition]) {
+ ib--;
+ }
+ }
+ i0 = get_bits(src, cibit, ib);
+ cibit += ib;
+
+ if (ab && info->ib2) {
+ ib2 = info->ib2;
+ if (ib2 && i == 0) {
+ ib2--;
+ }
+ i1 = get_bits(src, aibit, ib2);
+ aibit += ib2;
+ if (index_sel) {
+ bc7_lerp(&col[i], &endpoints[s], aw[i1], cw[i0]);
+ } else {
+ bc7_lerp(&col[i], &endpoints[s], cw[i0], aw[i1]);
+ }
+ } else {
+ bc7_lerp(&col[i], &endpoints[s], cw[i0], cw[i0]);
+ }
+#define ROTATE(x, y) \
+ val = x; \
+ x = y; \
+ y = val
+ if (rotation == 1) {
+ ROTATE(col[i].r, col[i].a);
+ } else if (rotation == 2) {
+ ROTATE(col[i].g, col[i].a);
+ } else if (rotation == 3) {
+ ROTATE(col[i].b, col[i].a);
+ }
+#undef ROTATE
+ }
+}
+
+/* BC6 */
+typedef struct {
+ char ns; /* number of subsets (also called regions) */
+ char tr; /* whether endpoints are delta-compressed */
+ char pb; /* partition bits */
+ char epb; /* endpoint bits */
+ char rb; /* red bits (delta) */
+ char gb; /* green bits (delta) */
+ char bb; /* blue bits (delta) */
+} bc6_mode_info;
+
+static const bc6_mode_info bc6_modes[] = {
+ // 00
+ {2, 1, 5, 10, 5, 5, 5},
+ // 01
+ {2, 1, 5, 7, 6, 6, 6},
+ // 10
+ {2, 1, 5, 11, 5, 4, 4},
+ {2, 1, 5, 11, 4, 5, 4},
+ {2, 1, 5, 11, 4, 4, 5},
+ {2, 1, 5, 9, 5, 5, 5},
+ {2, 1, 5, 8, 6, 5, 5},
+ {2, 1, 5, 8, 5, 6, 5},
+ {2, 1, 5, 8, 5, 5, 6},
+ {2, 0, 5, 6, 6, 6, 6},
+ // 11
+ {1, 0, 0, 10, 10, 10, 10},
+ {1, 1, 0, 11, 9, 9, 9},
+ {1, 1, 0, 12, 8, 8, 8},
+ {1, 1, 0, 16, 4, 4, 4}};
+
+/* Table.F, encoded as a sequence of bit indices */
+static const UINT8 bc6_bit_packings[][75] = {
+ {116, 132, 180, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 16, 17,
+ 18, 19, 20, 21, 22, 23, 24, 25, 32, 33, 34, 35, 36, 37, 38,
+ 39, 40, 41, 48, 49, 50, 51, 52, 164, 112, 113, 114, 115, 64, 65,
+ 66, 67, 68, 176, 160, 161, 162, 163, 80, 81, 82, 83, 84, 177, 128,
+ 129, 130, 131, 96, 97, 98, 99, 100, 178, 144, 145, 146, 147, 148, 179},
+ {117, 164, 165, 0, 1, 2, 3, 4, 5, 6, 176, 177, 132, 16, 17,
+ 18, 19, 20, 21, 22, 133, 178, 116, 32, 33, 34, 35, 36, 37, 38,
+ 179, 181, 180, 48, 49, 50, 51, 52, 53, 112, 113, 114, 115, 64, 65,
+ 66, 67, 68, 69, 160, 161, 162, 163, 80, 81, 82, 83, 84, 85, 128,
+ 129, 130, 131, 96, 97, 98, 99, 100, 101, 144, 145, 146, 147, 148, 149},
+ {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 16, 17, 18, 19, 20,
+ 21, 22, 23, 24, 25, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41,
+ 48, 49, 50, 51, 52, 10, 112, 113, 114, 115, 64, 65, 66, 67, 26,
+ 176, 160, 161, 162, 163, 80, 81, 82, 83, 42, 177, 128, 129, 130, 131,
+ 96, 97, 98, 99, 100, 178, 144, 145, 146, 147, 148, 179},
+ {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 16, 17, 18, 19, 20,
+ 21, 22, 23, 24, 25, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41,
+ 48, 49, 50, 51, 10, 164, 112, 113, 114, 115, 64, 65, 66, 67, 68,
+ 26, 160, 161, 162, 163, 80, 81, 82, 83, 42, 177, 128, 129, 130, 131,
+ 96, 97, 98, 99, 176, 178, 144, 145, 146, 147, 116, 179},
+ {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 16, 17, 18, 19, 20,
+ 21, 22, 23, 24, 25, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41,
+ 48, 49, 50, 51, 10, 132, 112, 113, 114, 115, 64, 65, 66, 67, 26,
+ 176, 160, 161, 162, 163, 80, 81, 82, 83, 84, 42, 128, 129, 130, 131,
+ 96, 97, 98, 99, 177, 178, 144, 145, 146, 147, 180, 179},
+ {0, 1, 2, 3, 4, 5, 6, 7, 8, 132, 16, 17, 18, 19, 20,
+ 21, 22, 23, 24, 116, 32, 33, 34, 35, 36, 37, 38, 39, 40, 180,
+ 48, 49, 50, 51, 52, 164, 112, 113, 114, 115, 64, 65, 66, 67, 68,
+ 176, 160, 161, 162, 163, 80, 81, 82, 83, 84, 177, 128, 129, 130, 131,
+ 96, 97, 98, 99, 100, 178, 144, 145, 146, 147, 148, 179},
+ {0, 1, 2, 3, 4, 5, 6, 7, 164, 132, 16, 17, 18, 19, 20,
+ 21, 22, 23, 178, 116, 32, 33, 34, 35, 36, 37, 38, 39, 179, 180,
+ 48, 49, 50, 51, 52, 53, 112, 113, 114, 115, 64, 65, 66, 67, 68,
+ 176, 160, 161, 162, 163, 80, 81, 82, 83, 84, 177, 128, 129, 130, 131,
+ 96, 97, 98, 99, 100, 101, 144, 145, 146, 147, 148, 149},
+ {0, 1, 2, 3, 4, 5, 6, 7, 176, 132, 16, 17, 18, 19, 20,
+ 21, 22, 23, 117, 116, 32, 33, 34, 35, 36, 37, 38, 39, 165, 180,
+ 48, 49, 50, 51, 52, 164, 112, 113, 114, 115, 64, 65, 66, 67, 68,
+ 69, 160, 161, 162, 163, 80, 81, 82, 83, 84, 177, 128, 129, 130, 131,
+ 96, 97, 98, 99, 100, 178, 144, 145, 146, 147, 148, 179},
+ {0, 1, 2, 3, 4, 5, 6, 7, 177, 132, 16, 17, 18, 19, 20,
+ 21, 22, 23, 133, 116, 32, 33, 34, 35, 36, 37, 38, 39, 181, 180,
+ 48, 49, 50, 51, 52, 164, 112, 113, 114, 115, 64, 65, 66, 67, 68,
+ 176, 160, 161, 162, 163, 80, 81, 82, 83, 84, 85, 128, 129, 130, 131,
+ 96, 97, 98, 99, 100, 178, 144, 145, 146, 147, 148, 179},
+ {0, 1, 2, 3, 4, 5, 164, 176, 177, 132, 16, 17, 18, 19, 20,
+ 21, 117, 133, 178, 116, 32, 33, 34, 35, 36, 37, 165, 179, 181, 180,
+ 48, 49, 50, 51, 52, 53, 112, 113, 114, 115, 64, 65, 66, 67, 68,
+ 69, 160, 161, 162, 163, 80, 81, 82, 83, 84, 85, 128, 129, 130, 131,
+ 96, 97, 98, 99, 100, 101, 144, 145, 146, 147, 148, 149},
+ {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25,
+ 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57,
+ 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 80, 81, 82, 83, 84, 85, 86, 87, 88, 89},
+ {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25,
+ 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 48, 49, 50, 51, 52, 53, 54, 55, 56, 10,
+ 64, 65, 66, 67, 68, 69, 70, 71, 72, 26, 80, 81, 82, 83, 84, 85, 86, 87, 88, 42},
+ {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25,
+ 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 48, 49, 50, 51, 52, 53, 54, 55, 11, 10,
+ 64, 65, 66, 67, 68, 69, 70, 71, 27, 26, 80, 81, 82, 83, 84, 85, 86, 87, 43, 42},
+ {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25,
+ 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 48, 49, 50, 51, 15, 14, 13, 12, 11, 10,
+ 64, 65, 66, 67, 31, 30, 29, 28, 27, 26, 80, 81, 82, 83, 47, 46, 45, 44, 43, 42}};
+
+static void
+bc6_sign_extend(UINT16 *v, int prec) {
+ int x = *v;
+ if (x & (1 << (prec - 1))) {
+ x |= -1 << prec;
+ }
+ *v = (UINT16)x;
+}
+
+static int
+bc6_unquantize(UINT16 v, int prec, int sign) {
+ int s = 0;
+ int x;
+ if (!sign) {
+ x = v;
+ if (prec >= 15) {
+ return x;
+ }
+ if (x == 0) {
+ return 0;
+ }
+ if (x == ((1 << prec) - 1)) {
+ return 0xffff;
+ }
+ return ((x << 15) + 0x4000) >> (prec - 1);
+ } else {
+ x = (INT16)v;
+ if (prec >= 16) {
+ return x;
+ }
+ if (x < 0) {
+ s = 1;
+ x = -x;
+ }
+
+ if (x != 0) {
+ if (x >= ((1 << (prec - 1)) - 1)) {
+ x = 0x7fff;
+ } else {
+ x = ((x << 15) + 0x4000) >> (prec - 1);
+ }
+ }
+
+ if (s) {
+ return -x;
+ }
+ return x;
+ }
+}
+
+static float
+half_to_float(UINT16 h) {
+ /* https://gist.github.com/rygorous/2144712 */
+ union {
+ UINT32 u;
+ float f;
+ } o, m;
+ m.u = 0x77800000;
+ o.u = (h & 0x7fff) << 13;
+ o.f *= m.f;
+ m.u = 0x47800000;
+ if (o.f >= m.f) {
+ o.u |= 255 << 23;
+ }
+ o.u |= (h & 0x8000) << 16;
+ return o.f;
+}
+
+static float
+bc6_finalize(int v, int sign) {
+ if (sign) {
+ if (v < 0) {
+ v = ((-v) * 31) / 32;
+ return half_to_float((UINT16)(0x8000 | v));
+ } else {
+ return half_to_float((UINT16)((v * 31) / 32));
+ }
+ } else {
+ return half_to_float((UINT16)((v * 31) / 64));
+ }
+}
+
+static UINT8
+bc6_clamp(float value) {
+ if (value < 0.0f) {
+ return 0;
+ } else if (value > 1.0f) {
+ return 255;
+ } else {
+ return (UINT8) (value * 255.0f);
+ }
+}
+
+static void
+bc6_lerp(rgba *col, int *e0, int *e1, int s, int sign) {
+ int r, g, b;
+ int t = 64 - s;
+ r = (e0[0] * t + e1[0] * s) >> 6;
+ g = (e0[1] * t + e1[1] * s) >> 6;
+ b = (e0[2] * t + e1[2] * s) >> 6;
+ col->r = bc6_clamp(bc6_finalize(r, sign));
+ col->g = bc6_clamp(bc6_finalize(g, sign));
+ col->b = bc6_clamp(bc6_finalize(b, sign));
+}
+
+static void
+decode_bc6_block(rgba *col, const UINT8 *src, int sign) {
+ UINT16 endpoints[12]; /* storage for r0, g0, b0, r1, ... */
+ int ueps[12];
+ int i, i0, ib2, di, dw, mask, numep, s;
+ UINT8 partition;
+ const bc6_mode_info *info;
+ const char *cw;
+ int bit = 5;
+ int epbits = 75;
+ int ib = 3;
+ int mode = src[0] & 0x1f;
+ if ((mode & 3) == 0 || (mode & 3) == 1) {
+ mode &= 3;
+ bit = 2;
+ } else if ((mode & 3) == 2) {
+ mode = 2 + (mode >> 2);
+ epbits = 72;
+ } else {
+ mode = 10 + (mode >> 2);
+ epbits = 60;
+ ib = 4;
+ }
+ if (mode >= 14) {
+ /* invalid block */
+ memset(col, 0, 16 * sizeof(col[0]));
+ return;
+ }
+ info = &bc6_modes[mode];
+ cw = bc7_get_weights(ib);
+ numep = info->ns == 2 ? 12 : 6;
+ for (i = 0; i < 12; i++) {
+ endpoints[i] = 0;
+ }
+ for (i = 0; i < epbits; i++) {
+ di = bc6_bit_packings[mode][i];
+ dw = di >> 4;
+ di &= 15;
+ endpoints[dw] |= (UINT16)get_bit(src, bit + i) << di;
+ }
+ bit += epbits;
+ partition = get_bits(src, bit, info->pb);
+ bit += info->pb;
+ mask = (1 << info->epb) - 1;
+ if (sign) { /* sign-extend e0 if signed */
+ bc6_sign_extend(&endpoints[0], info->epb);
+ bc6_sign_extend(&endpoints[1], info->epb);
+ bc6_sign_extend(&endpoints[2], info->epb);
+ }
+ if (sign || info->tr) { /* sign-extend e1,2,3 if signed or deltas */
+ for (i = 3; i < numep; i += 3) {
+ bc6_sign_extend(&endpoints[i], info->rb);
+ bc6_sign_extend(&endpoints[i + 1], info->gb);
+ bc6_sign_extend(&endpoints[i + 2], info->bb);
+ }
+ }
+ if (info->tr) { /* apply deltas */
+ for (i = 3; i < numep; i += 3) {
+ endpoints[i] = (endpoints[i] + endpoints[0]) & mask;
+ endpoints[i + 1] = (endpoints[i + 1] + endpoints[1]) & mask;
+ endpoints[i + 2] = (endpoints[i + 2] + endpoints[2]) & mask;
+ }
+ }
+ for (i = 0; i < numep; i++) {
+ ueps[i] = bc6_unquantize(endpoints[i], info->epb, sign);
+ }
+ for (i = 0; i < 16; i++) {
+ s = bc7_get_subset(info->ns, partition, i) * 6;
+ ib2 = ib;
+ if (i == 0) {
+ ib2--;
+ } else if (info->ns == 2) {
+ if (i == bc7_ai0[partition]) {
+ ib2--;
+ }
+ }
+ i0 = get_bits(src, bit, ib2);
+ bit += ib2;
+
+ bc6_lerp(&col[i], &ueps[s], &ueps[s + 3], cw[i0], sign);
+ }
+}
+
+static void
+put_block(Imaging im, ImagingCodecState state, const char *col, int sz, int C) {
+ int width = state->xsize;
+ int height = state->ysize;
+ int xmax = width + state->xoff;
+ int ymax = height + state->yoff;
+ int j, i, y, x;
+ char *dst;
+ for (j = 0; j < 4; j++) {
+ y = state->y + j;
+ if (C) {
+ if (y >= height) {
+ continue;
+ }
+ if (state->ystep < 0) {
+ y = state->yoff + ymax - y - 1;
+ }
+ dst = im->image[y];
+ for (i = 0; i < 4; i++) {
+ x = state->x + i;
+ if (x >= width) {
+ continue;
+ }
+ memcpy(dst + sz * x, col + sz * (j * 4 + i), sz);
+ }
+ } else {
+ if (state->ystep < 0) {
+ y = state->yoff + ymax - y - 1;
+ }
+ x = state->x;
+ dst = im->image[y] + sz * x;
+ memcpy(dst, col + sz * (j * 4), 4 * sz);
+ }
+ }
+ state->x += 4;
+ if (state->x >= xmax) {
+ state->y += 4;
+ state->x = state->xoff;
+ }
+}
+
+static int
+decode_bcn(
+ Imaging im, ImagingCodecState state, const UINT8 *src, int bytes, int N, int C, char *pixel_format) {
+ int ymax = state->ysize + state->yoff;
+ const UINT8 *ptr = src;
+ switch (N) {
+#define DECODE_LOOP(NN, SZ, TY, ...) \
+ case NN: \
+ while (bytes >= SZ) { \
+ TY col[16]; \
+ memset(col, 0, 16 * sizeof(col[0])); \
+ decode_bc##NN##_block(col, ptr); \
+ put_block(im, state, (const char *)col, sizeof(col[0]), C); \
+ ptr += SZ; \
+ bytes -= SZ; \
+ if (state->y >= ymax) { \
+ return -1; \
+ } \
+ } \
+ break
+
+ DECODE_LOOP(1, 8, rgba);
+ DECODE_LOOP(2, 16, rgba);
+ DECODE_LOOP(3, 16, rgba);
+ DECODE_LOOP(4, 8, lum);
+ case 5:
+ {
+ int sign = strcmp(pixel_format, "BC5S") == 0 ? 1 : 0;
+ while (bytes >= 16) {
+ rgba col[16];
+ memset(col, sign ? 128 : 0, 16 * sizeof(col[0]));
+ decode_bc5_block(col, ptr, sign);
+ put_block(im, state, (const char *)col, sizeof(col[0]), C);
+ ptr += 16;
+ bytes -= 16;
+ if (state->y >= ymax) {
+ return -1;
+ }
+ }
+ break;
+ }
+ case 6:
+ {
+ int sign = strcmp(pixel_format, "BC6HS") == 0 ? 1 : 0;
+ while (bytes >= 16) {
+ rgba col[16];
+ decode_bc6_block(col, ptr, sign);
+ put_block(im, state, (const char *)col, sizeof(col[0]), C);
+ ptr += 16;
+ bytes -= 16;
+ if (state->y >= ymax) {
+ return -1;
+ }
+ }
+ break;
+ }
+ DECODE_LOOP(7, 16, rgba);
+#undef DECODE_LOOP
+ }
+ return (int)(ptr - src);
+}
+
+int
+ImagingBcnDecode(Imaging im, ImagingCodecState state, UINT8 *buf, Py_ssize_t bytes) {
+ int N = state->state & 0xf;
+ int width = state->xsize;
+ int height = state->ysize;
+ int C = (width & 3) | (height & 3) ? 1 : 0;
+ char *pixel_format = ((BCNSTATE *)state->context)->pixel_format;
+ return decode_bcn(im, state, buf, bytes, N, C, pixel_format);
+}
diff --git a/contrib/python/Pillow/py3/libImaging/Bit.h b/contrib/python/Pillow/py3/libImaging/Bit.h
new file mode 100644
index 00000000000..f64bfb46990
--- /dev/null
+++ b/contrib/python/Pillow/py3/libImaging/Bit.h
@@ -0,0 +1,29 @@
+/* Bit.h */
+
+typedef struct {
+ /* CONFIGURATION */
+
+ /* Number of bits per pixel */
+ int bits;
+
+ /* Line padding (0 or 8) */
+ int pad;
+
+ /* Fill order */
+ /* 0=msb/msb, 1=msbfill/lsbshift, 2=lsbfill/msbshift, 3=lsb/lsb */
+ int fill;
+
+ /* Signed integers (0=unsigned, 1=signed) */
+ int sign;
+
+ /* Lookup table (not implemented) */
+ unsigned long lutsize;
+ FLOAT32 *lut;
+
+ /* INTERNAL */
+ unsigned long mask;
+ unsigned long signmask;
+ unsigned long bitbuffer;
+ int bitcount;
+
+} BITSTATE;
diff --git a/contrib/python/Pillow/py3/libImaging/BitDecode.c b/contrib/python/Pillow/py3/libImaging/BitDecode.c
new file mode 100644
index 00000000000..28baa8b7ea8
--- /dev/null
+++ b/contrib/python/Pillow/py3/libImaging/BitDecode.c
@@ -0,0 +1,138 @@
+/*
+ * The Python Imaging Library.
+ * $Id$
+ *
+ * decoder for packed bitfields (converts to floating point)
+ *
+ * history:
+ * 97-05-31 fl created (much more than originally intended)
+ *
+ * Copyright (c) Fredrik Lundh 1997.
+ * Copyright (c) Secret Labs AB 1997.
+ *
+ * See the README file for information on usage and redistribution.
+ */
+
+#include "Imaging.h"
+
+#include "Bit.h"
+
+int
+ImagingBitDecode(Imaging im, ImagingCodecState state, UINT8 *buf, Py_ssize_t bytes) {
+ BITSTATE *bitstate = state->context;
+ UINT8 *ptr;
+
+ if (state->state == 0) {
+ /* Initialize context variables */
+
+ /* this decoder only works for float32 image buffers */
+ if (im->type != IMAGING_TYPE_FLOAT32) {
+ state->errcode = IMAGING_CODEC_CONFIG;
+ return -1;
+ }
+
+ /* sanity check */
+ if (bitstate->bits < 1 || bitstate->bits >= 32) {
+ state->errcode = IMAGING_CODEC_CONFIG;
+ return -1;
+ }
+
+ bitstate->mask = (1 << bitstate->bits) - 1;
+
+ if (bitstate->sign) {
+ bitstate->signmask = (1 << (bitstate->bits - 1));
+ }
+
+ /* check image orientation */
+ if (state->ystep < 0) {
+ state->y = state->ysize - 1;
+ state->ystep = -1;
+ } else {
+ state->ystep = 1;
+ }
+
+ state->state = 1;
+ }
+
+ ptr = buf;
+
+ while (bytes > 0) {
+ UINT8 byte = *ptr;
+
+ ptr++;
+ bytes--;
+
+ /* get a byte from the input stream and insert in the bit buffer */
+ if (bitstate->fill & 1) {
+ /* fill MSB first */
+ bitstate->bitbuffer |= (unsigned long)byte << bitstate->bitcount;
+ } else {
+ /* fill LSB first */
+ bitstate->bitbuffer = (bitstate->bitbuffer << 8) | byte;
+ }
+
+ bitstate->bitcount += 8;
+
+ while (bitstate->bitcount >= bitstate->bits) {
+ /* get a pixel from the bit buffer */
+ unsigned long data;
+ FLOAT32 pixel;
+
+ if (bitstate->fill & 2) {
+ /* store LSB first */
+ data = bitstate->bitbuffer & bitstate->mask;
+ if (bitstate->bitcount > 32) {
+ /* bitbuffer overflow; restore it from last input byte */
+ bitstate->bitbuffer =
+ byte >> (8 - (bitstate->bitcount - bitstate->bits));
+ } else {
+ bitstate->bitbuffer >>= bitstate->bits;
+ }
+ } else {
+ /* store MSB first */
+ data = (bitstate->bitbuffer >> (bitstate->bitcount - bitstate->bits)) &
+ bitstate->mask;
+ }
+
+ bitstate->bitcount -= bitstate->bits;
+
+ if (bitstate->lutsize > 0) {
+ /* map through lookup table */
+ if (data <= 0) {
+ pixel = bitstate->lut[0];
+ } else if (data >= bitstate->lutsize) {
+ pixel = bitstate->lut[bitstate->lutsize - 1];
+ } else {
+ pixel = bitstate->lut[data];
+ }
+ } else {
+ /* convert */
+ if (data & bitstate->signmask) {
+ /* image memory contains signed data */
+ pixel = (FLOAT32)(INT32)(data | ~bitstate->mask);
+ } else {
+ pixel = (FLOAT32)data;
+ }
+ }
+
+ *(FLOAT32 *)(&im->image32[state->y][state->x]) = pixel;
+
+ /* step forward */
+ if (++state->x >= state->xsize) {
+ /* new line */
+ state->y += state->ystep;
+ if (state->y < 0 || state->y >= state->ysize) {
+ /* end of file (errcode = 0) */
+ return -1;
+ }
+ state->x = 0;
+ /* reset bit buffer */
+ if (bitstate->pad > 0) {
+ bitstate->bitcount = 0;
+ }
+ }
+ }
+ }
+
+ return ptr - buf;
+}
diff --git a/contrib/python/Pillow/py3/libImaging/Blend.c b/contrib/python/Pillow/py3/libImaging/Blend.c
new file mode 100644
index 00000000000..a53ae0fad53
--- /dev/null
+++ b/contrib/python/Pillow/py3/libImaging/Blend.c
@@ -0,0 +1,79 @@
+/*
+ * The Python Imaging Library
+ * $Id$
+ *
+ * interpolate between two existing images
+ *
+ * history:
+ * 96-03-20 fl Created
+ * 96-05-18 fl Simplified blend expression
+ * 96-10-05 fl Fixed expression bug, special case for interpolation
+ *
+ * Copyright (c) Fredrik Lundh 1996.
+ * Copyright (c) Secret Labs AB 1997.
+ *
+ * See the README file for details on usage and redistribution.
+ */
+
+#include "Imaging.h"
+
+Imaging
+ImagingBlend(Imaging imIn1, Imaging imIn2, float alpha) {
+ Imaging imOut;
+ int x, y;
+
+ /* Check arguments */
+ if (!imIn1 || !imIn2 || imIn1->type != IMAGING_TYPE_UINT8 || imIn1->palette ||
+ strcmp(imIn1->mode, "1") == 0 || imIn2->palette ||
+ strcmp(imIn2->mode, "1") == 0) {
+ return ImagingError_ModeError();
+ }
+
+ if (imIn1->type != imIn2->type || imIn1->bands != imIn2->bands ||
+ imIn1->xsize != imIn2->xsize || imIn1->ysize != imIn2->ysize) {
+ return ImagingError_Mismatch();
+ }
+
+ /* Shortcuts */
+ if (alpha == 0.0) {
+ return ImagingCopy(imIn1);
+ } else if (alpha == 1.0) {
+ return ImagingCopy(imIn2);
+ }
+
+ imOut = ImagingNewDirty(imIn1->mode, imIn1->xsize, imIn1->ysize);
+ if (!imOut) {
+ return NULL;
+ }
+
+ if (alpha >= 0 && alpha <= 1.0) {
+ /* Interpolate between bands */
+ for (y = 0; y < imIn1->ysize; y++) {
+ UINT8 *in1 = (UINT8 *)imIn1->image[y];
+ UINT8 *in2 = (UINT8 *)imIn2->image[y];
+ UINT8 *out = (UINT8 *)imOut->image[y];
+ for (x = 0; x < imIn1->linesize; x++) {
+ out[x] = (UINT8)((int)in1[x] + alpha * ((int)in2[x] - (int)in1[x]));
+ }
+ }
+ } else {
+ /* Extrapolation; must make sure to clip resulting values */
+ for (y = 0; y < imIn1->ysize; y++) {
+ UINT8 *in1 = (UINT8 *)imIn1->image[y];
+ UINT8 *in2 = (UINT8 *)imIn2->image[y];
+ UINT8 *out = (UINT8 *)imOut->image[y];
+ for (x = 0; x < imIn1->linesize; x++) {
+ float temp = (float)((int)in1[x] + alpha * ((int)in2[x] - (int)in1[x]));
+ if (temp <= 0.0) {
+ out[x] = 0;
+ } else if (temp >= 255.0) {
+ out[x] = 255;
+ } else {
+ out[x] = (UINT8)temp;
+ }
+ }
+ }
+ }
+
+ return imOut;
+}
diff --git a/contrib/python/Pillow/py3/libImaging/BoxBlur.c b/contrib/python/Pillow/py3/libImaging/BoxBlur.c
new file mode 100644
index 00000000000..adf425d0dbd
--- /dev/null
+++ b/contrib/python/Pillow/py3/libImaging/BoxBlur.c
@@ -0,0 +1,324 @@
+#include "Imaging.h"
+
+#define MAX(x, y) (((x) > (y)) ? (x) : (y))
+#define MIN(x, y) (((x) < (y)) ? (x) : (y))
+
+typedef UINT8 pixel[4];
+
+void static inline ImagingLineBoxBlur32(
+ pixel *lineOut,
+ pixel *lineIn,
+ int lastx,
+ int radius,
+ int edgeA,
+ int edgeB,
+ UINT32 ww,
+ UINT32 fw) {
+ int x;
+ UINT32 acc[4];
+ UINT32 bulk[4];
+
+#define MOVE_ACC(acc, subtract, add) \
+ acc[0] += lineIn[add][0] - lineIn[subtract][0]; \
+ acc[1] += lineIn[add][1] - lineIn[subtract][1]; \
+ acc[2] += lineIn[add][2] - lineIn[subtract][2]; \
+ acc[3] += lineIn[add][3] - lineIn[subtract][3];
+
+#define ADD_FAR(bulk, acc, left, right) \
+ bulk[0] = (acc[0] * ww) + (lineIn[left][0] + lineIn[right][0]) * fw; \
+ bulk[1] = (acc[1] * ww) + (lineIn[left][1] + lineIn[right][1]) * fw; \
+ bulk[2] = (acc[2] * ww) + (lineIn[left][2] + lineIn[right][2]) * fw; \
+ bulk[3] = (acc[3] * ww) + (lineIn[left][3] + lineIn[right][3]) * fw;
+
+#define SAVE(x, bulk) \
+ lineOut[x][0] = (UINT8)((bulk[0] + (1 << 23)) >> 24); \
+ lineOut[x][1] = (UINT8)((bulk[1] + (1 << 23)) >> 24); \
+ lineOut[x][2] = (UINT8)((bulk[2] + (1 << 23)) >> 24); \
+ lineOut[x][3] = (UINT8)((bulk[3] + (1 << 23)) >> 24);
+
+ /* Compute acc for -1 pixel (outside of image):
+ From "-radius-1" to "-1" get first pixel,
+ then from "0" to "radius-1". */
+ acc[0] = lineIn[0][0] * (radius + 1);
+ acc[1] = lineIn[0][1] * (radius + 1);
+ acc[2] = lineIn[0][2] * (radius + 1);
+ acc[3] = lineIn[0][3] * (radius + 1);
+ /* As radius can be bigger than xsize, iterate to edgeA -1. */
+ for (x = 0; x < edgeA - 1; x++) {
+ acc[0] += lineIn[x][0];
+ acc[1] += lineIn[x][1];
+ acc[2] += lineIn[x][2];
+ acc[3] += lineIn[x][3];
+ }
+ /* Then multiply remainder to last x. */
+ acc[0] += lineIn[lastx][0] * (radius - edgeA + 1);
+ acc[1] += lineIn[lastx][1] * (radius - edgeA + 1);
+ acc[2] += lineIn[lastx][2] * (radius - edgeA + 1);
+ acc[3] += lineIn[lastx][3] * (radius - edgeA + 1);
+
+ if (edgeA <= edgeB) {
+ /* Subtract pixel from left ("0").
+ Add pixels from radius. */
+ for (x = 0; x < edgeA; x++) {
+ MOVE_ACC(acc, 0, x + radius);
+ ADD_FAR(bulk, acc, 0, x + radius + 1);
+ SAVE(x, bulk);
+ }
+ /* Subtract previous pixel from "-radius".
+ Add pixels from radius. */
+ for (x = edgeA; x < edgeB; x++) {
+ MOVE_ACC(acc, x - radius - 1, x + radius);
+ ADD_FAR(bulk, acc, x - radius - 1, x + radius + 1);
+ SAVE(x, bulk);
+ }
+ /* Subtract previous pixel from "-radius".
+ Add last pixel. */
+ for (x = edgeB; x <= lastx; x++) {
+ MOVE_ACC(acc, x - radius - 1, lastx);
+ ADD_FAR(bulk, acc, x - radius - 1, lastx);
+ SAVE(x, bulk);
+ }
+ } else {
+ for (x = 0; x < edgeB; x++) {
+ MOVE_ACC(acc, 0, x + radius);
+ ADD_FAR(bulk, acc, 0, x + radius + 1);
+ SAVE(x, bulk);
+ }
+ for (x = edgeB; x < edgeA; x++) {
+ MOVE_ACC(acc, 0, lastx);
+ ADD_FAR(bulk, acc, 0, lastx);
+ SAVE(x, bulk);
+ }
+ for (x = edgeA; x <= lastx; x++) {
+ MOVE_ACC(acc, x - radius - 1, lastx);
+ ADD_FAR(bulk, acc, x - radius - 1, lastx);
+ SAVE(x, bulk);
+ }
+ }
+
+#undef MOVE_ACC
+#undef ADD_FAR
+#undef SAVE
+}
+
+void static inline ImagingLineBoxBlur8(
+ UINT8 *lineOut,
+ UINT8 *lineIn,
+ int lastx,
+ int radius,
+ int edgeA,
+ int edgeB,
+ UINT32 ww,
+ UINT32 fw) {
+ int x;
+ UINT32 acc;
+ UINT32 bulk;
+
+#define MOVE_ACC(acc, subtract, add) acc += lineIn[add] - lineIn[subtract];
+
+#define ADD_FAR(bulk, acc, left, right) \
+ bulk = (acc * ww) + (lineIn[left] + lineIn[right]) * fw;
+
+#define SAVE(x, bulk) lineOut[x] = (UINT8)((bulk + (1 << 23)) >> 24)
+
+ acc = lineIn[0] * (radius + 1);
+ for (x = 0; x < edgeA - 1; x++) {
+ acc += lineIn[x];
+ }
+ acc += lineIn[lastx] * (radius - edgeA + 1);
+
+ if (edgeA <= edgeB) {
+ for (x = 0; x < edgeA; x++) {
+ MOVE_ACC(acc, 0, x + radius);
+ ADD_FAR(bulk, acc, 0, x + radius + 1);
+ SAVE(x, bulk);
+ }
+ for (x = edgeA; x < edgeB; x++) {
+ MOVE_ACC(acc, x - radius - 1, x + radius);
+ ADD_FAR(bulk, acc, x - radius - 1, x + radius + 1);
+ SAVE(x, bulk);
+ }
+ for (x = edgeB; x <= lastx; x++) {
+ MOVE_ACC(acc, x - radius - 1, lastx);
+ ADD_FAR(bulk, acc, x - radius - 1, lastx);
+ SAVE(x, bulk);
+ }
+ } else {
+ for (x = 0; x < edgeB; x++) {
+ MOVE_ACC(acc, 0, x + radius);
+ ADD_FAR(bulk, acc, 0, x + radius + 1);
+ SAVE(x, bulk);
+ }
+ for (x = edgeB; x < edgeA; x++) {
+ MOVE_ACC(acc, 0, lastx);
+ ADD_FAR(bulk, acc, 0, lastx);
+ SAVE(x, bulk);
+ }
+ for (x = edgeA; x <= lastx; x++) {
+ MOVE_ACC(acc, x - radius - 1, lastx);
+ ADD_FAR(bulk, acc, x - radius - 1, lastx);
+ SAVE(x, bulk);
+ }
+ }
+
+#undef MOVE_ACC
+#undef ADD_FAR
+#undef SAVE
+}
+
+Imaging
+ImagingHorizontalBoxBlur(Imaging imOut, Imaging imIn, float floatRadius) {
+ ImagingSectionCookie cookie;
+
+ int y;
+
+ int radius = (int)floatRadius;
+ UINT32 ww = (UINT32)(1 << 24) / (floatRadius * 2 + 1);
+ UINT32 fw = ((1 << 24) - (radius * 2 + 1) * ww) / 2;
+
+ int edgeA = MIN(radius + 1, imIn->xsize);
+ int edgeB = MAX(imIn->xsize - radius - 1, 0);
+
+ UINT32 *lineOut = calloc(imIn->xsize, sizeof(UINT32));
+ if (lineOut == NULL) {
+ return ImagingError_MemoryError();
+ }
+
+ // printf(">>> %d %d %d\n", radius, ww, fw);
+
+ ImagingSectionEnter(&cookie);
+
+ if (imIn->image8) {
+ for (y = 0; y < imIn->ysize; y++) {
+ ImagingLineBoxBlur8(
+ (imIn == imOut ? (UINT8 *)lineOut : imOut->image8[y]),
+ imIn->image8[y],
+ imIn->xsize - 1,
+ radius,
+ edgeA,
+ edgeB,
+ ww,
+ fw);
+ if (imIn == imOut) {
+ // Commit.
+ memcpy(imOut->image8[y], lineOut, imIn->xsize);
+ }
+ }
+ } else {
+ for (y = 0; y < imIn->ysize; y++) {
+ ImagingLineBoxBlur32(
+ imIn == imOut ? (pixel *)lineOut : (pixel *)imOut->image32[y],
+ (pixel *)imIn->image32[y],
+ imIn->xsize - 1,
+ radius,
+ edgeA,
+ edgeB,
+ ww,
+ fw);
+ if (imIn == imOut) {
+ // Commit.
+ memcpy(imOut->image32[y], lineOut, imIn->xsize * 4);
+ }
+ }
+ }
+
+ ImagingSectionLeave(&cookie);
+
+ free(lineOut);
+
+ return imOut;
+}
+
+Imaging
+ImagingBoxBlur(Imaging imOut, Imaging imIn, float xradius, float yradius, int n) {
+ int i;
+ Imaging imTransposed;
+
+ if (n < 1) {
+ return ImagingError_ValueError("number of passes must be greater than zero");
+ }
+ if (xradius < 0 || yradius < 0) {
+ return ImagingError_ValueError("radius must be >= 0");
+ }
+
+ if (strcmp(imIn->mode, imOut->mode) || imIn->type != imOut->type ||
+ imIn->bands != imOut->bands || imIn->xsize != imOut->xsize ||
+ imIn->ysize != imOut->ysize) {
+ return ImagingError_Mismatch();
+ }
+
+ if (imIn->type != IMAGING_TYPE_UINT8) {
+ return ImagingError_ModeError();
+ }
+
+ if (!(strcmp(imIn->mode, "RGB") == 0 || strcmp(imIn->mode, "RGBA") == 0 ||
+ strcmp(imIn->mode, "RGBa") == 0 || strcmp(imIn->mode, "RGBX") == 0 ||
+ strcmp(imIn->mode, "CMYK") == 0 || strcmp(imIn->mode, "L") == 0 ||
+ strcmp(imIn->mode, "LA") == 0 || strcmp(imIn->mode, "La") == 0)) {
+ return ImagingError_ModeError();
+ }
+
+ /* Apply blur in one dimension.
+ Use imOut as a destination at first pass,
+ then use imOut as a source too. */
+
+ if (xradius != 0) {
+ ImagingHorizontalBoxBlur(imOut, imIn, xradius);
+ for (i = 1; i < n; i++) {
+ ImagingHorizontalBoxBlur(imOut, imOut, xradius);
+ }
+ }
+ if (yradius != 0) {
+ imTransposed = ImagingNewDirty(imIn->mode, imIn->ysize, imIn->xsize);
+ if (!imTransposed) {
+ return NULL;
+ }
+
+ /* Transpose result for blur in another direction. */
+ ImagingTranspose(imTransposed, xradius == 0 ? imIn : imOut);
+
+ /* Reuse imTransposed as a source and destination there. */
+ for (i = 0; i < n; i++) {
+ ImagingHorizontalBoxBlur(imTransposed, imTransposed, yradius);
+ }
+ /* Restore original orientation. */
+ ImagingTranspose(imOut, imTransposed);
+
+ ImagingDelete(imTransposed);
+ }
+ if (xradius == 0 && yradius == 0) {
+ if (!ImagingCopy2(imOut, imIn)) {
+ return NULL;
+ }
+ }
+
+ return imOut;
+}
+
+static float
+_gaussian_blur_radius(float radius, int passes) {
+ float sigma2, L, l, a;
+
+ sigma2 = radius * radius / passes;
+ // from https://www.mia.uni-saarland.de/Publications/gwosdek-ssvm11.pdf
+ // [7] Box length.
+ L = sqrt(12.0 * sigma2 + 1.0);
+ // [11] Integer part of box radius.
+ l = floor((L - 1.0) / 2.0);
+ // [14], [Fig. 2] Fractional part of box radius.
+ a = (2 * l + 1) * (l * (l + 1) - 3 * sigma2);
+ a /= 6 * (sigma2 - (l + 1) * (l + 1));
+
+ return l + a;
+}
+
+Imaging
+ImagingGaussianBlur(Imaging imOut, Imaging imIn, float xradius, float yradius, int passes) {
+ return ImagingBoxBlur(
+ imOut,
+ imIn,
+ _gaussian_blur_radius(xradius, passes),
+ _gaussian_blur_radius(yradius, passes),
+ passes
+ );
+}
diff --git a/contrib/python/Pillow/py3/libImaging/Chops.c b/contrib/python/Pillow/py3/libImaging/Chops.c
new file mode 100644
index 00000000000..f9c005efe3a
--- /dev/null
+++ b/contrib/python/Pillow/py3/libImaging/Chops.c
@@ -0,0 +1,162 @@
+/*
+ * The Python Imaging Library
+ * $Id$
+ *
+ * basic channel operations
+ *
+ * history:
+ * 1996-03-28 fl Created
+ * 1996-08-13 fl Added and/or/xor for "1" images
+ * 1996-12-14 fl Added add_modulo, sub_modulo
+ * 2005-09-10 fl Fixed output values from and/or/xor
+ *
+ * Copyright (c) 1996 by Fredrik Lundh.
+ * Copyright (c) 1997 by Secret Labs AB.
+ *
+ * See the README file for details on usage and redistribution.
+ */
+
+#include "Imaging.h"
+
+#define CHOP(operation) \
+ int x, y; \
+ Imaging imOut; \
+ imOut = create(imIn1, imIn2, NULL); \
+ if (!imOut) { \
+ return NULL; \
+ } \
+ for (y = 0; y < imOut->ysize; y++) { \
+ UINT8 *out = (UINT8 *)imOut->image[y]; \
+ UINT8 *in1 = (UINT8 *)imIn1->image[y]; \
+ UINT8 *in2 = (UINT8 *)imIn2->image[y]; \
+ for (x = 0; x < imOut->linesize; x++) { \
+ int temp = operation; \
+ if (temp <= 0) { \
+ out[x] = 0; \
+ } else if (temp >= 255) { \
+ out[x] = 255; \
+ } else { \
+ out[x] = temp; \
+ } \
+ } \
+ } \
+ return imOut;
+
+#define CHOP2(operation, mode) \
+ int x, y; \
+ Imaging imOut; \
+ imOut = create(imIn1, imIn2, mode); \
+ if (!imOut) { \
+ return NULL; \
+ } \
+ for (y = 0; y < imOut->ysize; y++) { \
+ UINT8 *out = (UINT8 *)imOut->image[y]; \
+ UINT8 *in1 = (UINT8 *)imIn1->image[y]; \
+ UINT8 *in2 = (UINT8 *)imIn2->image[y]; \
+ for (x = 0; x < imOut->linesize; x++) { \
+ out[x] = operation; \
+ } \
+ } \
+ return imOut;
+
+static Imaging
+create(Imaging im1, Imaging im2, char *mode) {
+ int xsize, ysize;
+
+ if (!im1 || !im2 || im1->type != IMAGING_TYPE_UINT8 ||
+ (mode != NULL && (strcmp(im1->mode, "1") || strcmp(im2->mode, "1")))) {
+ return (Imaging)ImagingError_ModeError();
+ }
+ if (im1->type != im2->type || im1->bands != im2->bands) {
+ return (Imaging)ImagingError_Mismatch();
+ }
+
+ xsize = (im1->xsize < im2->xsize) ? im1->xsize : im2->xsize;
+ ysize = (im1->ysize < im2->ysize) ? im1->ysize : im2->ysize;
+
+ return ImagingNewDirty(im1->mode, xsize, ysize);
+}
+
+Imaging
+ImagingChopLighter(Imaging imIn1, Imaging imIn2) {
+ CHOP((in1[x] > in2[x]) ? in1[x] : in2[x]);
+}
+
+Imaging
+ImagingChopDarker(Imaging imIn1, Imaging imIn2) {
+ CHOP((in1[x] < in2[x]) ? in1[x] : in2[x]);
+}
+
+Imaging
+ImagingChopDifference(Imaging imIn1, Imaging imIn2) {
+ CHOP(abs((int)in1[x] - (int)in2[x]));
+}
+
+Imaging
+ImagingChopMultiply(Imaging imIn1, Imaging imIn2) {
+ CHOP((int)in1[x] * (int)in2[x] / 255);
+}
+
+Imaging
+ImagingChopScreen(Imaging imIn1, Imaging imIn2) {
+ CHOP(255 - ((int)(255 - in1[x]) * (int)(255 - in2[x])) / 255);
+}
+
+Imaging
+ImagingChopAdd(Imaging imIn1, Imaging imIn2, float scale, int offset) {
+ CHOP(((int)in1[x] + (int)in2[x]) / scale + offset);
+}
+
+Imaging
+ImagingChopSubtract(Imaging imIn1, Imaging imIn2, float scale, int offset) {
+ CHOP(((int)in1[x] - (int)in2[x]) / scale + offset);
+}
+
+Imaging
+ImagingChopAnd(Imaging imIn1, Imaging imIn2) {
+ CHOP2((in1[x] && in2[x]) ? 255 : 0, "1");
+}
+
+Imaging
+ImagingChopOr(Imaging imIn1, Imaging imIn2) {
+ CHOP2((in1[x] || in2[x]) ? 255 : 0, "1");
+}
+
+Imaging
+ImagingChopXor(Imaging imIn1, Imaging imIn2) {
+ CHOP2(((in1[x] != 0) ^ (in2[x] != 0)) ? 255 : 0, "1");
+}
+
+Imaging
+ImagingChopAddModulo(Imaging imIn1, Imaging imIn2) {
+ CHOP2(in1[x] + in2[x], NULL);
+}
+
+Imaging
+ImagingChopSubtractModulo(Imaging imIn1, Imaging imIn2) {
+ CHOP2(in1[x] - in2[x], NULL);
+}
+
+Imaging
+ImagingChopSoftLight(Imaging imIn1, Imaging imIn2) {
+ CHOP2(
+ (((255 - in1[x]) * (in1[x] * in2[x])) / 65536) +
+ (in1[x] * (255 - ((255 - in1[x]) * (255 - in2[x]) / 255))) / 255,
+ NULL);
+}
+
+Imaging
+ImagingChopHardLight(Imaging imIn1, Imaging imIn2) {
+ CHOP2(
+ (in2[x] < 128) ? ((in1[x] * in2[x]) / 127)
+ : 255 - (((255 - in2[x]) * (255 - in1[x])) / 127),
+ NULL);
+}
+
+Imaging
+ImagingOverlay(Imaging imIn1, Imaging imIn2) {
+ CHOP2(
+ (in1[x] < 128) ? ((in1[x] * in2[x]) / 127)
+ : 255 - (((255 - in1[x]) * (255 - in2[x])) / 127),
+ NULL);
+}
diff --git a/contrib/python/Pillow/py3/libImaging/ColorLUT.c b/contrib/python/Pillow/py3/libImaging/ColorLUT.c
new file mode 100644
index 00000000000..aee7cda067d
--- /dev/null
+++ b/contrib/python/Pillow/py3/libImaging/ColorLUT.c
@@ -0,0 +1,187 @@
+#include "Imaging.h"
+#include <math.h>
+
+/* 8 bits for result. Table can overflow [0, 1.0] range,
+ so we need extra bits for overflow and negative values.
+ NOTE: This value should be the same as in _imaging/_prepare_lut_table() */
+#define PRECISION_BITS (16 - 8 - 2)
+#define PRECISION_ROUNDING (1 << (PRECISION_BITS - 1))
+
+/* 8 - scales are multiplied on byte.
+ 6 - max index in the table
+ (max size is 65, but index 64 is not reachable) */
+#define SCALE_BITS (32 - 8 - 6)
+#define SCALE_MASK ((1 << SCALE_BITS) - 1)
+
+#define SHIFT_BITS (16 - 1)
+
+static inline UINT8
+clip8(int in) {
+ return clip8_lookups[(in + PRECISION_ROUNDING) >> PRECISION_BITS];
+}
+
+static inline void
+interpolate3(INT16 out[3], const INT16 a[3], const INT16 b[3], INT16 shift) {
+ out[0] = (a[0] * ((1 << SHIFT_BITS) - shift) + b[0] * shift) >> SHIFT_BITS;
+ out[1] = (a[1] * ((1 << SHIFT_BITS) - shift) + b[1] * shift) >> SHIFT_BITS;
+ out[2] = (a[2] * ((1 << SHIFT_BITS) - shift) + b[2] * shift) >> SHIFT_BITS;
+}
+
+static inline void
+interpolate4(INT16 out[4], const INT16 a[4], const INT16 b[4], INT16 shift) {
+ out[0] = (a[0] * ((1 << SHIFT_BITS) - shift) + b[0] * shift) >> SHIFT_BITS;
+ out[1] = (a[1] * ((1 << SHIFT_BITS) - shift) + b[1] * shift) >> SHIFT_BITS;
+ out[2] = (a[2] * ((1 << SHIFT_BITS) - shift) + b[2] * shift) >> SHIFT_BITS;
+ out[3] = (a[3] * ((1 << SHIFT_BITS) - shift) + b[3] * shift) >> SHIFT_BITS;
+}
+
+static inline int
+table_index3D(int index1D, int index2D, int index3D, int size1D, int size1D_2D) {
+ return index1D + index2D * size1D + index3D * size1D_2D;
+}
+
+/*
+ Transforms colors of imIn using provided 3D lookup table
+ and puts the result in imOut. Returns imOut on success or 0 on error.
+
+ imOut, imIn - images, should be the same size and may be the same image.
+ Should have 3 or 4 channels.
+ table_channels - number of channels in the lookup table, 3 or 4.
+ Should be less or equal than number of channels in imOut image;
+ size1D, size_2D and size3D - dimensions of provided table;
+ table - flat table,
+ array with table_channels * size1D * size2D * size3D elements,
+ where channels are changed first, then 1D, then 2D, then 3D.
+ Each element is signed 16-bit int where 0 is lowest output value
+ and 255 << PRECISION_BITS (16320) is highest value.
+*/
+Imaging
+ImagingColorLUT3D_linear(
+ Imaging imOut,
+ Imaging imIn,
+ int table_channels,
+ int size1D,
+ int size2D,
+ int size3D,
+ INT16 *table) {
+ /* This float to int conversion doesn't have rounding
+ error compensation (+0.5) for two reasons:
+ 1. As we don't hit the highest value,
+ we can use one extra bit for precision.
+ 2. For every pixel, we interpolate 8 elements from the table:
+ current and +1 for every dimension and their combinations.
+ If we hit the upper cells from the table,
+ +1 cells will be outside of the table.
+ With this compensation we never hit the upper cells
+ but this also doesn't introduce any noticeable difference. */
+ UINT32 scale1D = (size1D - 1) / 255.0 * (1 << SCALE_BITS);
+ UINT32 scale2D = (size2D - 1) / 255.0 * (1 << SCALE_BITS);
+ UINT32 scale3D = (size3D - 1) / 255.0 * (1 << SCALE_BITS);
+ int size1D_2D = size1D * size2D;
+ int x, y;
+ ImagingSectionCookie cookie;
+
+ if (table_channels < 3 || table_channels > 4) {
+ PyErr_SetString(PyExc_ValueError, "table_channels could be 3 or 4");
+ return NULL;
+ }
+
+ if (imIn->type != IMAGING_TYPE_UINT8 || imOut->type != IMAGING_TYPE_UINT8 ||
+ imIn->bands < 3 || imOut->bands < table_channels) {
+ return (Imaging)ImagingError_ModeError();
+ }
+
+ /* In case we have one extra band in imOut and don't have in imIn.*/
+ if (imOut->bands > table_channels && imOut->bands > imIn->bands) {
+ return (Imaging)ImagingError_ModeError();
+ }
+
+ ImagingSectionEnter(&cookie);
+ for (y = 0; y < imOut->ysize; y++) {
+ UINT8 *rowIn = (UINT8 *)imIn->image[y];
+ char *rowOut = (char *)imOut->image[y];
+ for (x = 0; x < imOut->xsize; x++) {
+ UINT32 index1D = rowIn[x * 4 + 0] * scale1D;
+ UINT32 index2D = rowIn[x * 4 + 1] * scale2D;
+ UINT32 index3D = rowIn[x * 4 + 2] * scale3D;
+ INT16 shift1D = (SCALE_MASK & index1D) >> (SCALE_BITS - SHIFT_BITS);
+ INT16 shift2D = (SCALE_MASK & index2D) >> (SCALE_BITS - SHIFT_BITS);
+ INT16 shift3D = (SCALE_MASK & index3D) >> (SCALE_BITS - SHIFT_BITS);
+ int idx = table_channels * table_index3D(
+ index1D >> SCALE_BITS,
+ index2D >> SCALE_BITS,
+ index3D >> SCALE_BITS,
+ size1D,
+ size1D_2D);
+ INT16 result[4], left[4], right[4];
+ INT16 leftleft[4], leftright[4], rightleft[4], rightright[4];
+
+ if (table_channels == 3) {
+ UINT32 v;
+ interpolate3(leftleft, &table[idx + 0], &table[idx + 3], shift1D);
+ interpolate3(
+ leftright,
+ &table[idx + size1D * 3],
+ &table[idx + size1D * 3 + 3],
+ shift1D);
+ interpolate3(left, leftleft, leftright, shift2D);
+
+ interpolate3(
+ rightleft,
+ &table[idx + size1D_2D * 3],
+ &table[idx + size1D_2D * 3 + 3],
+ shift1D);
+ interpolate3(
+ rightright,
+ &table[idx + size1D_2D * 3 + size1D * 3],
+ &table[idx + size1D_2D * 3 + size1D * 3 + 3],
+ shift1D);
+ interpolate3(right, rightleft, rightright, shift2D);
+
+ interpolate3(result, left, right, shift3D);
+
+ v = MAKE_UINT32(
+ clip8(result[0]),
+ clip8(result[1]),
+ clip8(result[2]),
+ rowIn[x * 4 + 3]);
+ memcpy(rowOut + x * sizeof(v), &v, sizeof(v));
+ }
+
+ if (table_channels == 4) {
+ UINT32 v;
+ interpolate4(leftleft, &table[idx + 0], &table[idx + 4], shift1D);
+ interpolate4(
+ leftright,
+ &table[idx + size1D * 4],
+ &table[idx + size1D * 4 + 4],
+ shift1D);
+ interpolate4(left, leftleft, leftright, shift2D);
+
+ interpolate4(
+ rightleft,
+ &table[idx + size1D_2D * 4],
+ &table[idx + size1D_2D * 4 + 4],
+ shift1D);
+ interpolate4(
+ rightright,
+ &table[idx + size1D_2D * 4 + size1D * 4],
+ &table[idx + size1D_2D * 4 + size1D * 4 + 4],
+ shift1D);
+ interpolate4(right, rightleft, rightright, shift2D);
+
+ interpolate4(result, left, right, shift3D);
+
+ v = MAKE_UINT32(
+ clip8(result[0]),
+ clip8(result[1]),
+ clip8(result[2]),
+ clip8(result[3]));
+ memcpy(rowOut + x * sizeof(v), &v, sizeof(v));
+ }
+ }
+ }
+ ImagingSectionLeave(&cookie);
+
+ return imOut;
+}
diff --git a/contrib/python/Pillow/py3/libImaging/Convert.c b/contrib/python/Pillow/py3/libImaging/Convert.c
new file mode 100644
index 00000000000..b08519d3045
--- /dev/null
+++ b/contrib/python/Pillow/py3/libImaging/Convert.c
@@ -0,0 +1,1742 @@
+/*
+ * The Python Imaging Library
+ * $Id$
+ *
+ * convert images
+ *
+ * history:
+ * 1995-06-15 fl created
+ * 1995-11-28 fl added some "RGBA" and "CMYK" conversions
+ * 1996-04-22 fl added "1" conversions (same as "L")
+ * 1996-05-05 fl added palette conversions (hack)
+ * 1996-07-23 fl fixed "1" conversions to zero/non-zero convention
+ * 1996-11-01 fl fixed "P" to "L" and "RGB" to "1" conversions
+ * 1996-12-29 fl set alpha byte in RGB converters
+ * 1997-05-12 fl added ImagingConvert2
+ * 1997-05-30 fl added floating point support
+ * 1997-08-27 fl added "P" to "1" and "P" to "F" conversions
+ * 1998-01-11 fl added integer support
+ * 1998-07-01 fl added "YCbCr" support
+ * 1998-07-02 fl added "RGBX" conversions (sort of)
+ * 1998-07-04 fl added floyd-steinberg dithering
+ * 1998-07-12 fl changed "YCrCb" to "YCbCr" (!)
+ * 1998-12-29 fl added basic "I;16" and "I;16B" conversions
+ * 1999-02-03 fl added "RGBa", and "BGR" conversions (experimental)
+ * 2003-09-26 fl added "LA" and "PA" conversions (experimental)
+ * 2005-05-05 fl fixed "P" to "1" threshold
+ * 2005-12-08 fl fixed palette memory leak in topalette
+ *
+ * Copyright (c) 1997-2005 by Secret Labs AB.
+ * Copyright (c) 1995-1997 by Fredrik Lundh.
+ *
+ * See the README file for details on usage and redistribution.
+ */
+
+#include "Imaging.h"
+
+#define MAX(a, b) (a) > (b) ? (a) : (b)
+#define MIN(a, b) (a) < (b) ? (a) : (b)
+
+#define CLIP16(v) ((v) <= 0 ? 0 : (v) >= 65535 ? 65535 : (v))
+
+/* ITU-R Recommendation 601-2 (assuming nonlinear RGB) */
+#define L(rgb) ((INT32)(rgb)[0] * 299 + (INT32)(rgb)[1] * 587 + (INT32)(rgb)[2] * 114)
+#define L24(rgb) ((rgb)[0] * 19595 + (rgb)[1] * 38470 + (rgb)[2] * 7471 + 0x8000)
+
+/* ------------------- */
+/* 1 (bit) conversions */
+/* ------------------- */
+
+static void
+bit2l(UINT8 *out, const UINT8 *in, int xsize) {
+ int x;
+ for (x = 0; x < xsize; x++) *out++ = (*in++ != 0) ? 255 : 0;
+}
+
+static void
+bit2rgb(UINT8 *out, const UINT8 *in, int xsize) {
+ int x;
+ for (x = 0; x < xsize; x++) {
+ UINT8 v = (*in++ != 0) ? 255 : 0;
+ *out++ = v;
+ *out++ = v;
+ *out++ = v;
+ *out++ = 255;
+ }
+}
+
+static void
+bit2cmyk(UINT8 *out, const UINT8 *in, int xsize) {
+ int x;
+ for (x = 0; x < xsize; x++) {
+ *out++ = 0;
+ *out++ = 0;
+ *out++ = 0;
+ *out++ = (*in++ != 0) ? 0 : 255;
+ }
+}
+
+static void
+bit2ycbcr(UINT8 *out, const UINT8 *in, int xsize) {
+ int x;
+ for (x = 0; x < xsize; x++) {
+ *out++ = (*in++ != 0) ? 255 : 0;
+ *out++ = 128;
+ *out++ = 128;
+ *out++ = 255;
+ }
+}
+
+static void
+bit2hsv(UINT8 *out, const UINT8 *in, int xsize) {
+ int x;
+ for (x = 0; x < xsize; x++, out += 4) {
+ UINT8 v = (*in++ != 0) ? 255 : 0;
+ out[0] = 0;
+ out[1] = 0;
+ out[2] = v;
+ out[3] = 255;
+ }
+}
+
+/* ----------------- */
+/* RGB/L conversions */
+/* ----------------- */
+
+static void
+l2bit(UINT8 *out, const UINT8 *in, int xsize) {
+ int x;
+ for (x = 0; x < xsize; x++) {
+ *out++ = (*in++ >= 128) ? 255 : 0;
+ }
+}
+
+static void
+lA2la(UINT8 *out, const UINT8 *in, int xsize) {
+ int x;
+ unsigned int alpha, pixel, tmp;
+ for (x = 0; x < xsize; x++, in += 4) {
+ alpha = in[3];
+ pixel = MULDIV255(in[0], alpha, tmp);
+ *out++ = (UINT8)pixel;
+ *out++ = (UINT8)pixel;
+ *out++ = (UINT8)pixel;
+ *out++ = (UINT8)alpha;
+ }
+}
+
+/* RGBa -> RGBA conversion to remove premultiplication
+ Needed for correct transforms/resizing on RGBA images */
+static void
+la2lA(UINT8 *out, const UINT8 *in, int xsize) {
+ int x;
+ unsigned int alpha, pixel;
+ for (x = 0; x < xsize; x++, in += 4) {
+ alpha = in[3];
+ if (alpha == 255 || alpha == 0) {
+ pixel = in[0];
+ } else {
+ pixel = CLIP8((255 * in[0]) / alpha);
+ }
+ *out++ = (UINT8)pixel;
+ *out++ = (UINT8)pixel;
+ *out++ = (UINT8)pixel;
+ *out++ = (UINT8)alpha;
+ }
+}
+
+static void
+l2la(UINT8 *out, const UINT8 *in, int xsize) {
+ int x;
+ for (x = 0; x < xsize; x++) {
+ UINT8 v = *in++;
+ *out++ = v;
+ *out++ = v;
+ *out++ = v;
+ *out++ = 255;
+ }
+}
+
+static void
+l2rgb(UINT8 *out, const UINT8 *in, int xsize) {
+ int x;
+ for (x = 0; x < xsize; x++) {
+ UINT8 v = *in++;
+ *out++ = v;
+ *out++ = v;
+ *out++ = v;
+ *out++ = 255;
+ }
+}
+
+static void
+l2hsv(UINT8 *out, const UINT8 *in, int xsize) {
+ int x;
+ for (x = 0; x < xsize; x++, out += 4) {
+ UINT8 v = *in++;
+ out[0] = 0;
+ out[1] = 0;
+ out[2] = v;
+ out[3] = 255;
+ }
+}
+
+static void
+la2l(UINT8 *out, const UINT8 *in, int xsize) {
+ int x;
+ for (x = 0; x < xsize; x++, in += 4) {
+ *out++ = in[0];
+ }
+}
+
+static void
+la2rgb(UINT8 *out, const UINT8 *in, int xsize) {
+ int x;
+ for (x = 0; x < xsize; x++, in += 4) {
+ UINT8 v = in[0];
+ *out++ = v;
+ *out++ = v;
+ *out++ = v;
+ *out++ = in[3];
+ }
+}
+
+static void
+la2hsv(UINT8 *out, const UINT8 *in, int xsize) {
+ int x;
+ for (x = 0; x < xsize; x++, in += 4, out += 4) {
+ UINT8 v = in[0];
+ out[0] = 0;
+ out[1] = 0;
+ out[2] = v;
+ out[3] = in[3];
+ }
+}
+
+static void
+rgb2bit(UINT8 *out, const UINT8 *in, int xsize) {
+ int x;
+ for (x = 0; x < xsize; x++, in += 4) {
+ /* ITU-R Recommendation 601-2 (assuming nonlinear RGB) */
+ *out++ = (L(in) >= 128000) ? 255 : 0;
+ }
+}
+
+static void
+rgb2l(UINT8 *out, const UINT8 *in, int xsize) {
+ int x;
+ for (x = 0; x < xsize; x++, in += 4) {
+ /* ITU-R Recommendation 601-2 (assuming nonlinear RGB) */
+ *out++ = L24(in) >> 16;
+ }
+}
+
+static void
+rgb2la(UINT8 *out, const UINT8 *in, int xsize) {
+ int x;
+ for (x = 0; x < xsize; x++, in += 4, out += 4) {
+ /* ITU-R Recommendation 601-2 (assuming nonlinear RGB) */
+ out[0] = out[1] = out[2] = L24(in) >> 16;
+ out[3] = 255;
+ }
+}
+
+static void
+rgb2i(UINT8 *out_, const UINT8 *in, int xsize) {
+ int x;
+ for (x = 0; x < xsize; x++, in += 4, out_ += 4) {
+ INT32 v = L24(in) >> 16;
+ memcpy(out_, &v, sizeof(v));
+ }
+}
+
+static void
+rgb2f(UINT8 *out_, const UINT8 *in, int xsize) {
+ int x;
+ for (x = 0; x < xsize; x++, in += 4, out_ += 4) {
+ FLOAT32 v = (float)L(in) / 1000.0F;
+ memcpy(out_, &v, sizeof(v));
+ }
+}
+
+static void
+rgb2bgr15(UINT8 *out_, const UINT8 *in, int xsize) {
+ int x;
+ for (x = 0; x < xsize; x++, in += 4, out_ += 2) {
+ UINT16 v = ((((UINT16)in[0]) << 7) & 0x7c00) +
+ ((((UINT16)in[1]) << 2) & 0x03e0) +
+ ((((UINT16)in[2]) >> 3) & 0x001f);
+ memcpy(out_, &v, sizeof(v));
+ }
+}
+
+static void
+rgb2bgr16(UINT8 *out_, const UINT8 *in, int xsize) {
+ int x;
+ for (x = 0; x < xsize; x++, in += 4, out_ += 2) {
+ UINT16 v = ((((UINT16)in[0]) << 8) & 0xf800) +
+ ((((UINT16)in[1]) << 3) & 0x07e0) +
+ ((((UINT16)in[2]) >> 3) & 0x001f);
+ memcpy(out_, &v, sizeof(v));
+ }
+}
+
+static void
+rgb2bgr24(UINT8 *out, const UINT8 *in, int xsize) {
+ int x;
+ for (x = 0; x < xsize; x++, in += 4) {
+ *out++ = in[2];
+ *out++ = in[1];
+ *out++ = in[0];
+ }
+}
+
+static void
+rgb2hsv_row(UINT8 *out, const UINT8 *in) { // following colorsys.py
+ float h, s, rc, gc, bc, cr;
+ UINT8 maxc, minc;
+ UINT8 r, g, b;
+ UINT8 uh, us, uv;
+
+ r = in[0];
+ g = in[1];
+ b = in[2];
+ maxc = MAX(r, MAX(g, b));
+ minc = MIN(r, MIN(g, b));
+ uv = maxc;
+ if (minc == maxc) {
+ uh = 0;
+ us = 0;
+ } else {
+ cr = (float)(maxc - minc);
+ s = cr / (float)maxc;
+ rc = ((float)(maxc - r)) / cr;
+ gc = ((float)(maxc - g)) / cr;
+ bc = ((float)(maxc - b)) / cr;
+ if (r == maxc) {
+ h = bc - gc;
+ } else if (g == maxc) {
+ h = 2.0 + rc - bc;
+ } else {
+ h = 4.0 + gc - rc;
+ }
+ // incorrect hue happens if h/6 is negative.
+ h = fmod((h / 6.0 + 1.0), 1.0);
+
+ uh = (UINT8)CLIP8((int)(h * 255.0));
+ us = (UINT8)CLIP8((int)(s * 255.0));
+ }
+ out[0] = uh;
+ out[1] = us;
+ out[2] = uv;
+}
+
+static void
+rgb2hsv(UINT8 *out, const UINT8 *in, int xsize) {
+ int x;
+ for (x = 0; x < xsize; x++, in += 4, out += 4) {
+ rgb2hsv_row(out, in);
+ out[3] = in[3];
+ }
+}
+
+static void
+hsv2rgb(UINT8 *out, const UINT8 *in, int xsize) { // following colorsys.py
+
+ int p, q, t;
+ UINT8 up, uq, ut;
+ int i, x;
+ float f, fs;
+ UINT8 h, s, v;
+
+ for (x = 0; x < xsize; x++, in += 4) {
+ h = in[0];
+ s = in[1];
+ v = in[2];
+
+ if (s == 0) {
+ *out++ = v;
+ *out++ = v;
+ *out++ = v;
+ } else {
+ i = floor((float)h * 6.0 / 255.0); // 0 - 6
+ f = (float)h * 6.0 / 255.0 - (float)i; // 0-1 : remainder.
+ fs = ((float)s) / 255.0;
+
+ p = round((float)v * (1.0 - fs));
+ q = round((float)v * (1.0 - fs * f));
+ t = round((float)v * (1.0 - fs * (1.0 - f)));
+ up = (UINT8)CLIP8(p);
+ uq = (UINT8)CLIP8(q);
+ ut = (UINT8)CLIP8(t);
+
+ switch (i % 6) {
+ case 0:
+ *out++ = v;
+ *out++ = ut;
+ *out++ = up;
+ break;
+ case 1:
+ *out++ = uq;
+ *out++ = v;
+ *out++ = up;
+ break;
+ case 2:
+ *out++ = up;
+ *out++ = v;
+ *out++ = ut;
+ break;
+ case 3:
+ *out++ = up;
+ *out++ = uq;
+ *out++ = v;
+ break;
+ case 4:
+ *out++ = ut;
+ *out++ = up;
+ *out++ = v;
+ break;
+ case 5:
+ *out++ = v;
+ *out++ = up;
+ *out++ = uq;
+ break;
+ }
+ }
+ *out++ = in[3];
+ }
+}
+
+/* ---------------- */
+/* RGBA conversions */
+/* ---------------- */
+
+static void
+rgb2rgba(UINT8 *out, const UINT8 *in, int xsize) {
+ int x;
+ for (x = 0; x < xsize; x++) {
+ *out++ = *in++;
+ *out++ = *in++;
+ *out++ = *in++;
+ *out++ = 255;
+ in++;
+ }
+}
+
+static void
+rgba2la(UINT8 *out, const UINT8 *in, int xsize) {
+ int x;
+ for (x = 0; x < xsize; x++, in += 4, out += 4) {
+ /* ITU-R Recommendation 601-2 (assuming nonlinear RGB) */
+ out[0] = out[1] = out[2] = L24(in) >> 16;
+ out[3] = in[3];
+ }
+}
+
+static void
+rgba2rgb(UINT8 *out, const UINT8 *in, int xsize) {
+ int x;
+ for (x = 0; x < xsize; x++) {
+ *out++ = *in++;
+ *out++ = *in++;
+ *out++ = *in++;
+ *out++ = 255;
+ in++;
+ }
+}
+
+static void
+rgbA2rgba(UINT8 *out, const UINT8 *in, int xsize) {
+ int x;
+ unsigned int alpha, tmp;
+ for (x = 0; x < xsize; x++) {
+ alpha = in[3];
+ *out++ = MULDIV255(*in++, alpha, tmp);
+ *out++ = MULDIV255(*in++, alpha, tmp);
+ *out++ = MULDIV255(*in++, alpha, tmp);
+ *out++ = *in++;
+ }
+}
+
+/* RGBa -> RGBA conversion to remove premultiplication
+ Needed for correct transforms/resizing on RGBA images */
+static void
+rgba2rgbA(UINT8 *out, const UINT8 *in, int xsize) {
+ int x;
+ unsigned int alpha;
+ for (x = 0; x < xsize; x++, in += 4) {
+ alpha = in[3];
+ if (alpha == 255 || alpha == 0) {
+ *out++ = in[0];
+ *out++ = in[1];
+ *out++ = in[2];
+ } else {
+ *out++ = CLIP8((255 * in[0]) / alpha);
+ *out++ = CLIP8((255 * in[1]) / alpha);
+ *out++ = CLIP8((255 * in[2]) / alpha);
+ }
+ *out++ = in[3];
+ }
+}
+
+static void
+rgba2rgb_(UINT8 *out, const UINT8 *in, int xsize) {
+ int x;
+ unsigned int alpha;
+ for (x = 0; x < xsize; x++, in += 4) {
+ alpha = in[3];
+ if (alpha == 255 || alpha == 0) {
+ *out++ = in[0];
+ *out++ = in[1];
+ *out++ = in[2];
+ } else {
+ *out++ = CLIP8((255 * in[0]) / alpha);
+ *out++ = CLIP8((255 * in[1]) / alpha);
+ *out++ = CLIP8((255 * in[2]) / alpha);
+ }
+ *out++ = 255;
+ }
+}
+
+/*
+ * Conversion of RGB + single transparent color to RGBA,
+ * where any pixel that matches the color will have the
+ * alpha channel set to 0
+ */
+
+static void
+rgbT2rgba(UINT8 *out, int xsize, int r, int g, int b) {
+#ifdef WORDS_BIGENDIAN
+ UINT32 trns = ((r & 0xff) << 24) | ((g & 0xff) << 16) | ((b & 0xff) << 8) | 0xff;
+ UINT32 repl = trns & 0xffffff00;
+#else
+ UINT32 trns = (0xff << 24) | ((b & 0xff) << 16) | ((g & 0xff) << 8) | (r & 0xff);
+ UINT32 repl = trns & 0x00ffffff;
+#endif
+
+ int i;
+
+ for (i = 0; i < xsize; i++, out += sizeof(trns)) {
+ UINT32 v;
+ memcpy(&v, out, sizeof(v));
+ if (v == trns) {
+ memcpy(out, &repl, sizeof(repl));
+ }
+ }
+}
+
+/* ---------------- */
+/* CMYK conversions */
+/* ---------------- */
+
+static void
+l2cmyk(UINT8 *out, const UINT8 *in, int xsize) {
+ int x;
+ for (x = 0; x < xsize; x++) {
+ *out++ = 0;
+ *out++ = 0;
+ *out++ = 0;
+ *out++ = ~(*in++);
+ }
+}
+
+static void
+la2cmyk(UINT8 *out, const UINT8 *in, int xsize) {
+ int x;
+ for (x = 0; x < xsize; x++, in += 4) {
+ *out++ = 0;
+ *out++ = 0;
+ *out++ = 0;
+ *out++ = ~(in[0]);
+ }
+}
+
+static void
+rgb2cmyk(UINT8 *out, const UINT8 *in, int xsize) {
+ int x;
+ for (x = 0; x < xsize; x++) {
+ /* Note: no undercolour removal */
+ *out++ = ~(*in++);
+ *out++ = ~(*in++);
+ *out++ = ~(*in++);
+ *out++ = 0;
+ in++;
+ }
+}
+
+void
+cmyk2rgb(UINT8 *out, const UINT8 *in, int xsize) {
+ int x, nk, tmp;
+ for (x = 0; x < xsize; x++) {
+ nk = 255 - in[3];
+ out[0] = CLIP8(nk - MULDIV255(in[0], nk, tmp));
+ out[1] = CLIP8(nk - MULDIV255(in[1], nk, tmp));
+ out[2] = CLIP8(nk - MULDIV255(in[2], nk, tmp));
+ out[3] = 255;
+ out += 4;
+ in += 4;
+ }
+}
+
+static void
+cmyk2hsv(UINT8 *out, const UINT8 *in, int xsize) {
+ int x, nk, tmp;
+ for (x = 0; x < xsize; x++) {
+ nk = 255 - in[3];
+ out[0] = CLIP8(nk - MULDIV255(in[0], nk, tmp));
+ out[1] = CLIP8(nk - MULDIV255(in[1], nk, tmp));
+ out[2] = CLIP8(nk - MULDIV255(in[2], nk, tmp));
+ rgb2hsv_row(out, out);
+ out[3] = 255;
+ out += 4;
+ in += 4;
+ }
+}
+
+/* ------------- */
+/* I conversions */
+/* ------------- */
+
+static void
+bit2i(UINT8 *out_, const UINT8 *in, int xsize) {
+ int x;
+ for (x = 0; x < xsize; x++, out_ += 4) {
+ INT32 v = (*in++ != 0) ? 255 : 0;
+ memcpy(out_, &v, sizeof(v));
+ }
+}
+
+static void
+l2i(UINT8 *out_, const UINT8 *in, int xsize) {
+ int x;
+ for (x = 0; x < xsize; x++, out_ += 4) {
+ INT32 v = *in++;
+ memcpy(out_, &v, sizeof(v));
+ }
+}
+
+static void
+i2l(UINT8 *out, const UINT8 *in_, int xsize) {
+ int x;
+ for (x = 0; x < xsize; x++, out++, in_ += 4) {
+ INT32 v;
+ memcpy(&v, in_, sizeof(v));
+ if (v <= 0) {
+ *out = 0;
+ } else if (v >= 255) {
+ *out = 255;
+ } else {
+ *out = (UINT8)v;
+ }
+ }
+}
+
+static void
+i2f(UINT8 *out_, const UINT8 *in_, int xsize) {
+ int x;
+ for (x = 0; x < xsize; x++, in_ += 4, out_ += 4) {
+ INT32 i;
+ FLOAT32 f;
+ memcpy(&i, in_, sizeof(i));
+ f = i;
+ memcpy(out_, &f, sizeof(f));
+ }
+}
+
+static void
+i2rgb(UINT8 *out, const UINT8 *in_, int xsize) {
+ int x;
+ INT32 *in = (INT32 *)in_;
+ for (x = 0; x < xsize; x++, in++, out += 4) {
+ if (*in <= 0) {
+ out[0] = out[1] = out[2] = 0;
+ } else if (*in >= 255) {
+ out[0] = out[1] = out[2] = 255;
+ } else {
+ out[0] = out[1] = out[2] = (UINT8)*in;
+ }
+ out[3] = 255;
+ }
+}
+
+static void
+i2hsv(UINT8 *out, const UINT8 *in_, int xsize) {
+ int x;
+ INT32 *in = (INT32 *)in_;
+ for (x = 0; x < xsize; x++, in++, out += 4) {
+ out[0] = 0;
+ out[1] = 0;
+ if (*in <= 0) {
+ out[2] = 0;
+ } else if (*in >= 255) {
+ out[2] = 255;
+ } else {
+ out[2] = (UINT8)*in;
+ }
+ out[3] = 255;
+ }
+}
+
+/* ------------- */
+/* F conversions */
+/* ------------- */
+
+static void
+bit2f(UINT8 *out_, const UINT8 *in, int xsize) {
+ int x;
+ for (x = 0; x < xsize; x++, out_ += 4) {
+ FLOAT32 f = (*in++ != 0) ? 255.0F : 0.0F;
+ memcpy(out_, &f, sizeof(f));
+ }
+}
+
+static void
+l2f(UINT8 *out_, const UINT8 *in, int xsize) {
+ int x;
+ for (x = 0; x < xsize; x++, out_ += 4) {
+ FLOAT32 f = (FLOAT32)*in++;
+ memcpy(out_, &f, sizeof(f));
+ }
+}
+
+static void
+f2l(UINT8 *out, const UINT8 *in_, int xsize) {
+ int x;
+ for (x = 0; x < xsize; x++, out++, in_ += 4) {
+ FLOAT32 v;
+ memcpy(&v, in_, sizeof(v));
+ if (v <= 0.0) {
+ *out = 0;
+ } else if (v >= 255.0) {
+ *out = 255;
+ } else {
+ *out = (UINT8)v;
+ }
+ }
+}
+
+static void
+f2i(UINT8 *out_, const UINT8 *in_, int xsize) {
+ int x;
+ for (x = 0; x < xsize; x++, in_ += 4, out_ += 4) {
+ FLOAT32 f;
+ INT32 i;
+ memcpy(&f, in_, sizeof(f));
+ i = f;
+ memcpy(out_, &i, sizeof(i));
+ }
+}
+
+/* ----------------- */
+/* YCbCr conversions */
+/* ----------------- */
+
+/* See ConvertYCbCr.c for RGB/YCbCr tables */
+
+static void
+l2ycbcr(UINT8 *out, const UINT8 *in, int xsize) {
+ int x;
+ for (x = 0; x < xsize; x++) {
+ *out++ = *in++;
+ *out++ = 128;
+ *out++ = 128;
+ *out++ = 255;
+ }
+}
+
+static void
+la2ycbcr(UINT8 *out, const UINT8 *in, int xsize) {
+ int x;
+ for (x = 0; x < xsize; x++, in += 4) {
+ *out++ = in[0];
+ *out++ = 128;
+ *out++ = 128;
+ *out++ = 255;
+ }
+}
+
+static void
+ycbcr2l(UINT8 *out, const UINT8 *in, int xsize) {
+ int x;
+ for (x = 0; x < xsize; x++, in += 4) {
+ *out++ = in[0];
+ }
+}
+
+static void
+ycbcr2la(UINT8 *out, const UINT8 *in, int xsize) {
+ int x;
+ for (x = 0; x < xsize; x++, in += 4, out += 4) {
+ out[0] = out[1] = out[2] = in[0];
+ out[3] = 255;
+ }
+}
+
+/* ------------------------- */
+/* I;16 (16-bit) conversions */
+/* ------------------------- */
+
+static void
+I_I16L(UINT8 *out, const UINT8 *in_, int xsize) {
+ int x, v;
+ for (x = 0; x < xsize; x++, in_ += 4) {
+ INT32 i;
+ memcpy(&i, in_, sizeof(i));
+ v = CLIP16(i);
+ *out++ = (UINT8)v;
+ *out++ = (UINT8)(v >> 8);
+ }
+}
+
+static void
+I_I16B(UINT8 *out, const UINT8 *in_, int xsize) {
+ int x, v;
+ for (x = 0; x < xsize; x++, in_ += 4) {
+ INT32 i;
+ memcpy(&i, in_, sizeof(i));
+ v = CLIP16(i);
+ *out++ = (UINT8)(v >> 8);
+ *out++ = (UINT8)v;
+ }
+}
+
+static void
+I16L_I(UINT8 *out_, const UINT8 *in, int xsize) {
+ int x;
+ for (x = 0; x < xsize; x++, in += 2, out_ += 4) {
+ INT32 v = in[0] + ((int)in[1] << 8);
+ memcpy(out_, &v, sizeof(v));
+ }
+}
+
+static void
+I16B_I(UINT8 *out_, const UINT8 *in, int xsize) {
+ int x;
+ for (x = 0; x < xsize; x++, in += 2, out_ += 4) {
+ INT32 v = in[1] + ((int)in[0] << 8);
+ memcpy(out_, &v, sizeof(v));
+ }
+}
+
+static void
+I16L_F(UINT8 *out_, const UINT8 *in, int xsize) {
+ int x;
+ for (x = 0; x < xsize; x++, in += 2, out_ += 4) {
+ FLOAT32 v = in[0] + ((int)in[1] << 8);
+ memcpy(out_, &v, sizeof(v));
+ }
+}
+
+static void
+I16B_F(UINT8 *out_, const UINT8 *in, int xsize) {
+ int x;
+ for (x = 0; x < xsize; x++, in += 2, out_ += 4) {
+ FLOAT32 v = in[1] + ((int)in[0] << 8);
+ memcpy(out_, &v, sizeof(v));
+ }
+}
+
+static void
+L_I16L(UINT8 *out, const UINT8 *in, int xsize) {
+ int x;
+ for (x = 0; x < xsize; x++, in++) {
+ *out++ = *in;
+ *out++ = 0;
+ }
+}
+
+static void
+L_I16B(UINT8 *out, const UINT8 *in, int xsize) {
+ int x;
+ for (x = 0; x < xsize; x++, in++) {
+ *out++ = 0;
+ *out++ = *in;
+ }
+}
+
+static void
+I16L_L(UINT8 *out, const UINT8 *in, int xsize) {
+ int x;
+ for (x = 0; x < xsize; x++, in += 2) {
+ if (in[1] != 0) {
+ *out++ = 255;
+ } else {
+ *out++ = in[0];
+ }
+ }
+}
+
+static void
+I16B_L(UINT8 *out, const UINT8 *in, int xsize) {
+ int x;
+ for (x = 0; x < xsize; x++, in += 2) {
+ if (in[0] != 0) {
+ *out++ = 255;
+ } else {
+ *out++ = in[1];
+ }
+ }
+}
+
+static struct {
+ const char *from;
+ const char *to;
+ ImagingShuffler convert;
+} converters[] = {
+
+ {"1", "L", bit2l},
+ {"1", "I", bit2i},
+ {"1", "F", bit2f},
+ {"1", "RGB", bit2rgb},
+ {"1", "RGBA", bit2rgb},
+ {"1", "RGBX", bit2rgb},
+ {"1", "CMYK", bit2cmyk},
+ {"1", "YCbCr", bit2ycbcr},
+ {"1", "HSV", bit2hsv},
+
+ {"L", "1", l2bit},
+ {"L", "LA", l2la},
+ {"L", "I", l2i},
+ {"L", "F", l2f},
+ {"L", "RGB", l2rgb},
+ {"L", "RGBA", l2rgb},
+ {"L", "RGBX", l2rgb},
+ {"L", "CMYK", l2cmyk},
+ {"L", "YCbCr", l2ycbcr},
+ {"L", "HSV", l2hsv},
+
+ {"LA", "L", la2l},
+ {"LA", "La", lA2la},
+ {"LA", "RGB", la2rgb},
+ {"LA", "RGBA", la2rgb},
+ {"LA", "RGBX", la2rgb},
+ {"LA", "CMYK", la2cmyk},
+ {"LA", "YCbCr", la2ycbcr},
+ {"LA", "HSV", la2hsv},
+
+ {"La", "LA", la2lA},
+
+ {"I", "L", i2l},
+ {"I", "F", i2f},
+ {"I", "RGB", i2rgb},
+ {"I", "RGBA", i2rgb},
+ {"I", "RGBX", i2rgb},
+ {"I", "HSV", i2hsv},
+
+ {"F", "L", f2l},
+ {"F", "I", f2i},
+
+ {"RGB", "1", rgb2bit},
+ {"RGB", "L", rgb2l},
+ {"RGB", "LA", rgb2la},
+ {"RGB", "I", rgb2i},
+ {"RGB", "F", rgb2f},
+ {"RGB", "BGR;15", rgb2bgr15},
+ {"RGB", "BGR;16", rgb2bgr16},
+ {"RGB", "BGR;24", rgb2bgr24},
+ {"RGB", "RGBA", rgb2rgba},
+ {"RGB", "RGBX", rgb2rgba},
+ {"RGB", "CMYK", rgb2cmyk},
+ {"RGB", "YCbCr", ImagingConvertRGB2YCbCr},
+ {"RGB", "HSV", rgb2hsv},
+
+ {"RGBA", "1", rgb2bit},
+ {"RGBA", "L", rgb2l},
+ {"RGBA", "LA", rgba2la},
+ {"RGBA", "I", rgb2i},
+ {"RGBA", "F", rgb2f},
+ {"RGBA", "RGB", rgba2rgb},
+ {"RGBA", "RGBa", rgbA2rgba},
+ {"RGBA", "RGBX", rgb2rgba},
+ {"RGBA", "CMYK", rgb2cmyk},
+ {"RGBA", "YCbCr", ImagingConvertRGB2YCbCr},
+ {"RGBA", "HSV", rgb2hsv},
+
+ {"RGBa", "RGBA", rgba2rgbA},
+ {"RGBa", "RGB", rgba2rgb_},
+
+ {"RGBX", "1", rgb2bit},
+ {"RGBX", "L", rgb2l},
+ {"RGBX", "LA", rgb2la},
+ {"RGBX", "I", rgb2i},
+ {"RGBX", "F", rgb2f},
+ {"RGBX", "RGB", rgba2rgb},
+ {"RGBX", "CMYK", rgb2cmyk},
+ {"RGBX", "YCbCr", ImagingConvertRGB2YCbCr},
+ {"RGBX", "HSV", rgb2hsv},
+
+ {"CMYK", "RGB", cmyk2rgb},
+ {"CMYK", "RGBA", cmyk2rgb},
+ {"CMYK", "RGBX", cmyk2rgb},
+ {"CMYK", "HSV", cmyk2hsv},
+
+ {"YCbCr", "L", ycbcr2l},
+ {"YCbCr", "LA", ycbcr2la},
+ {"YCbCr", "RGB", ImagingConvertYCbCr2RGB},
+
+ {"HSV", "RGB", hsv2rgb},
+
+ {"I", "I;16", I_I16L},
+ {"I;16", "I", I16L_I},
+ {"L", "I;16", L_I16L},
+ {"I;16", "L", I16L_L},
+
+ {"I", "I;16L", I_I16L},
+ {"I;16L", "I", I16L_I},
+ {"I", "I;16B", I_I16B},
+ {"I;16B", "I", I16B_I},
+
+ {"L", "I;16L", L_I16L},
+ {"I;16L", "L", I16L_L},
+ {"L", "I;16B", L_I16B},
+ {"I;16B", "L", I16B_L},
+#ifdef WORDS_BIGENDIAN
+ {"L", "I;16N", L_I16B},
+ {"I;16N", "L", I16B_L},
+#else
+ {"L", "I;16N", L_I16L},
+ {"I;16N", "L", I16L_L},
+#endif
+
+ {"I;16", "F", I16L_F},
+ {"I;16L", "F", I16L_F},
+ {"I;16B", "F", I16B_F},
+
+ {NULL}};
+
+/* FIXME: translate indexed versions to pointer versions below this line */
+
+/* ------------------- */
+/* Palette conversions */
+/* ------------------- */
+
+static void
+p2bit(UINT8 *out, const UINT8 *in, int xsize, ImagingPalette palette) {
+ int x;
+ /* FIXME: precalculate greyscale palette? */
+ for (x = 0; x < xsize; x++) {
+ *out++ = (L(&palette->palette[in[x] * 4]) >= 128000) ? 255 : 0;
+ }
+}
+
+static void
+pa2bit(UINT8 *out, const UINT8 *in, int xsize, ImagingPalette palette) {
+ int x;
+ /* FIXME: precalculate greyscale palette? */
+ for (x = 0; x < xsize; x++, in += 4) {
+ *out++ = (L(&palette->palette[in[0] * 4]) >= 128000) ? 255 : 0;
+ }
+}
+
+static void
+p2l(UINT8 *out, const UINT8 *in, int xsize, ImagingPalette palette) {
+ int x;
+ /* FIXME: precalculate greyscale palette? */
+ for (x = 0; x < xsize; x++) {
+ *out++ = L24(&palette->palette[in[x] * 4]) >> 16;
+ }
+}
+
+static void
+pa2l(UINT8 *out, const UINT8 *in, int xsize, ImagingPalette palette) {
+ int x;
+ /* FIXME: precalculate greyscale palette? */
+ for (x = 0; x < xsize; x++, in += 4) {
+ *out++ = L24(&palette->palette[in[0] * 4]) >> 16;
+ }
+}
+
+static void
+pa2p(UINT8 *out, const UINT8 *in, int xsize, ImagingPalette palette) {
+ int x;
+ for (x = 0; x < xsize; x++, in += 4) {
+ *out++ = in[0];
+ }
+}
+
+static void
+p2pa(UINT8 *out, const UINT8 *in, int xsize, ImagingPalette palette) {
+ int x;
+ int rgb = strcmp(palette->mode, "RGB");
+ for (x = 0; x < xsize; x++, in++) {
+ const UINT8 *rgba = &palette->palette[in[0] * 4];
+ *out++ = in[0];
+ *out++ = in[0];
+ *out++ = in[0];
+ *out++ = rgb == 0 ? 255 : rgba[3];
+ }
+}
+
+static void
+p2la(UINT8 *out, const UINT8 *in, int xsize, ImagingPalette palette) {
+ int x;
+ /* FIXME: precalculate greyscale palette? */
+ for (x = 0; x < xsize; x++, out += 4) {
+ const UINT8 *rgba = &palette->palette[*in++ * 4];
+ out[0] = out[1] = out[2] = L24(rgba) >> 16;
+ out[3] = rgba[3];
+ }
+}
+
+static void
+pa2la(UINT8 *out, const UINT8 *in, int xsize, ImagingPalette palette) {
+ int x;
+ /* FIXME: precalculate greyscale palette? */
+ for (x = 0; x < xsize; x++, in += 4, out += 4) {
+ out[0] = out[1] = out[2] = L24(&palette->palette[in[0] * 4]) >> 16;
+ out[3] = in[3];
+ }
+}
+
+static void
+p2i(UINT8 *out_, const UINT8 *in, int xsize, ImagingPalette palette) {
+ int x;
+ for (x = 0; x < xsize; x++, out_ += 4) {
+ INT32 v = L24(&palette->palette[in[x] * 4]) >> 16;
+ memcpy(out_, &v, sizeof(v));
+ }
+}
+
+static void
+pa2i(UINT8 *out_, const UINT8 *in, int xsize, ImagingPalette palette) {
+ int x;
+ INT32 *out = (INT32 *)out_;
+ for (x = 0; x < xsize; x++, in += 4) {
+ *out++ = L24(&palette->palette[in[0] * 4]) >> 16;
+ }
+}
+
+static void
+p2f(UINT8 *out_, const UINT8 *in, int xsize, ImagingPalette palette) {
+ int x;
+ for (x = 0; x < xsize; x++, out_ += 4) {
+ FLOAT32 v = L(&palette->palette[in[x] * 4]) / 1000.0F;
+ memcpy(out_, &v, sizeof(v));
+ }
+}
+
+static void
+pa2f(UINT8 *out_, const UINT8 *in, int xsize, ImagingPalette palette) {
+ int x;
+ FLOAT32 *out = (FLOAT32 *)out_;
+ for (x = 0; x < xsize; x++, in += 4) {
+ *out++ = (float)L(&palette->palette[in[0] * 4]) / 1000.0F;
+ }
+}
+
+static void
+p2rgb(UINT8 *out, const UINT8 *in, int xsize, ImagingPalette palette) {
+ int x;
+ for (x = 0; x < xsize; x++) {
+ const UINT8 *rgb = &palette->palette[*in++ * 4];
+ *out++ = rgb[0];
+ *out++ = rgb[1];
+ *out++ = rgb[2];
+ *out++ = 255;
+ }
+}
+
+static void
+pa2rgb(UINT8 *out, const UINT8 *in, int xsize, ImagingPalette palette) {
+ int x;
+ for (x = 0; x < xsize; x++, in += 4) {
+ const UINT8 *rgb = &palette->palette[in[0] * 4];
+ *out++ = rgb[0];
+ *out++ = rgb[1];
+ *out++ = rgb[2];
+ *out++ = 255;
+ }
+}
+
+static void
+p2hsv(UINT8 *out, const UINT8 *in, int xsize, ImagingPalette palette) {
+ int x;
+ for (x = 0; x < xsize; x++, out += 4) {
+ const UINT8 *rgb = &palette->palette[*in++ * 4];
+ rgb2hsv_row(out, rgb);
+ out[3] = 255;
+ }
+}
+
+static void
+pa2hsv(UINT8 *out, const UINT8 *in, int xsize, ImagingPalette palette) {
+ int x;
+ for (x = 0; x < xsize; x++, in += 4, out += 4) {
+ const UINT8 *rgb = &palette->palette[in[0] * 4];
+ rgb2hsv_row(out, rgb);
+ out[3] = 255;
+ }
+}
+
+static void
+p2rgba(UINT8 *out, const UINT8 *in, int xsize, ImagingPalette palette) {
+ int x;
+ for (x = 0; x < xsize; x++) {
+ const UINT8 *rgba = &palette->palette[*in++ * 4];
+ *out++ = rgba[0];
+ *out++ = rgba[1];
+ *out++ = rgba[2];
+ *out++ = rgba[3];
+ }
+}
+
+static void
+pa2rgba(UINT8 *out, const UINT8 *in, int xsize, ImagingPalette palette) {
+ int x;
+ for (x = 0; x < xsize; x++, in += 4) {
+ const UINT8 *rgb = &palette->palette[in[0] * 4];
+ *out++ = rgb[0];
+ *out++ = rgb[1];
+ *out++ = rgb[2];
+ *out++ = in[3];
+ }
+}
+
+static void
+p2cmyk(UINT8 *out, const UINT8 *in, int xsize, ImagingPalette palette) {
+ p2rgb(out, in, xsize, palette);
+ rgb2cmyk(out, out, xsize);
+}
+
+static void
+pa2cmyk(UINT8 *out, const UINT8 *in, int xsize, ImagingPalette palette) {
+ pa2rgb(out, in, xsize, palette);
+ rgb2cmyk(out, out, xsize);
+}
+
+static void
+p2ycbcr(UINT8 *out, const UINT8 *in, int xsize, ImagingPalette palette) {
+ p2rgb(out, in, xsize, palette);
+ ImagingConvertRGB2YCbCr(out, out, xsize);
+}
+
+static void
+pa2ycbcr(UINT8 *out, const UINT8 *in, int xsize, ImagingPalette palette) {
+ pa2rgb(out, in, xsize, palette);
+ ImagingConvertRGB2YCbCr(out, out, xsize);
+}
+
+static Imaging
+frompalette(Imaging imOut, Imaging imIn, const char *mode) {
+ ImagingSectionCookie cookie;
+ int alpha;
+ int y;
+ void (*convert)(UINT8 *, const UINT8 *, int, ImagingPalette);
+
+ /* Map palette image to L, RGB, RGBA, or CMYK */
+
+ if (!imIn->palette) {
+ return (Imaging)ImagingError_ValueError("no palette");
+ }
+
+ alpha = !strcmp(imIn->mode, "PA");
+
+ if (strcmp(mode, "1") == 0) {
+ convert = alpha ? pa2bit : p2bit;
+ } else if (strcmp(mode, "L") == 0) {
+ convert = alpha ? pa2l : p2l;
+ } else if (strcmp(mode, "LA") == 0) {
+ convert = alpha ? pa2la : p2la;
+ } else if (strcmp(mode, "P") == 0) {
+ convert = pa2p;
+ } else if (strcmp(mode, "PA") == 0) {
+ convert = p2pa;
+ } else if (strcmp(mode, "I") == 0) {
+ convert = alpha ? pa2i : p2i;
+ } else if (strcmp(mode, "F") == 0) {
+ convert = alpha ? pa2f : p2f;
+ } else if (strcmp(mode, "RGB") == 0) {
+ convert = alpha ? pa2rgb : p2rgb;
+ } else if (strcmp(mode, "RGBA") == 0 || strcmp(mode, "RGBX") == 0) {
+ convert = alpha ? pa2rgba : p2rgba;
+ } else if (strcmp(mode, "CMYK") == 0) {
+ convert = alpha ? pa2cmyk : p2cmyk;
+ } else if (strcmp(mode, "YCbCr") == 0) {
+ convert = alpha ? pa2ycbcr : p2ycbcr;
+ } else if (strcmp(mode, "HSV") == 0) {
+ convert = alpha ? pa2hsv : p2hsv;
+ } else {
+ return (Imaging)ImagingError_ValueError("conversion not supported");
+ }
+
+ imOut = ImagingNew2Dirty(mode, imOut, imIn);
+ if (!imOut) {
+ return NULL;
+ }
+ if (strcmp(mode, "P") == 0 || strcmp(mode, "PA") == 0) {
+ ImagingPaletteDelete(imOut->palette);
+ imOut->palette = ImagingPaletteDuplicate(imIn->palette);
+ }
+
+ ImagingSectionEnter(&cookie);
+ for (y = 0; y < imIn->ysize; y++) {
+ (*convert)(
+ (UINT8 *)imOut->image[y],
+ (UINT8 *)imIn->image[y],
+ imIn->xsize,
+ imIn->palette);
+ }
+ ImagingSectionLeave(&cookie);
+
+ return imOut;
+}
+
+#if defined(_MSC_VER)
+#pragma optimize("", off)
+#endif
+static Imaging
+topalette(
+ Imaging imOut,
+ Imaging imIn,
+ const char *mode,
+ ImagingPalette inpalette,
+ int dither) {
+ ImagingSectionCookie cookie;
+ int alpha;
+ int x, y;
+ ImagingPalette palette = inpalette;
+
+ /* Map L or RGB/RGBX/RGBA to palette image */
+ if (strcmp(imIn->mode, "L") != 0 && strncmp(imIn->mode, "RGB", 3) != 0) {
+ return (Imaging)ImagingError_ValueError("conversion not supported");
+ }
+
+ alpha = !strcmp(mode, "PA");
+
+ if (palette == NULL) {
+ /* FIXME: make user configurable */
+ if (imIn->bands == 1) {
+ palette = ImagingPaletteNew("RGB");
+
+ palette->size = 256;
+ int i;
+ for (i = 0; i < 256; i++) {
+ palette->palette[i * 4] = palette->palette[i * 4 + 1] =
+ palette->palette[i * 4 + 2] = (UINT8)i;
+ }
+ } else {
+ palette = ImagingPaletteNewBrowser(); /* Standard colour cube */
+ }
+ }
+
+ if (!palette) {
+ return (Imaging)ImagingError_ValueError("no palette");
+ }
+
+ imOut = ImagingNew2Dirty(mode, imOut, imIn);
+ if (!imOut) {
+ if (palette != inpalette) {
+ ImagingPaletteDelete(palette);
+ }
+ return NULL;
+ }
+
+ ImagingPaletteDelete(imOut->palette);
+ imOut->palette = ImagingPaletteDuplicate(palette);
+
+ if (imIn->bands == 1) {
+ /* greyscale image */
+
+ /* Greyscale palette: copy data as is */
+ ImagingSectionEnter(&cookie);
+ for (y = 0; y < imIn->ysize; y++) {
+ if (alpha) {
+ l2la((UINT8 *)imOut->image[y], (UINT8 *)imIn->image[y], imIn->xsize);
+ } else {
+ memcpy(imOut->image[y], imIn->image[y], imIn->linesize);
+ }
+ }
+ ImagingSectionLeave(&cookie);
+
+ } else {
+ /* colour image */
+
+ /* Create mapping cache */
+ if (ImagingPaletteCachePrepare(palette) < 0) {
+ ImagingDelete(imOut);
+ if (palette != inpalette) {
+ ImagingPaletteDelete(palette);
+ }
+ return NULL;
+ }
+
+ if (dither) {
+ /* floyd-steinberg dither */
+
+ int *errors;
+ errors = calloc(imIn->xsize + 1, sizeof(int) * 3);
+ if (!errors) {
+ ImagingDelete(imOut);
+ return ImagingError_MemoryError();
+ }
+
+ /* Map each pixel to the nearest palette entry */
+ ImagingSectionEnter(&cookie);
+ for (y = 0; y < imIn->ysize; y++) {
+ int r, r0, r1, r2;
+ int g, g0, g1, g2;
+ int b, b0, b1, b2;
+ UINT8 *in = (UINT8 *)imIn->image[y];
+ UINT8 *out = alpha ? (UINT8 *)imOut->image32[y] : imOut->image8[y];
+ int *e = errors;
+
+ r = r0 = r1 = 0;
+ g = g0 = g1 = 0;
+ b = b0 = b1 = b2 = 0;
+
+ for (x = 0; x < imIn->xsize; x++, in += 4) {
+ int d2;
+ INT16 *cache;
+
+ r = CLIP8(in[0] + (r + e[3 + 0]) / 16);
+ g = CLIP8(in[1] + (g + e[3 + 1]) / 16);
+ b = CLIP8(in[2] + (b + e[3 + 2]) / 16);
+
+ /* get closest colour */
+ cache = &ImagingPaletteCache(palette, r, g, b);
+ if (cache[0] == 0x100) {
+ ImagingPaletteCacheUpdate(palette, r, g, b);
+ }
+ if (alpha) {
+ out[x * 4] = out[x * 4 + 1] = out[x * 4 + 2] = (UINT8)cache[0];
+ out[x * 4 + 3] = 255;
+ } else {
+ out[x] = (UINT8)cache[0];
+ }
+
+ r -= (int)palette->palette[cache[0] * 4];
+ g -= (int)palette->palette[cache[0] * 4 + 1];
+ b -= (int)palette->palette[cache[0] * 4 + 2];
+
+ /* propagate errors (don't ask ;-) */
+ r2 = r;
+ d2 = r + r;
+ r += d2;
+ e[0] = r + r0;
+ r += d2;
+ r0 = r + r1;
+ r1 = r2;
+ r += d2;
+ g2 = g;
+ d2 = g + g;
+ g += d2;
+ e[1] = g + g0;
+ g += d2;
+ g0 = g + g1;
+ g1 = g2;
+ g += d2;
+ b2 = b;
+ d2 = b + b;
+ b += d2;
+ e[2] = b + b0;
+ b += d2;
+ b0 = b + b1;
+ b1 = b2;
+ b += d2;
+
+ e += 3;
+ }
+
+ e[0] = b0;
+ e[1] = b1;
+ e[2] = b2;
+ }
+ ImagingSectionLeave(&cookie);
+ free(errors);
+
+ } else {
+ /* closest colour */
+ ImagingSectionEnter(&cookie);
+ for (y = 0; y < imIn->ysize; y++) {
+ int r, g, b;
+ UINT8 *in = (UINT8 *)imIn->image[y];
+ UINT8 *out = alpha ? (UINT8 *)imOut->image32[y] : imOut->image8[y];
+
+ for (x = 0; x < imIn->xsize; x++, in += 4) {
+ INT16 *cache;
+
+ r = in[0];
+ g = in[1];
+ b = in[2];
+
+ /* get closest colour */
+ cache = &ImagingPaletteCache(palette, r, g, b);
+ if (cache[0] == 0x100) {
+ ImagingPaletteCacheUpdate(palette, r, g, b);
+ }
+ if (alpha) {
+ out[x * 4] = out[x * 4 + 1] = out[x * 4 + 2] = (UINT8)cache[0];
+ out[x * 4 + 3] = 255;
+ } else {
+ out[x] = (UINT8)cache[0];
+ }
+ }
+ }
+ ImagingSectionLeave(&cookie);
+ }
+ if (inpalette != palette) {
+ ImagingPaletteCacheDelete(palette);
+ }
+ }
+
+ if (inpalette != palette) {
+ ImagingPaletteDelete(palette);
+ }
+
+ return imOut;
+}
+
+static Imaging
+tobilevel(Imaging imOut, Imaging imIn) {
+ ImagingSectionCookie cookie;
+ int x, y;
+ int *errors;
+
+ /* Map L or RGB to dithered 1 image */
+ if (strcmp(imIn->mode, "L") != 0 && strcmp(imIn->mode, "RGB") != 0) {
+ return (Imaging)ImagingError_ValueError("conversion not supported");
+ }
+
+ imOut = ImagingNew2Dirty("1", imOut, imIn);
+ if (!imOut) {
+ return NULL;
+ }
+
+ errors = calloc(imIn->xsize + 1, sizeof(int));
+ if (!errors) {
+ ImagingDelete(imOut);
+ return ImagingError_MemoryError();
+ }
+
+ if (imIn->bands == 1) {
+ /* map each pixel to black or white, using error diffusion */
+ ImagingSectionEnter(&cookie);
+ for (y = 0; y < imIn->ysize; y++) {
+ int l, l0, l1, l2, d2;
+ UINT8 *in = (UINT8 *)imIn->image[y];
+ UINT8 *out = imOut->image8[y];
+
+ l = l0 = l1 = 0;
+
+ for (x = 0; x < imIn->xsize; x++) {
+ /* pick closest colour */
+ l = CLIP8(in[x] + (l + errors[x + 1]) / 16);
+ out[x] = (l > 128) ? 255 : 0;
+
+ /* propagate errors */
+ l -= (int)out[x];
+ l2 = l;
+ d2 = l + l;
+ l += d2;
+ errors[x] = l + l0;
+ l += d2;
+ l0 = l + l1;
+ l1 = l2;
+ l += d2;
+ }
+
+ errors[x] = l0;
+ }
+ ImagingSectionLeave(&cookie);
+
+ } else {
+ /* map each pixel to black or white, using error diffusion */
+ ImagingSectionEnter(&cookie);
+ for (y = 0; y < imIn->ysize; y++) {
+ int l, l0, l1, l2, d2;
+ UINT8 *in = (UINT8 *)imIn->image[y];
+ UINT8 *out = imOut->image8[y];
+
+ l = l0 = l1 = 0;
+
+ for (x = 0; x < imIn->xsize; x++, in += 4) {
+ /* pick closest colour */
+ l = CLIP8(L(in) / 1000 + (l + errors[x + 1]) / 16);
+ out[x] = (l > 128) ? 255 : 0;
+
+ /* propagate errors */
+ l -= (int)out[x];
+ l2 = l;
+ d2 = l + l;
+ l += d2;
+ errors[x] = l + l0;
+ l += d2;
+ l0 = l + l1;
+ l1 = l2;
+ l += d2;
+ }
+
+ errors[x] = l0;
+ }
+ ImagingSectionLeave(&cookie);
+ }
+
+ free(errors);
+
+ return imOut;
+}
+#if defined(_MSC_VER)
+#pragma optimize("", on)
+#endif
+
+static Imaging
+convert(
+ Imaging imOut, Imaging imIn, const char *mode, ImagingPalette palette, int dither) {
+ ImagingSectionCookie cookie;
+ ImagingShuffler convert;
+ int y;
+
+ if (!imIn) {
+ return (Imaging)ImagingError_ModeError();
+ }
+
+ if (!mode) {
+ /* Map palette image to full depth */
+ if (!imIn->palette) {
+ return (Imaging)ImagingError_ModeError();
+ }
+ mode = imIn->palette->mode;
+ } else {
+ /* Same mode? */
+ if (!strcmp(imIn->mode, mode)) {
+ return ImagingCopy2(imOut, imIn);
+ }
+ }
+
+ /* test for special conversions */
+
+ if (strcmp(imIn->mode, "P") == 0 || strcmp(imIn->mode, "PA") == 0) {
+ return frompalette(imOut, imIn, mode);
+ }
+
+ if (strcmp(mode, "P") == 0 || strcmp(mode, "PA") == 0) {
+ return topalette(imOut, imIn, mode, palette, dither);
+ }
+
+ if (dither && strcmp(mode, "1") == 0) {
+ return tobilevel(imOut, imIn);
+ }
+
+ /* standard conversion machinery */
+
+ convert = NULL;
+
+ for (y = 0; converters[y].from; y++) {
+ if (!strcmp(imIn->mode, converters[y].from) &&
+ !strcmp(mode, converters[y].to)) {
+ convert = converters[y].convert;
+ break;
+ }
+ }
+
+ if (!convert) {
+#ifdef notdef
+ return (Imaging)ImagingError_ValueError("conversion not supported");
+#else
+ static char buf[100];
+ snprintf(buf, 100, "conversion from %.10s to %.10s not supported", imIn->mode, mode);
+ return (Imaging)ImagingError_ValueError(buf);
+#endif
+ }
+
+ imOut = ImagingNew2Dirty(mode, imOut, imIn);
+ if (!imOut) {
+ return NULL;
+ }
+
+ ImagingSectionEnter(&cookie);
+ for (y = 0; y < imIn->ysize; y++) {
+ (*convert)((UINT8 *)imOut->image[y], (UINT8 *)imIn->image[y], imIn->xsize);
+ }
+ ImagingSectionLeave(&cookie);
+
+ return imOut;
+}
+
+Imaging
+ImagingConvert(Imaging imIn, const char *mode, ImagingPalette palette, int dither) {
+ return convert(NULL, imIn, mode, palette, dither);
+}
+
+Imaging
+ImagingConvert2(Imaging imOut, Imaging imIn) {
+ return convert(imOut, imIn, imOut->mode, NULL, 0);
+}
+
+Imaging
+ImagingConvertTransparent(Imaging imIn, const char *mode, int r, int g, int b) {
+ ImagingSectionCookie cookie;
+ ImagingShuffler convert;
+ Imaging imOut = NULL;
+ int y;
+
+ if (!imIn) {
+ return (Imaging)ImagingError_ModeError();
+ }
+
+ if (strcmp(imIn->mode, "RGB") == 0 && strcmp(mode, "RGBA") == 0) {
+ convert = rgb2rgba;
+ } else if ((strcmp(imIn->mode, "1") == 0 ||
+ strcmp(imIn->mode, "I") == 0 ||
+ strcmp(imIn->mode, "L") == 0
+ ) && (
+ strcmp(mode, "RGBA") == 0 ||
+ strcmp(mode, "LA") == 0
+ )) {
+ if (strcmp(imIn->mode, "1") == 0) {
+ convert = bit2rgb;
+ } else if (strcmp(imIn->mode, "I") == 0) {
+ convert = i2rgb;
+ } else {
+ convert = l2rgb;
+ }
+ g = b = r;
+ } else {
+ static char buf[100];
+ snprintf(
+ buf,
+ 100,
+ "conversion from %.10s to %.10s not supported in convert_transparent",
+ imIn->mode,
+ mode);
+ return (Imaging)ImagingError_ValueError(buf);
+ }
+
+ imOut = ImagingNew2Dirty(mode, imOut, imIn);
+ if (!imOut) {
+ return NULL;
+ }
+
+ ImagingSectionEnter(&cookie);
+ for (y = 0; y < imIn->ysize; y++) {
+ (*convert)((UINT8 *)imOut->image[y], (UINT8 *)imIn->image[y], imIn->xsize);
+ rgbT2rgba((UINT8 *)imOut->image[y], imIn->xsize, r, g, b);
+ }
+ ImagingSectionLeave(&cookie);
+
+ return imOut;
+}
+
+Imaging
+ImagingConvertInPlace(Imaging imIn, const char *mode) {
+ ImagingSectionCookie cookie;
+ ImagingShuffler convert;
+ int y;
+
+ /* limited support for inplace conversion */
+ if (strcmp(imIn->mode, "L") == 0 && strcmp(mode, "1") == 0) {
+ convert = l2bit;
+ } else if (strcmp(imIn->mode, "1") == 0 && strcmp(mode, "L") == 0) {
+ convert = bit2l;
+ } else {
+ return ImagingError_ModeError();
+ }
+
+ ImagingSectionEnter(&cookie);
+ for (y = 0; y < imIn->ysize; y++) {
+ (*convert)((UINT8 *)imIn->image[y], (UINT8 *)imIn->image[y], imIn->xsize);
+ }
+ ImagingSectionLeave(&cookie);
+
+ return imIn;
+}
diff --git a/contrib/python/Pillow/py3/libImaging/Convert.h b/contrib/python/Pillow/py3/libImaging/Convert.h
new file mode 100644
index 00000000000..e688e301836
--- /dev/null
+++ b/contrib/python/Pillow/py3/libImaging/Convert.h
@@ -0,0 +1,2 @@
+extern void
+cmyk2rgb(UINT8 *out, const UINT8 *in, int xsize);
diff --git a/contrib/python/Pillow/py3/libImaging/ConvertYCbCr.c b/contrib/python/Pillow/py3/libImaging/ConvertYCbCr.c
new file mode 100644
index 00000000000..142f065e57d
--- /dev/null
+++ b/contrib/python/Pillow/py3/libImaging/ConvertYCbCr.c
@@ -0,0 +1,363 @@
+/*
+ * The Python Imaging Library.
+ * $Id$
+ *
+ * code to convert YCbCr data
+ *
+ * history:
+ * 98-07-01 hk Created
+ *
+ * Copyright (c) Secret Labs AB 1998
+ *
+ * See the README file for information on usage and redistribution.
+ */
+
+#include "Imaging.h"
+
+/* JPEG/JFIF YCbCr conversions
+
+ Y = R * 0.29900 + G * 0.58700 + B * 0.11400
+ Cb = R * -0.16874 + G * -0.33126 + B * 0.50000 + 128
+ Cr = R * 0.50000 + G * -0.41869 + B * -0.08131 + 128
+
+ R = Y + + (Cr - 128) * 1.40200
+ G = Y + (Cb - 128) * -0.34414 + (Cr - 128) * -0.71414
+ B = Y + (Cb - 128) * 1.77200
+
+*/
+
+#define SCALE 6 /* bits */
+
+static INT16 Y_R[] = {
+ 0, 19, 38, 57, 77, 96, 115, 134, 153, 172, 191, 210, 230, 249,
+ 268, 287, 306, 325, 344, 364, 383, 402, 421, 440, 459, 478, 498, 517,
+ 536, 555, 574, 593, 612, 631, 651, 670, 689, 708, 727, 746, 765, 785,
+ 804, 823, 842, 861, 880, 899, 919, 938, 957, 976, 995, 1014, 1033, 1052,
+ 1072, 1091, 1110, 1129, 1148, 1167, 1186, 1206, 1225, 1244, 1263, 1282, 1301, 1320,
+ 1340, 1359, 1378, 1397, 1416, 1435, 1454, 1473, 1493, 1512, 1531, 1550, 1569, 1588,
+ 1607, 1627, 1646, 1665, 1684, 1703, 1722, 1741, 1761, 1780, 1799, 1818, 1837, 1856,
+ 1875, 1894, 1914, 1933, 1952, 1971, 1990, 2009, 2028, 2048, 2067, 2086, 2105, 2124,
+ 2143, 2162, 2182, 2201, 2220, 2239, 2258, 2277, 2296, 2315, 2335, 2354, 2373, 2392,
+ 2411, 2430, 2449, 2469, 2488, 2507, 2526, 2545, 2564, 2583, 2602, 2622, 2641, 2660,
+ 2679, 2698, 2717, 2736, 2756, 2775, 2794, 2813, 2832, 2851, 2870, 2890, 2909, 2928,
+ 2947, 2966, 2985, 3004, 3023, 3043, 3062, 3081, 3100, 3119, 3138, 3157, 3177, 3196,
+ 3215, 3234, 3253, 3272, 3291, 3311, 3330, 3349, 3368, 3387, 3406, 3425, 3444, 3464,
+ 3483, 3502, 3521, 3540, 3559, 3578, 3598, 3617, 3636, 3655, 3674, 3693, 3712, 3732,
+ 3751, 3770, 3789, 3808, 3827, 3846, 3865, 3885, 3904, 3923, 3942, 3961, 3980, 3999,
+ 4019, 4038, 4057, 4076, 4095, 4114, 4133, 4153, 4172, 4191, 4210, 4229, 4248, 4267,
+ 4286, 4306, 4325, 4344, 4363, 4382, 4401, 4420, 4440, 4459, 4478, 4497, 4516, 4535,
+ 4554, 4574, 4593, 4612, 4631, 4650, 4669, 4688, 4707, 4727, 4746, 4765, 4784, 4803,
+ 4822, 4841, 4861, 4880};
+
+static INT16 Y_G[] = {
+ 0, 38, 75, 113, 150, 188, 225, 263, 301, 338, 376, 413, 451, 488,
+ 526, 564, 601, 639, 676, 714, 751, 789, 826, 864, 902, 939, 977, 1014,
+ 1052, 1089, 1127, 1165, 1202, 1240, 1277, 1315, 1352, 1390, 1428, 1465, 1503, 1540,
+ 1578, 1615, 1653, 1691, 1728, 1766, 1803, 1841, 1878, 1916, 1954, 1991, 2029, 2066,
+ 2104, 2141, 2179, 2217, 2254, 2292, 2329, 2367, 2404, 2442, 2479, 2517, 2555, 2592,
+ 2630, 2667, 2705, 2742, 2780, 2818, 2855, 2893, 2930, 2968, 3005, 3043, 3081, 3118,
+ 3156, 3193, 3231, 3268, 3306, 3344, 3381, 3419, 3456, 3494, 3531, 3569, 3607, 3644,
+ 3682, 3719, 3757, 3794, 3832, 3870, 3907, 3945, 3982, 4020, 4057, 4095, 4132, 4170,
+ 4208, 4245, 4283, 4320, 4358, 4395, 4433, 4471, 4508, 4546, 4583, 4621, 4658, 4696,
+ 4734, 4771, 4809, 4846, 4884, 4921, 4959, 4997, 5034, 5072, 5109, 5147, 5184, 5222,
+ 5260, 5297, 5335, 5372, 5410, 5447, 5485, 5522, 5560, 5598, 5635, 5673, 5710, 5748,
+ 5785, 5823, 5861, 5898, 5936, 5973, 6011, 6048, 6086, 6124, 6161, 6199, 6236, 6274,
+ 6311, 6349, 6387, 6424, 6462, 6499, 6537, 6574, 6612, 6650, 6687, 6725, 6762, 6800,
+ 6837, 6875, 6913, 6950, 6988, 7025, 7063, 7100, 7138, 7175, 7213, 7251, 7288, 7326,
+ 7363, 7401, 7438, 7476, 7514, 7551, 7589, 7626, 7664, 7701, 7739, 7777, 7814, 7852,
+ 7889, 7927, 7964, 8002, 8040, 8077, 8115, 8152, 8190, 8227, 8265, 8303, 8340, 8378,
+ 8415, 8453, 8490, 8528, 8566, 8603, 8641, 8678, 8716, 8753, 8791, 8828, 8866, 8904,
+ 8941, 8979, 9016, 9054, 9091, 9129, 9167, 9204, 9242, 9279, 9317, 9354, 9392, 9430,
+ 9467, 9505, 9542, 9580};
+
+static INT16 Y_B[] = {
+ 0, 7, 15, 22, 29, 36, 44, 51, 58, 66, 73, 80, 88, 95,
+ 102, 109, 117, 124, 131, 139, 146, 153, 161, 168, 175, 182, 190, 197,
+ 204, 212, 219, 226, 233, 241, 248, 255, 263, 270, 277, 285, 292, 299,
+ 306, 314, 321, 328, 336, 343, 350, 358, 365, 372, 379, 387, 394, 401,
+ 409, 416, 423, 430, 438, 445, 452, 460, 467, 474, 482, 489, 496, 503,
+ 511, 518, 525, 533, 540, 547, 554, 562, 569, 576, 584, 591, 598, 606,
+ 613, 620, 627, 635, 642, 649, 657, 664, 671, 679, 686, 693, 700, 708,
+ 715, 722, 730, 737, 744, 751, 759, 766, 773, 781, 788, 795, 803, 810,
+ 817, 824, 832, 839, 846, 854, 861, 868, 876, 883, 890, 897, 905, 912,
+ 919, 927, 934, 941, 948, 956, 963, 970, 978, 985, 992, 1000, 1007, 1014,
+ 1021, 1029, 1036, 1043, 1051, 1058, 1065, 1073, 1080, 1087, 1094, 1102, 1109, 1116,
+ 1124, 1131, 1138, 1145, 1153, 1160, 1167, 1175, 1182, 1189, 1197, 1204, 1211, 1218,
+ 1226, 1233, 1240, 1248, 1255, 1262, 1270, 1277, 1284, 1291, 1299, 1306, 1313, 1321,
+ 1328, 1335, 1342, 1350, 1357, 1364, 1372, 1379, 1386, 1394, 1401, 1408, 1415, 1423,
+ 1430, 1437, 1445, 1452, 1459, 1466, 1474, 1481, 1488, 1496, 1503, 1510, 1518, 1525,
+ 1532, 1539, 1547, 1554, 1561, 1569, 1576, 1583, 1591, 1598, 1605, 1612, 1620, 1627,
+ 1634, 1642, 1649, 1656, 1663, 1671, 1678, 1685, 1693, 1700, 1707, 1715, 1722, 1729,
+ 1736, 1744, 1751, 1758, 1766, 1773, 1780, 1788, 1795, 1802, 1809, 1817, 1824, 1831,
+ 1839, 1846, 1853, 1860};
+
+static INT16 Cb_R[] = {
+ 0, -10, -21, -31, -42, -53, -64, -75, -85, -96, -107, -118,
+ -129, -139, -150, -161, -172, -183, -193, -204, -215, -226, -237, -247,
+ -258, -269, -280, -291, -301, -312, -323, -334, -345, -355, -366, -377,
+ -388, -399, -409, -420, -431, -442, -453, -463, -474, -485, -496, -507,
+ -517, -528, -539, -550, -561, -571, -582, -593, -604, -615, -625, -636,
+ -647, -658, -669, -679, -690, -701, -712, -723, -733, -744, -755, -766,
+ -777, -787, -798, -809, -820, -831, -841, -852, -863, -874, -885, -895,
+ -906, -917, -928, -939, -949, -960, -971, -982, -993, -1003, -1014, -1025,
+ -1036, -1047, -1057, -1068, -1079, -1090, -1101, -1111, -1122, -1133, -1144, -1155,
+ -1165, -1176, -1187, -1198, -1209, -1219, -1230, -1241, -1252, -1263, -1273, -1284,
+ -1295, -1306, -1317, -1327, -1338, -1349, -1360, -1371, -1381, -1392, -1403, -1414,
+ -1425, -1435, -1446, -1457, -1468, -1479, -1489, -1500, -1511, -1522, -1533, -1543,
+ -1554, -1565, -1576, -1587, -1597, -1608, -1619, -1630, -1641, -1651, -1662, -1673,
+ -1684, -1694, -1705, -1716, -1727, -1738, -1748, -1759, -1770, -1781, -1792, -1802,
+ -1813, -1824, -1835, -1846, -1856, -1867, -1878, -1889, -1900, -1910, -1921, -1932,
+ -1943, -1954, -1964, -1975, -1986, -1997, -2008, -2018, -2029, -2040, -2051, -2062,
+ -2072, -2083, -2094, -2105, -2116, -2126, -2137, -2148, -2159, -2170, -2180, -2191,
+ -2202, -2213, -2224, -2234, -2245, -2256, -2267, -2278, -2288, -2299, -2310, -2321,
+ -2332, -2342, -2353, -2364, -2375, -2386, -2396, -2407, -2418, -2429, -2440, -2450,
+ -2461, -2472, -2483, -2494, -2504, -2515, -2526, -2537, -2548, -2558, -2569, -2580,
+ -2591, -2602, -2612, -2623, -2634, -2645, -2656, -2666, -2677, -2688, -2699, -2710,
+ -2720, -2731, -2742, -2753};
+
+static INT16 Cb_G[] = {
+ 0, -20, -41, -63, -84, -105, -126, -147, -169, -190, -211, -232,
+ -253, -275, -296, -317, -338, -359, -381, -402, -423, -444, -465, -487,
+ -508, -529, -550, -571, -593, -614, -635, -656, -677, -699, -720, -741,
+ -762, -783, -805, -826, -847, -868, -889, -911, -932, -953, -974, -995,
+ -1017, -1038, -1059, -1080, -1101, -1123, -1144, -1165, -1186, -1207, -1229, -1250,
+ -1271, -1292, -1313, -1335, -1356, -1377, -1398, -1419, -1441, -1462, -1483, -1504,
+ -1525, -1547, -1568, -1589, -1610, -1631, -1653, -1674, -1695, -1716, -1737, -1759,
+ -1780, -1801, -1822, -1843, -1865, -1886, -1907, -1928, -1949, -1971, -1992, -2013,
+ -2034, -2055, -2077, -2098, -2119, -2140, -2161, -2183, -2204, -2225, -2246, -2267,
+ -2289, -2310, -2331, -2352, -2373, -2395, -2416, -2437, -2458, -2479, -2501, -2522,
+ -2543, -2564, -2585, -2607, -2628, -2649, -2670, -2691, -2713, -2734, -2755, -2776,
+ -2797, -2819, -2840, -2861, -2882, -2903, -2925, -2946, -2967, -2988, -3009, -3031,
+ -3052, -3073, -3094, -3115, -3137, -3158, -3179, -3200, -3221, -3243, -3264, -3285,
+ -3306, -3328, -3349, -3370, -3391, -3412, -3434, -3455, -3476, -3497, -3518, -3540,
+ -3561, -3582, -3603, -3624, -3646, -3667, -3688, -3709, -3730, -3752, -3773, -3794,
+ -3815, -3836, -3858, -3879, -3900, -3921, -3942, -3964, -3985, -4006, -4027, -4048,
+ -4070, -4091, -4112, -4133, -4154, -4176, -4197, -4218, -4239, -4260, -4282, -4303,
+ -4324, -4345, -4366, -4388, -4409, -4430, -4451, -4472, -4494, -4515, -4536, -4557,
+ -4578, -4600, -4621, -4642, -4663, -4684, -4706, -4727, -4748, -4769, -4790, -4812,
+ -4833, -4854, -4875, -4896, -4918, -4939, -4960, -4981, -5002, -5024, -5045, -5066,
+ -5087, -5108, -5130, -5151, -5172, -5193, -5214, -5236, -5257, -5278, -5299, -5320,
+ -5342, -5363, -5384, -5405};
+
+static INT16 Cb_B[] = {
+ 0, 32, 64, 96, 128, 160, 192, 224, 256, 288, 320, 352, 384, 416,
+ 448, 480, 512, 544, 576, 608, 640, 672, 704, 736, 768, 800, 832, 864,
+ 896, 928, 960, 992, 1024, 1056, 1088, 1120, 1152, 1184, 1216, 1248, 1280, 1312,
+ 1344, 1376, 1408, 1440, 1472, 1504, 1536, 1568, 1600, 1632, 1664, 1696, 1728, 1760,
+ 1792, 1824, 1856, 1888, 1920, 1952, 1984, 2016, 2048, 2080, 2112, 2144, 2176, 2208,
+ 2240, 2272, 2304, 2336, 2368, 2400, 2432, 2464, 2496, 2528, 2560, 2592, 2624, 2656,
+ 2688, 2720, 2752, 2784, 2816, 2848, 2880, 2912, 2944, 2976, 3008, 3040, 3072, 3104,
+ 3136, 3168, 3200, 3232, 3264, 3296, 3328, 3360, 3392, 3424, 3456, 3488, 3520, 3552,
+ 3584, 3616, 3648, 3680, 3712, 3744, 3776, 3808, 3840, 3872, 3904, 3936, 3968, 4000,
+ 4032, 4064, 4096, 4128, 4160, 4192, 4224, 4256, 4288, 4320, 4352, 4384, 4416, 4448,
+ 4480, 4512, 4544, 4576, 4608, 4640, 4672, 4704, 4736, 4768, 4800, 4832, 4864, 4896,
+ 4928, 4960, 4992, 5024, 5056, 5088, 5120, 5152, 5184, 5216, 5248, 5280, 5312, 5344,
+ 5376, 5408, 5440, 5472, 5504, 5536, 5568, 5600, 5632, 5664, 5696, 5728, 5760, 5792,
+ 5824, 5856, 5888, 5920, 5952, 5984, 6016, 6048, 6080, 6112, 6144, 6176, 6208, 6240,
+ 6272, 6304, 6336, 6368, 6400, 6432, 6464, 6496, 6528, 6560, 6592, 6624, 6656, 6688,
+ 6720, 6752, 6784, 6816, 6848, 6880, 6912, 6944, 6976, 7008, 7040, 7072, 7104, 7136,
+ 7168, 7200, 7232, 7264, 7296, 7328, 7360, 7392, 7424, 7456, 7488, 7520, 7552, 7584,
+ 7616, 7648, 7680, 7712, 7744, 7776, 7808, 7840, 7872, 7904, 7936, 7968, 8000, 8032,
+ 8064, 8096, 8128, 8160};
+
+#define Cr_R Cb_B
+
+static INT16 Cr_G[] = {
+ 0, -26, -53, -79, -106, -133, -160, -187, -213, -240, -267, -294,
+ -321, -347, -374, -401, -428, -455, -481, -508, -535, -562, -589, -615,
+ -642, -669, -696, -722, -749, -776, -803, -830, -856, -883, -910, -937,
+ -964, -990, -1017, -1044, -1071, -1098, -1124, -1151, -1178, -1205, -1232, -1258,
+ -1285, -1312, -1339, -1366, -1392, -1419, -1446, -1473, -1500, -1526, -1553, -1580,
+ -1607, -1634, -1660, -1687, -1714, -1741, -1768, -1794, -1821, -1848, -1875, -1902,
+ -1928, -1955, -1982, -2009, -2036, -2062, -2089, -2116, -2143, -2169, -2196, -2223,
+ -2250, -2277, -2303, -2330, -2357, -2384, -2411, -2437, -2464, -2491, -2518, -2545,
+ -2571, -2598, -2625, -2652, -2679, -2705, -2732, -2759, -2786, -2813, -2839, -2866,
+ -2893, -2920, -2947, -2973, -3000, -3027, -3054, -3081, -3107, -3134, -3161, -3188,
+ -3215, -3241, -3268, -3295, -3322, -3349, -3375, -3402, -3429, -3456, -3483, -3509,
+ -3536, -3563, -3590, -3616, -3643, -3670, -3697, -3724, -3750, -3777, -3804, -3831,
+ -3858, -3884, -3911, -3938, -3965, -3992, -4018, -4045, -4072, -4099, -4126, -4152,
+ -4179, -4206, -4233, -4260, -4286, -4313, -4340, -4367, -4394, -4420, -4447, -4474,
+ -4501, -4528, -4554, -4581, -4608, -4635, -4662, -4688, -4715, -4742, -4769, -4796,
+ -4822, -4849, -4876, -4903, -4929, -4956, -4983, -5010, -5037, -5063, -5090, -5117,
+ -5144, -5171, -5197, -5224, -5251, -5278, -5305, -5331, -5358, -5385, -5412, -5439,
+ -5465, -5492, -5519, -5546, -5573, -5599, -5626, -5653, -5680, -5707, -5733, -5760,
+ -5787, -5814, -5841, -5867, -5894, -5921, -5948, -5975, -6001, -6028, -6055, -6082,
+ -6109, -6135, -6162, -6189, -6216, -6243, -6269, -6296, -6323, -6350, -6376, -6403,
+ -6430, -6457, -6484, -6510, -6537, -6564, -6591, -6618, -6644, -6671, -6698, -6725,
+ -6752, -6778, -6805, -6832};
+
+static INT16 Cr_B[] = {
+ 0, -4, -9, -15, -20, -25, -30, -35, -41, -46, -51, -56,
+ -61, -67, -72, -77, -82, -87, -93, -98, -103, -108, -113, -119,
+ -124, -129, -134, -140, -145, -150, -155, -160, -166, -171, -176, -181,
+ -186, -192, -197, -202, -207, -212, -218, -223, -228, -233, -238, -244,
+ -249, -254, -259, -264, -270, -275, -280, -285, -290, -296, -301, -306,
+ -311, -316, -322, -327, -332, -337, -342, -348, -353, -358, -363, -368,
+ -374, -379, -384, -389, -394, -400, -405, -410, -415, -421, -426, -431,
+ -436, -441, -447, -452, -457, -462, -467, -473, -478, -483, -488, -493,
+ -499, -504, -509, -514, -519, -525, -530, -535, -540, -545, -551, -556,
+ -561, -566, -571, -577, -582, -587, -592, -597, -603, -608, -613, -618,
+ -623, -629, -634, -639, -644, -649, -655, -660, -665, -670, -675, -681,
+ -686, -691, -696, -702, -707, -712, -717, -722, -728, -733, -738, -743,
+ -748, -754, -759, -764, -769, -774, -780, -785, -790, -795, -800, -806,
+ -811, -816, -821, -826, -832, -837, -842, -847, -852, -858, -863, -868,
+ -873, -878, -884, -889, -894, -899, -904, -910, -915, -920, -925, -930,
+ -936, -941, -946, -951, -957, -962, -967, -972, -977, -983, -988, -993,
+ -998, -1003, -1009, -1014, -1019, -1024, -1029, -1035, -1040, -1045, -1050, -1055,
+ -1061, -1066, -1071, -1076, -1081, -1087, -1092, -1097, -1102, -1107, -1113, -1118,
+ -1123, -1128, -1133, -1139, -1144, -1149, -1154, -1159, -1165, -1170, -1175, -1180,
+ -1185, -1191, -1196, -1201, -1206, -1211, -1217, -1222, -1227, -1232, -1238, -1243,
+ -1248, -1253, -1258, -1264, -1269, -1274, -1279, -1284, -1290, -1295, -1300, -1305,
+ -1310, -1316, -1321, -1326};
+
+static INT16 R_Cr[] = {
+ -11484, -11394, -11305, -11215, -11125, -11036, -10946, -10856, -10766, -10677,
+ -10587, -10497, -10407, -10318, -10228, -10138, -10049, -9959, -9869, -9779,
+ -9690, -9600, -9510, -9420, -9331, -9241, -9151, -9062, -8972, -8882,
+ -8792, -8703, -8613, -8523, -8433, -8344, -8254, -8164, -8075, -7985,
+ -7895, -7805, -7716, -7626, -7536, -7446, -7357, -7267, -7177, -7088,
+ -6998, -6908, -6818, -6729, -6639, -6549, -6459, -6370, -6280, -6190,
+ -6101, -6011, -5921, -5831, -5742, -5652, -5562, -5472, -5383, -5293,
+ -5203, -5113, -5024, -4934, -4844, -4755, -4665, -4575, -4485, -4396,
+ -4306, -4216, -4126, -4037, -3947, -3857, -3768, -3678, -3588, -3498,
+ -3409, -3319, -3229, -3139, -3050, -2960, -2870, -2781, -2691, -2601,
+ -2511, -2422, -2332, -2242, -2152, -2063, -1973, -1883, -1794, -1704,
+ -1614, -1524, -1435, -1345, -1255, -1165, -1076, -986, -896, -807,
+ -717, -627, -537, -448, -358, -268, -178, -89, 0, 90,
+ 179, 269, 359, 449, 538, 628, 718, 808, 897, 987,
+ 1077, 1166, 1256, 1346, 1436, 1525, 1615, 1705, 1795, 1884,
+ 1974, 2064, 2153, 2243, 2333, 2423, 2512, 2602, 2692, 2782,
+ 2871, 2961, 3051, 3140, 3230, 3320, 3410, 3499, 3589, 3679,
+ 3769, 3858, 3948, 4038, 4127, 4217, 4307, 4397, 4486, 4576,
+ 4666, 4756, 4845, 4935, 5025, 5114, 5204, 5294, 5384, 5473,
+ 5563, 5653, 5743, 5832, 5922, 6012, 6102, 6191, 6281, 6371,
+ 6460, 6550, 6640, 6730, 6819, 6909, 6999, 7089, 7178, 7268,
+ 7358, 7447, 7537, 7627, 7717, 7806, 7896, 7986, 8076, 8165,
+ 8255, 8345, 8434, 8524, 8614, 8704, 8793, 8883, 8973, 9063,
+ 9152, 9242, 9332, 9421, 9511, 9601, 9691, 9780, 9870, 9960,
+ 10050, 10139, 10229, 10319, 10408, 10498, 10588, 10678, 10767, 10857,
+ 10947, 11037, 11126, 11216, 11306, 11395};
+
+static INT16 G_Cb[] = {
+ 2819, 2797, 2775, 2753, 2731, 2709, 2687, 2665, 2643, 2621, 2599, 2577,
+ 2555, 2533, 2511, 2489, 2467, 2445, 2423, 2401, 2379, 2357, 2335, 2313,
+ 2291, 2269, 2247, 2225, 2202, 2180, 2158, 2136, 2114, 2092, 2070, 2048,
+ 2026, 2004, 1982, 1960, 1938, 1916, 1894, 1872, 1850, 1828, 1806, 1784,
+ 1762, 1740, 1718, 1696, 1674, 1652, 1630, 1608, 1586, 1564, 1542, 1520,
+ 1498, 1476, 1454, 1432, 1410, 1388, 1366, 1344, 1321, 1299, 1277, 1255,
+ 1233, 1211, 1189, 1167, 1145, 1123, 1101, 1079, 1057, 1035, 1013, 991,
+ 969, 947, 925, 903, 881, 859, 837, 815, 793, 771, 749, 727,
+ 705, 683, 661, 639, 617, 595, 573, 551, 529, 507, 485, 463,
+ 440, 418, 396, 374, 352, 330, 308, 286, 264, 242, 220, 198,
+ 176, 154, 132, 110, 88, 66, 44, 22, 0, -21, -43, -65,
+ -87, -109, -131, -153, -175, -197, -219, -241, -263, -285, -307, -329,
+ -351, -373, -395, -417, -439, -462, -484, -506, -528, -550, -572, -594,
+ -616, -638, -660, -682, -704, -726, -748, -770, -792, -814, -836, -858,
+ -880, -902, -924, -946, -968, -990, -1012, -1034, -1056, -1078, -1100, -1122,
+ -1144, -1166, -1188, -1210, -1232, -1254, -1276, -1298, -1320, -1343, -1365, -1387,
+ -1409, -1431, -1453, -1475, -1497, -1519, -1541, -1563, -1585, -1607, -1629, -1651,
+ -1673, -1695, -1717, -1739, -1761, -1783, -1805, -1827, -1849, -1871, -1893, -1915,
+ -1937, -1959, -1981, -2003, -2025, -2047, -2069, -2091, -2113, -2135, -2157, -2179,
+ -2201, -2224, -2246, -2268, -2290, -2312, -2334, -2356, -2378, -2400, -2422, -2444,
+ -2466, -2488, -2510, -2532, -2554, -2576, -2598, -2620, -2642, -2664, -2686, -2708,
+ -2730, -2752, -2774, -2796};
+
+static INT16 G_Cr[] = {
+ 5850, 5805, 5759, 5713, 5667, 5622, 5576, 5530, 5485, 5439, 5393, 5347,
+ 5302, 5256, 5210, 5165, 5119, 5073, 5028, 4982, 4936, 4890, 4845, 4799,
+ 4753, 4708, 4662, 4616, 4570, 4525, 4479, 4433, 4388, 4342, 4296, 4251,
+ 4205, 4159, 4113, 4068, 4022, 3976, 3931, 3885, 3839, 3794, 3748, 3702,
+ 3656, 3611, 3565, 3519, 3474, 3428, 3382, 3336, 3291, 3245, 3199, 3154,
+ 3108, 3062, 3017, 2971, 2925, 2879, 2834, 2788, 2742, 2697, 2651, 2605,
+ 2559, 2514, 2468, 2422, 2377, 2331, 2285, 2240, 2194, 2148, 2102, 2057,
+ 2011, 1965, 1920, 1874, 1828, 1782, 1737, 1691, 1645, 1600, 1554, 1508,
+ 1463, 1417, 1371, 1325, 1280, 1234, 1188, 1143, 1097, 1051, 1006, 960,
+ 914, 868, 823, 777, 731, 686, 640, 594, 548, 503, 457, 411,
+ 366, 320, 274, 229, 183, 137, 91, 46, 0, -45, -90, -136,
+ -182, -228, -273, -319, -365, -410, -456, -502, -547, -593, -639, -685,
+ -730, -776, -822, -867, -913, -959, -1005, -1050, -1096, -1142, -1187, -1233,
+ -1279, -1324, -1370, -1416, -1462, -1507, -1553, -1599, -1644, -1690, -1736, -1781,
+ -1827, -1873, -1919, -1964, -2010, -2056, -2101, -2147, -2193, -2239, -2284, -2330,
+ -2376, -2421, -2467, -2513, -2558, -2604, -2650, -2696, -2741, -2787, -2833, -2878,
+ -2924, -2970, -3016, -3061, -3107, -3153, -3198, -3244, -3290, -3335, -3381, -3427,
+ -3473, -3518, -3564, -3610, -3655, -3701, -3747, -3793, -3838, -3884, -3930, -3975,
+ -4021, -4067, -4112, -4158, -4204, -4250, -4295, -4341, -4387, -4432, -4478, -4524,
+ -4569, -4615, -4661, -4707, -4752, -4798, -4844, -4889, -4935, -4981, -5027, -5072,
+ -5118, -5164, -5209, -5255, -5301, -5346, -5392, -5438, -5484, -5529, -5575, -5621,
+ -5666, -5712, -5758, -5804};
+
+static INT16 B_Cb[] = {
+ -14515, -14402, -14288, -14175, -14062, -13948, -13835, -13721, -13608, -13495,
+ -13381, -13268, -13154, -13041, -12928, -12814, -12701, -12587, -12474, -12360,
+ -12247, -12134, -12020, -11907, -11793, -11680, -11567, -11453, -11340, -11226,
+ -11113, -11000, -10886, -10773, -10659, -10546, -10433, -10319, -10206, -10092,
+ -9979, -9865, -9752, -9639, -9525, -9412, -9298, -9185, -9072, -8958,
+ -8845, -8731, -8618, -8505, -8391, -8278, -8164, -8051, -7938, -7824,
+ -7711, -7597, -7484, -7371, -7257, -7144, -7030, -6917, -6803, -6690,
+ -6577, -6463, -6350, -6236, -6123, -6010, -5896, -5783, -5669, -5556,
+ -5443, -5329, -5216, -5102, -4989, -4876, -4762, -4649, -4535, -4422,
+ -4309, -4195, -4082, -3968, -3855, -3741, -3628, -3515, -3401, -3288,
+ -3174, -3061, -2948, -2834, -2721, -2607, -2494, -2381, -2267, -2154,
+ -2040, -1927, -1814, -1700, -1587, -1473, -1360, -1246, -1133, -1020,
+ -906, -793, -679, -566, -453, -339, -226, -112, 0, 113,
+ 227, 340, 454, 567, 680, 794, 907, 1021, 1134, 1247,
+ 1361, 1474, 1588, 1701, 1815, 1928, 2041, 2155, 2268, 2382,
+ 2495, 2608, 2722, 2835, 2949, 3062, 3175, 3289, 3402, 3516,
+ 3629, 3742, 3856, 3969, 4083, 4196, 4310, 4423, 4536, 4650,
+ 4763, 4877, 4990, 5103, 5217, 5330, 5444, 5557, 5670, 5784,
+ 5897, 6011, 6124, 6237, 6351, 6464, 6578, 6691, 6804, 6918,
+ 7031, 7145, 7258, 7372, 7485, 7598, 7712, 7825, 7939, 8052,
+ 8165, 8279, 8392, 8506, 8619, 8732, 8846, 8959, 9073, 9186,
+ 9299, 9413, 9526, 9640, 9753, 9866, 9980, 10093, 10207, 10320,
+ 10434, 10547, 10660, 10774, 10887, 11001, 11114, 11227, 11341, 11454,
+ 11568, 11681, 11794, 11908, 12021, 12135, 12248, 12361, 12475, 12588,
+ 12702, 12815, 12929, 13042, 13155, 13269, 13382, 13496, 13609, 13722,
+ 13836, 13949, 14063, 14176, 14289, 14403};
+
+void
+ImagingConvertRGB2YCbCr(UINT8 *out, const UINT8 *in, int pixels) {
+ int x;
+ UINT8 a;
+ int r, g, b;
+ int y, cr, cb;
+
+ for (x = 0; x < pixels; x++, in += 4, out += 4) {
+ r = in[0];
+ g = in[1];
+ b = in[2];
+ a = in[3];
+
+ y = (Y_R[r] + Y_G[g] + Y_B[b]) >> SCALE;
+ cb = ((Cb_R[r] + Cb_G[g] + Cb_B[b]) >> SCALE) + 128;
+ cr = ((Cr_R[r] + Cr_G[g] + Cr_B[b]) >> SCALE) + 128;
+
+ out[0] = (UINT8)y;
+ out[1] = (UINT8)cb;
+ out[2] = (UINT8)cr;
+ out[3] = a;
+ }
+}
+
+void
+ImagingConvertYCbCr2RGB(UINT8 *out, const UINT8 *in, int pixels) {
+ int x;
+ UINT8 a;
+ int r, g, b;
+ int y, cr, cb;
+
+ for (x = 0; x < pixels; x++, in += 4, out += 4) {
+ y = in[0];
+ cb = in[1];
+ cr = in[2];
+ a = in[3];
+
+ r = y + ((R_Cr[cr]) >> SCALE);
+ g = y + ((G_Cb[cb] + G_Cr[cr]) >> SCALE);
+ b = y + ((B_Cb[cb]) >> SCALE);
+
+ out[0] = (r <= 0) ? 0 : (r >= 255) ? 255 : r;
+ out[1] = (g <= 0) ? 0 : (g >= 255) ? 255 : g;
+ out[2] = (b <= 0) ? 0 : (b >= 255) ? 255 : b;
+ out[3] = a;
+ }
+}
diff --git a/contrib/python/Pillow/py3/libImaging/Copy.c b/contrib/python/Pillow/py3/libImaging/Copy.c
new file mode 100644
index 00000000000..571133e14b6
--- /dev/null
+++ b/contrib/python/Pillow/py3/libImaging/Copy.c
@@ -0,0 +1,57 @@
+/*
+ * The Python Imaging Library
+ * $Id$
+ *
+ * copy image
+ *
+ * history:
+ * 95-11-26 fl Moved from Imaging.c
+ * 97-05-12 fl Added ImagingCopy2
+ * 97-08-28 fl Allow imOut == NULL in ImagingCopy2
+ *
+ * Copyright (c) Fredrik Lundh 1995-97.
+ * Copyright (c) Secret Labs AB 1997.
+ *
+ * See the README file for details on usage and redistribution.
+ */
+
+#include "Imaging.h"
+
+static Imaging
+_copy(Imaging imOut, Imaging imIn) {
+ ImagingSectionCookie cookie;
+ int y;
+
+ if (!imIn) {
+ return (Imaging)ImagingError_ValueError(NULL);
+ }
+
+ imOut = ImagingNew2Dirty(imIn->mode, imOut, imIn);
+ if (!imOut) {
+ return NULL;
+ }
+
+ ImagingCopyPalette(imOut, imIn);
+
+ ImagingSectionEnter(&cookie);
+ if (imIn->block != NULL && imOut->block != NULL) {
+ memcpy(imOut->block, imIn->block, imIn->ysize * imIn->linesize);
+ } else {
+ for (y = 0; y < imIn->ysize; y++) {
+ memcpy(imOut->image[y], imIn->image[y], imIn->linesize);
+ }
+ }
+ ImagingSectionLeave(&cookie);
+
+ return imOut;
+}
+
+Imaging
+ImagingCopy(Imaging imIn) {
+ return _copy(NULL, imIn);
+}
+
+Imaging
+ImagingCopy2(Imaging imOut, Imaging imIn) {
+ return _copy(imOut, imIn);
+}
diff --git a/contrib/python/Pillow/py3/libImaging/Crop.c b/contrib/python/Pillow/py3/libImaging/Crop.c
new file mode 100644
index 00000000000..2425b4cd589
--- /dev/null
+++ b/contrib/python/Pillow/py3/libImaging/Crop.c
@@ -0,0 +1,63 @@
+/*
+ * The Python Imaging Library
+ * $Id$
+ *
+ * cut region from image
+ *
+ * history:
+ * 95-11-27 fl Created
+ * 98-07-10 fl Fixed "null result" error
+ * 99-02-05 fl Rewritten to use Paste primitive
+ *
+ * Copyright (c) Secret Labs AB 1997-99.
+ * Copyright (c) Fredrik Lundh 1995.
+ *
+ * See the README file for information on usage and redistribution.
+ */
+
+#include "Imaging.h"
+
+Imaging
+ImagingCrop(Imaging imIn, int sx0, int sy0, int sx1, int sy1) {
+ Imaging imOut;
+ int xsize, ysize;
+ int dx0, dy0, dx1, dy1;
+ INT32 zero = 0;
+
+ if (!imIn) {
+ return (Imaging)ImagingError_ModeError();
+ }
+
+ xsize = sx1 - sx0;
+ if (xsize < 0) {
+ xsize = 0;
+ }
+ ysize = sy1 - sy0;
+ if (ysize < 0) {
+ ysize = 0;
+ }
+
+ imOut = ImagingNewDirty(imIn->mode, xsize, ysize);
+ if (!imOut) {
+ return NULL;
+ }
+
+ ImagingCopyPalette(imOut, imIn);
+
+ if (sx0 < 0 || sy0 < 0 || sx1 > imIn->xsize || sy1 > imIn->ysize) {
+ (void)ImagingFill(imOut, &zero);
+ }
+
+ dx0 = -sx0;
+ dy0 = -sy0;
+ dx1 = imIn->xsize - sx0;
+ dy1 = imIn->ysize - sy0;
+
+ /* paste the source image on top of the output image!!! */
+ if (ImagingPaste(imOut, imIn, NULL, dx0, dy0, dx1, dy1) < 0) {
+ ImagingDelete(imOut);
+ return NULL;
+ }
+
+ return imOut;
+}
diff --git a/contrib/python/Pillow/py3/libImaging/Dib.c b/contrib/python/Pillow/py3/libImaging/Dib.c
new file mode 100644
index 00000000000..f8a2901b8c7
--- /dev/null
+++ b/contrib/python/Pillow/py3/libImaging/Dib.c
@@ -0,0 +1,313 @@
+/*
+ * The Python Imaging Library
+ * $Id$
+ *
+ * imaging display object for Windows
+ *
+ * history:
+ * 1996-05-12 fl Created
+ * 1996-05-17 fl Up and running
+ * 1996-05-21 fl Added palette stuff
+ * 1996-05-26 fl Added query palette and mode inquery
+ * 1997-09-21 fl Added draw primitive
+ * 1998-01-20 fl Use StretchDIBits instead of StretchBlt
+ * 1998-12-30 fl Plugged a resource leak in DeleteDIB (from Roger Burnham)
+ *
+ * Copyright (c) Secret Labs AB 1997-2001.
+ * Copyright (c) Fredrik Lundh 1996.
+ *
+ * See the README file for information on usage and redistribution.
+ */
+
+#include "Imaging.h"
+
+#ifdef _WIN32
+
+#include "ImDib.h"
+
+char *
+ImagingGetModeDIB(int size_out[2]) {
+ /* Get device characteristics */
+
+ HDC dc;
+ char *mode;
+
+ dc = CreateCompatibleDC(NULL);
+
+ mode = "P";
+ if (!(GetDeviceCaps(dc, RASTERCAPS) & RC_PALETTE)) {
+ mode = "RGB";
+ if (GetDeviceCaps(dc, BITSPIXEL) == 1) {
+ mode = "1";
+ }
+ }
+
+ if (size_out) {
+ size_out[0] = GetDeviceCaps(dc, HORZRES);
+ size_out[1] = GetDeviceCaps(dc, VERTRES);
+ }
+
+ DeleteDC(dc);
+
+ return mode;
+}
+
+ImagingDIB
+ImagingNewDIB(const char *mode, int xsize, int ysize) {
+ /* Create a Windows bitmap */
+
+ ImagingDIB dib;
+ RGBQUAD *palette;
+ int i;
+
+ /* Check mode */
+ if (strcmp(mode, "1") != 0 && strcmp(mode, "L") != 0 && strcmp(mode, "RGB") != 0) {
+ return (ImagingDIB)ImagingError_ModeError();
+ }
+
+ /* Create DIB context and info header */
+ /* malloc check ok, small constant allocation */
+ dib = (ImagingDIB)malloc(sizeof(*dib));
+ if (!dib) {
+ return (ImagingDIB)ImagingError_MemoryError();
+ }
+ /* malloc check ok, small constant allocation */
+ dib->info = (BITMAPINFO *)malloc(sizeof(BITMAPINFOHEADER) + 256 * sizeof(RGBQUAD));
+ if (!dib->info) {
+ free(dib);
+ return (ImagingDIB)ImagingError_MemoryError();
+ }
+
+ memset(dib->info, 0, sizeof(BITMAPINFOHEADER));
+ dib->info->bmiHeader.biSize = sizeof(BITMAPINFOHEADER);
+ dib->info->bmiHeader.biWidth = xsize;
+ dib->info->bmiHeader.biHeight = ysize;
+ dib->info->bmiHeader.biPlanes = 1;
+ dib->info->bmiHeader.biBitCount = strlen(mode) * 8;
+ dib->info->bmiHeader.biCompression = BI_RGB;
+
+ /* Create DIB */
+ dib->dc = CreateCompatibleDC(NULL);
+ if (!dib->dc) {
+ free(dib->info);
+ free(dib);
+ return (ImagingDIB)ImagingError_MemoryError();
+ }
+
+ dib->bitmap =
+ CreateDIBSection(dib->dc, dib->info, DIB_RGB_COLORS, &dib->bits, NULL, 0);
+ if (!dib->bitmap) {
+ free(dib->info);
+ free(dib);
+ return (ImagingDIB)ImagingError_MemoryError();
+ }
+
+ strcpy(dib->mode, mode);
+ dib->xsize = xsize;
+ dib->ysize = ysize;
+
+ dib->pixelsize = strlen(mode);
+ dib->linesize = (xsize * dib->pixelsize + 3) & -4;
+
+ if (dib->pixelsize == 1) {
+ dib->pack = dib->unpack = (ImagingShuffler)memcpy;
+ } else {
+ dib->pack = ImagingPackBGR;
+ dib->unpack = ImagingPackBGR;
+ }
+
+ /* Bind the DIB to the device context */
+ dib->old_bitmap = SelectObject(dib->dc, dib->bitmap);
+
+ palette = dib->info->bmiColors;
+
+ /* Bind a palette to it as well (only required for 8-bit DIBs) */
+ if (dib->pixelsize == 1) {
+ for (i = 0; i < 256; i++) {
+ palette[i].rgbRed = palette[i].rgbGreen = palette[i].rgbBlue = i;
+ palette[i].rgbReserved = 0;
+ }
+ SetDIBColorTable(dib->dc, 0, 256, palette);
+ }
+
+ /* Create an associated palette (for 8-bit displays only) */
+ if (strcmp(ImagingGetModeDIB(NULL), "P") == 0) {
+ char palbuf[sizeof(LOGPALETTE) + 256 * sizeof(PALETTEENTRY)];
+ LPLOGPALETTE pal = (LPLOGPALETTE)palbuf;
+ int i, r, g, b;
+
+ /* Load system palette */
+ pal->palVersion = 0x300;
+ pal->palNumEntries = 256;
+ GetSystemPaletteEntries(dib->dc, 0, 256, pal->palPalEntry);
+
+ if (strcmp(mode, "L") == 0) {
+ /* Greyscale DIB. Fill all 236 slots with a greyscale ramp
+ * (this is usually overkill on Windows since VGA only offers
+ * 6 bits greyscale resolution). Ignore the slots already
+ * allocated by Windows */
+
+ i = 10;
+ for (r = 0; r < 236; r++) {
+ pal->palPalEntry[i].peRed = pal->palPalEntry[i].peGreen =
+ pal->palPalEntry[i].peBlue = i;
+ i++;
+ }
+
+ dib->palette = CreatePalette(pal);
+
+ } else if (strcmp(mode, "RGB") == 0) {
+#ifdef CUBE216
+
+ /* Colour DIB. Create a 6x6x6 colour cube (216 entries) and
+ * add 20 extra greylevels for best result with greyscale
+ * images. */
+
+ i = 10;
+ for (r = 0; r < 256; r += 51) {
+ for (g = 0; g < 256; g += 51) {
+ for (b = 0; b < 256; b += 51) {
+ pal->palPalEntry[i].peRed = r;
+ pal->palPalEntry[i].peGreen = g;
+ pal->palPalEntry[i].peBlue = b;
+ i++;
+ }
+ }
+ }
+ for (r = 1; r < 22 - 1; r++) {
+ /* Black and white are already provided by the cube. */
+ pal->palPalEntry[i].peRed = pal->palPalEntry[i].peGreen =
+ pal->palPalEntry[i].peBlue = r * 255 / (22 - 1);
+ i++;
+ }
+
+#else
+
+ /* Colour DIB. Alternate palette. */
+
+ i = 10;
+ for (r = 0; r < 256; r += 37) {
+ for (g = 0; g < 256; g += 32) {
+ for (b = 0; b < 256; b += 64) {
+ pal->palPalEntry[i].peRed = r;
+ pal->palPalEntry[i].peGreen = g;
+ pal->palPalEntry[i].peBlue = b;
+ i++;
+ }
+ }
+ }
+
+#endif
+
+ dib->palette = CreatePalette(pal);
+ }
+ }
+
+ return dib;
+}
+
+void
+ImagingPasteDIB(ImagingDIB dib, Imaging im, int xy[4]) {
+ /* Paste image data into a bitmap */
+
+ /* FIXME: check size! */
+
+ int y;
+ for (y = 0; y < im->ysize; y++) {
+ dib->pack(
+ dib->bits + dib->linesize * (dib->ysize - (xy[1] + y) - 1) +
+ xy[0] * dib->pixelsize,
+ im->image[y],
+ im->xsize);
+ }
+}
+
+void
+ImagingExposeDIB(ImagingDIB dib, void *dc) {
+ /* Copy bitmap to display */
+
+ if (dib->palette != 0) {
+ SelectPalette((HDC)dc, dib->palette, FALSE);
+ }
+ BitBlt((HDC)dc, 0, 0, dib->xsize, dib->ysize, dib->dc, 0, 0, SRCCOPY);
+}
+
+void
+ImagingDrawDIB(ImagingDIB dib, void *dc, int dst[4], int src[4]) {
+ /* Copy bitmap to printer/display */
+
+ if (GetDeviceCaps((HDC)dc, RASTERCAPS) & RC_STRETCHDIB) {
+ /* stretchdib (printers) */
+ StretchDIBits(
+ (HDC)dc,
+ dst[0],
+ dst[1],
+ dst[2] - dst[0],
+ dst[3] - dst[1],
+ src[0],
+ src[1],
+ src[2] - src[0],
+ src[3] - src[1],
+ dib->bits,
+ dib->info,
+ DIB_RGB_COLORS,
+ SRCCOPY);
+ } else {
+ /* stretchblt (displays) */
+ if (dib->palette != 0) {
+ SelectPalette((HDC)dc, dib->palette, FALSE);
+ }
+ StretchBlt(
+ (HDC)dc,
+ dst[0],
+ dst[1],
+ dst[2] - dst[0],
+ dst[3] - dst[1],
+ dib->dc,
+ src[0],
+ src[1],
+ src[2] - src[0],
+ src[3] - src[1],
+ SRCCOPY);
+ }
+}
+
+int
+ImagingQueryPaletteDIB(ImagingDIB dib, void *dc) {
+ /* Install bitmap palette */
+
+ int n;
+
+ if (dib->palette != 0) {
+ /* Realize associated palette */
+ HPALETTE now = SelectPalette((HDC)dc, dib->palette, FALSE);
+ n = RealizePalette((HDC)dc);
+
+ /* Restore palette */
+ SelectPalette((HDC)dc, now, FALSE);
+
+ } else {
+ n = 0;
+ }
+
+ return n; /* number of colours that was changed */
+}
+
+void
+ImagingDeleteDIB(ImagingDIB dib) {
+ /* Clean up */
+
+ if (dib->palette) {
+ DeleteObject(dib->palette);
+ }
+ if (dib->bitmap) {
+ SelectObject(dib->dc, dib->old_bitmap);
+ DeleteObject(dib->bitmap);
+ }
+ if (dib->dc) {
+ DeleteDC(dib->dc);
+ }
+ free(dib->info);
+}
+
+#endif /* _WIN32 */
diff --git a/contrib/python/Pillow/py3/libImaging/Draw.c b/contrib/python/Pillow/py3/libImaging/Draw.c
new file mode 100644
index 00000000000..0ccf22d58dd
--- /dev/null
+++ b/contrib/python/Pillow/py3/libImaging/Draw.c
@@ -0,0 +1,1948 @@
+/*
+ * The Python Imaging Library.
+ * $Id$
+ *
+ * a simple drawing package for the Imaging library
+ *
+ * history:
+ * 1996-04-13 fl Created.
+ * 1996-04-30 fl Added transforms and polygon support.
+ * 1996-08-12 fl Added filled polygons.
+ * 1996-11-05 fl Fixed float/int confusion in polygon filler
+ * 1997-07-04 fl Support 32-bit images (C++ would have been nice)
+ * 1998-09-09 fl Eliminated qsort casts; improved rectangle clipping
+ * 1998-09-10 fl Fixed fill rectangle to include lower edge (!)
+ * 1998-12-29 fl Added arc, chord, and pieslice primitives
+ * 1999-01-10 fl Added some level 2 ("arrow") stuff (experimental)
+ * 1999-02-06 fl Added bitmap primitive
+ * 1999-07-26 fl Eliminated a compiler warning
+ * 1999-07-31 fl Pass ink as void* instead of int
+ * 2002-12-10 fl Added experimental RGBA-on-RGB drawing
+ * 2004-09-04 fl Support simple wide lines (no joins)
+ * 2005-05-25 fl Fixed line width calculation
+ *
+ * Copyright (c) 1996-2006 by Fredrik Lundh
+ * Copyright (c) 1997-2006 by Secret Labs AB.
+ *
+ * See the README file for information on usage and redistribution.
+ */
+
+/* FIXME: support fill/outline attribute for all filled shapes */
+/* FIXME: support zero-winding fill */
+/* FIXME: add drawing context, support affine transforms */
+/* FIXME: support clip window (and mask?) */
+
+#include "Imaging.h"
+
+#include <math.h>
+#include <stdint.h>
+
+#define CEIL(v) (int)ceil(v)
+#define FLOOR(v) ((v) >= 0.0 ? (int)(v) : (int)floor(v))
+
+#define INK8(ink) (*(UINT8 *)ink)
+#define INK16(ink) (*(UINT16 *)ink)
+
+/*
+ * Rounds around zero (up=away from zero, down=towards zero)
+ * This guarantees that ROUND_UP|DOWN(f) == -ROUND_UP|DOWN(-f)
+ */
+#define ROUND_UP(f) ((int)((f) >= 0.0 ? floor((f) + 0.5F) : -floor(fabs(f) + 0.5F)))
+#define ROUND_DOWN(f) ((int)((f) >= 0.0 ? ceil((f)-0.5F) : -ceil(fabs(f) - 0.5F)))
+
+/* -------------------------------------------------------------------- */
+/* Primitives */
+/* -------------------------------------------------------------------- */
+
+typedef struct {
+ /* edge descriptor for polygon engine */
+ int d;
+ int x0, y0;
+ int xmin, ymin, xmax, ymax;
+ float dx;
+} Edge;
+
+/* Type used in "polygon*" functions */
+typedef void (*hline_handler)(Imaging, int, int, int, int);
+
+static inline void
+point8(Imaging im, int x, int y, int ink) {
+ if (x >= 0 && x < im->xsize && y >= 0 && y < im->ysize) {
+ if (strncmp(im->mode, "I;16", 4) == 0) {
+#ifdef WORDS_BIGENDIAN
+ im->image8[y][x * 2] = (UINT8)(ink >> 8);
+ im->image8[y][x * 2 + 1] = (UINT8)ink;
+#else
+ im->image8[y][x * 2] = (UINT8)ink;
+ im->image8[y][x * 2 + 1] = (UINT8)(ink >> 8);
+#endif
+ } else {
+ im->image8[y][x] = (UINT8)ink;
+ }
+ }
+}
+
+static inline void
+point32(Imaging im, int x, int y, int ink) {
+ if (x >= 0 && x < im->xsize && y >= 0 && y < im->ysize) {
+ im->image32[y][x] = ink;
+ }
+}
+
+static inline void
+point32rgba(Imaging im, int x, int y, int ink) {
+ unsigned int tmp;
+
+ if (x >= 0 && x < im->xsize && y >= 0 && y < im->ysize) {
+ UINT8 *out = (UINT8 *)im->image[y] + x * 4;
+ UINT8 *in = (UINT8 *)&ink;
+ out[0] = BLEND(in[3], out[0], in[0], tmp);
+ out[1] = BLEND(in[3], out[1], in[1], tmp);
+ out[2] = BLEND(in[3], out[2], in[2], tmp);
+ }
+}
+
+static inline void
+hline8(Imaging im, int x0, int y0, int x1, int ink) {
+ int pixelwidth;
+
+ if (y0 >= 0 && y0 < im->ysize) {
+ if (x0 < 0) {
+ x0 = 0;
+ } else if (x0 >= im->xsize) {
+ return;
+ }
+ if (x1 < 0) {
+ return;
+ } else if (x1 >= im->xsize) {
+ x1 = im->xsize - 1;
+ }
+ if (x0 <= x1) {
+ pixelwidth = strncmp(im->mode, "I;16", 4) == 0 ? 2 : 1;
+ memset(
+ im->image8[y0] + x0 * pixelwidth,
+ (UINT8)ink,
+ (x1 - x0 + 1) * pixelwidth);
+ }
+ }
+}
+
+static inline void
+hline32(Imaging im, int x0, int y0, int x1, int ink) {
+ INT32 *p;
+
+ if (y0 >= 0 && y0 < im->ysize) {
+ if (x0 < 0) {
+ x0 = 0;
+ } else if (x0 >= im->xsize) {
+ return;
+ }
+ if (x1 < 0) {
+ return;
+ } else if (x1 >= im->xsize) {
+ x1 = im->xsize - 1;
+ }
+ p = im->image32[y0];
+ while (x0 <= x1) {
+ p[x0++] = ink;
+ }
+ }
+}
+
+static inline void
+hline32rgba(Imaging im, int x0, int y0, int x1, int ink) {
+ unsigned int tmp;
+
+ if (y0 >= 0 && y0 < im->ysize) {
+ if (x0 < 0) {
+ x0 = 0;
+ } else if (x0 >= im->xsize) {
+ return;
+ }
+ if (x1 < 0) {
+ return;
+ } else if (x1 >= im->xsize) {
+ x1 = im->xsize - 1;
+ }
+ if (x0 <= x1) {
+ UINT8 *out = (UINT8 *)im->image[y0] + x0 * 4;
+ UINT8 *in = (UINT8 *)&ink;
+ while (x0 <= x1) {
+ out[0] = BLEND(in[3], out[0], in[0], tmp);
+ out[1] = BLEND(in[3], out[1], in[1], tmp);
+ out[2] = BLEND(in[3], out[2], in[2], tmp);
+ x0++;
+ out += 4;
+ }
+ }
+ }
+}
+
+static inline void
+line8(Imaging im, int x0, int y0, int x1, int y1, int ink) {
+ int i, n, e;
+ int dx, dy;
+ int xs, ys;
+
+ /* normalize coordinates */
+ dx = x1 - x0;
+ if (dx < 0) {
+ dx = -dx, xs = -1;
+ } else {
+ xs = 1;
+ }
+ dy = y1 - y0;
+ if (dy < 0) {
+ dy = -dy, ys = -1;
+ } else {
+ ys = 1;
+ }
+
+ n = (dx > dy) ? dx : dy;
+
+ if (dx == 0) {
+ /* vertical */
+ for (i = 0; i < dy; i++) {
+ point8(im, x0, y0, ink);
+ y0 += ys;
+ }
+
+ } else if (dy == 0) {
+ /* horizontal */
+ for (i = 0; i < dx; i++) {
+ point8(im, x0, y0, ink);
+ x0 += xs;
+ }
+
+ } else if (dx > dy) {
+ /* bresenham, horizontal slope */
+ n = dx;
+ dy += dy;
+ e = dy - dx;
+ dx += dx;
+
+ for (i = 0; i < n; i++) {
+ point8(im, x0, y0, ink);
+ if (e >= 0) {
+ y0 += ys;
+ e -= dx;
+ }
+ e += dy;
+ x0 += xs;
+ }
+
+ } else {
+ /* bresenham, vertical slope */
+ n = dy;
+ dx += dx;
+ e = dx - dy;
+ dy += dy;
+
+ for (i = 0; i < n; i++) {
+ point8(im, x0, y0, ink);
+ if (e >= 0) {
+ x0 += xs;
+ e -= dy;
+ }
+ e += dx;
+ y0 += ys;
+ }
+ }
+}
+
+static inline void
+line32(Imaging im, int x0, int y0, int x1, int y1, int ink) {
+ int i, n, e;
+ int dx, dy;
+ int xs, ys;
+
+ /* normalize coordinates */
+ dx = x1 - x0;
+ if (dx < 0) {
+ dx = -dx, xs = -1;
+ } else {
+ xs = 1;
+ }
+ dy = y1 - y0;
+ if (dy < 0) {
+ dy = -dy, ys = -1;
+ } else {
+ ys = 1;
+ }
+
+ n = (dx > dy) ? dx : dy;
+
+ if (dx == 0) {
+ /* vertical */
+ for (i = 0; i < dy; i++) {
+ point32(im, x0, y0, ink);
+ y0 += ys;
+ }
+
+ } else if (dy == 0) {
+ /* horizontal */
+ for (i = 0; i < dx; i++) {
+ point32(im, x0, y0, ink);
+ x0 += xs;
+ }
+
+ } else if (dx > dy) {
+ /* bresenham, horizontal slope */
+ n = dx;
+ dy += dy;
+ e = dy - dx;
+ dx += dx;
+
+ for (i = 0; i < n; i++) {
+ point32(im, x0, y0, ink);
+ if (e >= 0) {
+ y0 += ys;
+ e -= dx;
+ }
+ e += dy;
+ x0 += xs;
+ }
+
+ } else {
+ /* bresenham, vertical slope */
+ n = dy;
+ dx += dx;
+ e = dx - dy;
+ dy += dy;
+
+ for (i = 0; i < n; i++) {
+ point32(im, x0, y0, ink);
+ if (e >= 0) {
+ x0 += xs;
+ e -= dy;
+ }
+ e += dx;
+ y0 += ys;
+ }
+ }
+}
+
+static inline void
+line32rgba(Imaging im, int x0, int y0, int x1, int y1, int ink) {
+ int i, n, e;
+ int dx, dy;
+ int xs, ys;
+
+ /* normalize coordinates */
+ dx = x1 - x0;
+ if (dx < 0) {
+ dx = -dx, xs = -1;
+ } else {
+ xs = 1;
+ }
+ dy = y1 - y0;
+ if (dy < 0) {
+ dy = -dy, ys = -1;
+ } else {
+ ys = 1;
+ }
+
+ n = (dx > dy) ? dx : dy;
+
+ if (dx == 0) {
+ /* vertical */
+ for (i = 0; i < dy; i++) {
+ point32rgba(im, x0, y0, ink);
+ y0 += ys;
+ }
+
+ } else if (dy == 0) {
+ /* horizontal */
+ for (i = 0; i < dx; i++) {
+ point32rgba(im, x0, y0, ink);
+ x0 += xs;
+ }
+
+ } else if (dx > dy) {
+ /* bresenham, horizontal slope */
+ n = dx;
+ dy += dy;
+ e = dy - dx;
+ dx += dx;
+
+ for (i = 0; i < n; i++) {
+ point32rgba(im, x0, y0, ink);
+ if (e >= 0) {
+ y0 += ys;
+ e -= dx;
+ }
+ e += dy;
+ x0 += xs;
+ }
+
+ } else {
+ /* bresenham, vertical slope */
+ n = dy;
+ dx += dx;
+ e = dx - dy;
+ dy += dy;
+
+ for (i = 0; i < n; i++) {
+ point32rgba(im, x0, y0, ink);
+ if (e >= 0) {
+ x0 += xs;
+ e -= dy;
+ }
+ e += dx;
+ y0 += ys;
+ }
+ }
+}
+
+static int
+x_cmp(const void *x0, const void *x1) {
+ float diff = *((float *)x0) - *((float *)x1);
+ if (diff < 0) {
+ return -1;
+ } else if (diff > 0) {
+ return 1;
+ } else {
+ return 0;
+ }
+}
+
+static void
+draw_horizontal_lines(
+ Imaging im, int n, Edge *e, int ink, int *x_pos, int y, hline_handler hline) {
+ int i;
+ for (i = 0; i < n; i++) {
+ if (e[i].ymin == y && e[i].ymin == e[i].ymax) {
+ int xmax;
+ int xmin = e[i].xmin;
+ if (*x_pos != -1 && *x_pos < xmin) {
+ // Line would be after the current position
+ continue;
+ }
+
+ xmax = e[i].xmax;
+ if (*x_pos > xmin) {
+ // Line would be partway through x_pos, so increase the starting point
+ xmin = *x_pos;
+ if (xmax < xmin) {
+ // Line would now end before it started
+ continue;
+ }
+ }
+
+ (*hline)(im, xmin, e[i].ymin, xmax, ink);
+ *x_pos = xmax + 1;
+ }
+ }
+}
+
+/*
+ * Filled polygon draw function using scan line algorithm.
+ */
+static inline int
+polygon_generic(Imaging im, int n, Edge *e, int ink, int eofill, hline_handler hline, int hasAlpha) {
+ Edge **edge_table;
+ float *xx;
+ int edge_count = 0;
+ int ymin = im->ysize - 1;
+ int ymax = 0;
+ int i, j, k;
+ float adjacent_line_x, adjacent_line_x_other_edge;
+
+ if (n <= 0) {
+ return 0;
+ }
+
+ /* Initialize the edge table and find polygon boundaries */
+ /* malloc check ok, using calloc */
+ edge_table = calloc(n, sizeof(Edge *));
+ if (!edge_table) {
+ return -1;
+ }
+
+ for (i = 0; i < n; i++) {
+ if (ymin > e[i].ymin) {
+ ymin = e[i].ymin;
+ }
+ if (ymax < e[i].ymax) {
+ ymax = e[i].ymax;
+ }
+ if (e[i].ymin == e[i].ymax) {
+ if (hasAlpha != 1) {
+ (*hline)(im, e[i].xmin, e[i].ymin, e[i].xmax, ink);
+ }
+ continue;
+ }
+ edge_table[edge_count++] = (e + i);
+ }
+ if (ymin < 0) {
+ ymin = 0;
+ }
+ if (ymax > im->ysize) {
+ ymax = im->ysize;
+ }
+
+ /* Process the edge table with a scan line searching for intersections */
+ /* malloc check ok, using calloc */
+ xx = calloc(edge_count * 2, sizeof(float));
+ if (!xx) {
+ free(edge_table);
+ return -1;
+ }
+ for (; ymin <= ymax; ymin++) {
+ j = 0;
+ for (i = 0; i < edge_count; i++) {
+ Edge *current = edge_table[i];
+ if (ymin >= current->ymin && ymin <= current->ymax) {
+ xx[j++] = (ymin - current->y0) * current->dx + current->x0;
+
+ if (ymin == current->ymax && ymin < ymax) {
+ // Needed to draw consistent polygons
+ xx[j] = xx[j - 1];
+ j++;
+ } else if (current->dx != 0 && roundf(xx[j-1]) == xx[j-1]) {
+ // Connect discontiguous corners
+ for (k = 0; k < i; k++) {
+ Edge *other_edge = edge_table[k];
+ if ((current->dx > 0 && other_edge->dx <= 0) ||
+ (current->dx < 0 && other_edge->dx >= 0)) {
+ continue;
+ }
+ // Check if the two edges join to make a corner
+ if (((ymin == current->ymin && ymin == other_edge->ymin) ||
+ (ymin == current->ymax && ymin == other_edge->ymax)) &&
+ xx[j-1] == (ymin - other_edge->y0) * other_edge->dx + other_edge->x0) {
+ // Determine points from the edges on the next row
+ // Or if this is the last row, check the previous row
+ int offset = ymin == ymax ? -1 : 1;
+ adjacent_line_x = (ymin + offset - current->y0) * current->dx + current->x0;
+ adjacent_line_x_other_edge = (ymin + offset - other_edge->y0) * other_edge->dx + other_edge->x0;
+ if (ymin == current->ymax) {
+ if (current->dx > 0) {
+ xx[k] = fmax(adjacent_line_x, adjacent_line_x_other_edge) + 1;
+ } else {
+ xx[k] = fmin(adjacent_line_x, adjacent_line_x_other_edge) - 1;
+ }
+ } else {
+ if (current->dx > 0) {
+ xx[k] = fmin(adjacent_line_x, adjacent_line_x_other_edge);
+ } else {
+ xx[k] = fmax(adjacent_line_x, adjacent_line_x_other_edge) + 1;
+ }
+ }
+ break;
+ }
+ }
+ }
+ }
+ }
+ qsort(xx, j, sizeof(float), x_cmp);
+ if (hasAlpha == 1) {
+ int x_pos = j == 0 ? -1 : 0;
+ for (i = 1; i < j; i += 2) {
+ int x_end = ROUND_DOWN(xx[i]);
+ if (x_end < x_pos) {
+ // Line would be before the current position
+ continue;
+ }
+ draw_horizontal_lines(im, n, e, ink, &x_pos, ymin, hline);
+ if (x_end < x_pos) {
+ // Line would be before the current position
+ continue;
+ }
+
+ int x_start = ROUND_UP(xx[i - 1]);
+ if (x_pos > x_start) {
+ // Line would be partway through x_pos, so increase the starting point
+ x_start = x_pos;
+ if (x_end < x_start) {
+ // Line would now end before it started
+ continue;
+ }
+ }
+ (*hline)(im, x_start, ymin, x_end, ink);
+ x_pos = x_end + 1;
+ }
+ draw_horizontal_lines(im, n, e, ink, &x_pos, ymin, hline);
+ } else {
+ for (i = 1; i < j; i += 2) {
+ (*hline)(im, ROUND_UP(xx[i - 1]), ymin, ROUND_DOWN(xx[i]), ink);
+ }
+ }
+ }
+
+ free(xx);
+ free(edge_table);
+ return 0;
+}
+
+static inline int
+polygon8(Imaging im, int n, Edge *e, int ink, int eofill) {
+ return polygon_generic(im, n, e, ink, eofill, hline8, 0);
+}
+
+static inline int
+polygon32(Imaging im, int n, Edge *e, int ink, int eofill) {
+ return polygon_generic(im, n, e, ink, eofill, hline32, 0);
+}
+
+static inline int
+polygon32rgba(Imaging im, int n, Edge *e, int ink, int eofill) {
+ return polygon_generic(im, n, e, ink, eofill, hline32rgba, 1);
+}
+
+static inline void
+add_edge(Edge *e, int x0, int y0, int x1, int y1) {
+ /* printf("edge %d %d %d %d\n", x0, y0, x1, y1); */
+
+ if (x0 <= x1) {
+ e->xmin = x0, e->xmax = x1;
+ } else {
+ e->xmin = x1, e->xmax = x0;
+ }
+
+ if (y0 <= y1) {
+ e->ymin = y0, e->ymax = y1;
+ } else {
+ e->ymin = y1, e->ymax = y0;
+ }
+
+ if (y0 == y1) {
+ e->d = 0;
+ e->dx = 0.0;
+ } else {
+ e->dx = ((float)(x1 - x0)) / (y1 - y0);
+ if (y0 == e->ymin) {
+ e->d = 1;
+ } else {
+ e->d = -1;
+ }
+ }
+
+ e->x0 = x0;
+ e->y0 = y0;
+}
+
+typedef struct {
+ void (*point)(Imaging im, int x, int y, int ink);
+ void (*hline)(Imaging im, int x0, int y0, int x1, int ink);
+ void (*line)(Imaging im, int x0, int y0, int x1, int y1, int ink);
+ int (*polygon)(Imaging im, int n, Edge *e, int ink, int eofill);
+} DRAW;
+
+DRAW draw8 = {point8, hline8, line8, polygon8};
+DRAW draw32 = {point32, hline32, line32, polygon32};
+DRAW draw32rgba = {point32rgba, hline32rgba, line32rgba, polygon32rgba};
+
+/* -------------------------------------------------------------------- */
+/* Interface */
+/* -------------------------------------------------------------------- */
+
+#define DRAWINIT() \
+ if (im->image8) { \
+ draw = &draw8; \
+ if (strncmp(im->mode, "I;16", 4) == 0) { \
+ ink = INK16(ink_); \
+ } else { \
+ ink = INK8(ink_); \
+ } \
+ } else { \
+ draw = (op) ? &draw32rgba : &draw32; \
+ memcpy(&ink, ink_, sizeof(ink)); \
+ }
+
+int
+ImagingDrawPoint(Imaging im, int x0, int y0, const void *ink_, int op) {
+ DRAW *draw;
+ INT32 ink;
+
+ DRAWINIT();
+
+ draw->point(im, x0, y0, ink);
+
+ return 0;
+}
+
+int
+ImagingDrawLine(Imaging im, int x0, int y0, int x1, int y1, const void *ink_, int op) {
+ DRAW *draw;
+ INT32 ink;
+
+ DRAWINIT();
+
+ draw->line(im, x0, y0, x1, y1, ink);
+
+ return 0;
+}
+
+int
+ImagingDrawWideLine(
+ Imaging im, int x0, int y0, int x1, int y1, const void *ink_, int width, int op) {
+ DRAW *draw;
+ INT32 ink;
+ int dx, dy;
+ double big_hypotenuse, small_hypotenuse, ratio_max, ratio_min;
+ int dxmin, dxmax, dymin, dymax;
+ Edge e[4];
+
+ DRAWINIT();
+
+ dx = x1 - x0;
+ dy = y1 - y0;
+ if (dx == 0 && dy == 0) {
+ draw->point(im, x0, y0, ink);
+ return 0;
+ }
+
+ big_hypotenuse = hypot(dx, dy);
+ small_hypotenuse = (width - 1) / 2.0;
+ ratio_max = ROUND_UP(small_hypotenuse) / big_hypotenuse;
+ ratio_min = ROUND_DOWN(small_hypotenuse) / big_hypotenuse;
+
+ dxmin = ROUND_DOWN(ratio_min * dy);
+ dxmax = ROUND_DOWN(ratio_max * dy);
+ dymin = ROUND_DOWN(ratio_min * dx);
+ dymax = ROUND_DOWN(ratio_max * dx);
+ {
+ int vertices[4][2] = {
+ {x0 - dxmin, y0 + dymax},
+ {x1 - dxmin, y1 + dymax},
+ {x1 + dxmax, y1 - dymin},
+ {x0 + dxmax, y0 - dymin}};
+
+ add_edge(e + 0, vertices[0][0], vertices[0][1], vertices[1][0], vertices[1][1]);
+ add_edge(e + 1, vertices[1][0], vertices[1][1], vertices[2][0], vertices[2][1]);
+ add_edge(e + 2, vertices[2][0], vertices[2][1], vertices[3][0], vertices[3][1]);
+ add_edge(e + 3, vertices[3][0], vertices[3][1], vertices[0][0], vertices[0][1]);
+
+ draw->polygon(im, 4, e, ink, 0);
+ }
+ return 0;
+}
+
+int
+ImagingDrawRectangle(
+ Imaging im,
+ int x0,
+ int y0,
+ int x1,
+ int y1,
+ const void *ink_,
+ int fill,
+ int width,
+ int op) {
+ int i;
+ int y;
+ int tmp;
+ DRAW *draw;
+ INT32 ink;
+
+ DRAWINIT();
+
+ if (y0 > y1) {
+ tmp = y0, y0 = y1, y1 = tmp;
+ }
+
+ if (fill) {
+ if (y0 < 0) {
+ y0 = 0;
+ } else if (y0 >= im->ysize) {
+ return 0;
+ }
+
+ if (y1 < 0) {
+ return 0;
+ } else if (y1 > im->ysize) {
+ y1 = im->ysize;
+ }
+
+ for (y = y0; y <= y1; y++) {
+ draw->hline(im, x0, y, x1, ink);
+ }
+
+ } else {
+ /* outline */
+ if (width == 0) {
+ width = 1;
+ }
+ for (i = 0; i < width; i++) {
+ draw->hline(im, x0, y0 + i, x1, ink);
+ draw->hline(im, x0, y1 - i, x1, ink);
+ draw->line(im, x1 - i, y0 + width, x1 - i, y1 - width + 1, ink);
+ draw->line(im, x0 + i, y0 + width, x0 + i, y1 - width + 1, ink);
+ }
+ }
+
+ return 0;
+}
+
+int
+ImagingDrawPolygon(Imaging im, int count, int *xy, const void *ink_, int fill, int width, int op) {
+ int i, n, x0, y0, x1, y1;
+ DRAW *draw;
+ INT32 ink;
+
+ if (count <= 0) {
+ return 0;
+ }
+
+ DRAWINIT();
+
+ if (fill) {
+ /* Build edge list */
+ /* malloc check ok, using calloc */
+ Edge *e = calloc(count, sizeof(Edge));
+ if (!e) {
+ (void)ImagingError_MemoryError();
+ return -1;
+ }
+ for (i = n = 0; i < count - 1; i++) {
+ x0 = xy[i * 2];
+ y0 = xy[i * 2 + 1];
+ x1 = xy[i * 2 + 2];
+ y1 = xy[i * 2 + 3];
+ if (y0 == y1 && i != 0 && y0 == xy[i * 2 - 1]) {
+ // This is a horizontal line,
+ // that immediately follows another horizontal line
+ Edge *last_e = &e[n-1];
+ if (x1 > x0 && x0 > xy[i * 2 - 2]) {
+ // They are both increasing in x
+ last_e->xmax = x1;
+ continue;
+ } else if (x1 < x0 && x0 < xy[i * 2 - 2]) {
+ // They are both decreasing in x
+ last_e->xmin = x1;
+ continue;
+ }
+ }
+ add_edge(&e[n++], x0, y0, x1, y1);
+ }
+ if (xy[i * 2] != xy[0] || xy[i * 2 + 1] != xy[1]) {
+ add_edge(&e[n++], xy[i * 2], xy[i * 2 + 1], xy[0], xy[1]);
+ }
+ draw->polygon(im, n, e, ink, 0);
+ free(e);
+
+ } else {
+ /* Outline */
+ if (width == 1) {
+ for (i = 0; i < count - 1; i++) {
+ draw->line(im, xy[i * 2], xy[i * 2 + 1], xy[i * 2 + 2], xy[i * 2 + 3], ink);
+ }
+ draw->line(im, xy[i * 2], xy[i * 2 + 1], xy[0], xy[1], ink);
+ } else {
+ for (i = 0; i < count - 1; i++) {
+ ImagingDrawWideLine(im, xy[i * 2], xy[i * 2 + 1], xy[i * 2 + 2], xy[i * 2 + 3], ink_, width, op);
+ }
+ ImagingDrawWideLine(im, xy[i * 2], xy[i * 2 + 1], xy[0], xy[1], ink_, width, op);
+ }
+ }
+
+ return 0;
+}
+
+int
+ImagingDrawBitmap(Imaging im, int x0, int y0, Imaging bitmap, const void *ink, int op) {
+ return ImagingFill2(
+ im, ink, bitmap, x0, y0, x0 + bitmap->xsize, y0 + bitmap->ysize);
+}
+
+/* -------------------------------------------------------------------- */
+/* standard shapes */
+
+// Imagine 2D plane and ellipse with center in (0, 0) and semi-major axes a and b.
+// Then quarter_* stuff approximates its top right quarter (x, y >= 0) with integer
+// points from set {(2x+x0, 2y+y0) | x,y in Z} where x0, y0 are from {0, 1} and
+// are such that point (a, b) is in the set.
+
+typedef struct {
+ int32_t a, b, cx, cy, ex, ey;
+ int64_t a2, b2, a2b2;
+ int8_t finished;
+} quarter_state;
+
+void
+quarter_init(quarter_state *s, int32_t a, int32_t b) {
+ if (a < 0 || b < 0) {
+ s->finished = 1;
+ } else {
+ s->a = a;
+ s->b = b;
+ s->cx = a;
+ s->cy = b % 2;
+ s->ex = a % 2;
+ s->ey = b;
+ s->a2 = a * a;
+ s->b2 = b * b;
+ s->a2b2 = s->a2 * s->b2;
+ s->finished = 0;
+ }
+}
+
+// deviation of the point from ellipse curve, basically a substitution
+// of the point into the ellipse equation
+int64_t
+quarter_delta(quarter_state *s, int64_t x, int64_t y) {
+ return llabs(s->a2 * y * y + s->b2 * x * x - s->a2b2);
+}
+
+int8_t
+quarter_next(quarter_state *s, int32_t *ret_x, int32_t *ret_y) {
+ if (s->finished) {
+ return -1;
+ }
+ *ret_x = s->cx;
+ *ret_y = s->cy;
+ if (s->cx == s->ex && s->cy == s->ey) {
+ s->finished = 1;
+ } else {
+ // Bresenham's algorithm, possible optimization: only consider 2 of 3
+ // next points depending on current slope
+ int32_t nx = s->cx;
+ int32_t ny = s->cy + 2;
+ int64_t ndelta = quarter_delta(s, nx, ny);
+ if (nx > 1) {
+ int64_t newdelta = quarter_delta(s, s->cx - 2, s->cy + 2);
+ if (ndelta > newdelta) {
+ nx = s->cx - 2;
+ ny = s->cy + 2;
+ ndelta = newdelta;
+ }
+ newdelta = quarter_delta(s, s->cx - 2, s->cy);
+ if (ndelta > newdelta) {
+ nx = s->cx - 2;
+ ny = s->cy;
+ }
+ }
+ s->cx = nx;
+ s->cy = ny;
+ }
+ return 0;
+}
+
+// quarter_* stuff can "draw" a quarter of an ellipse with thickness 1, great.
+// Now we use ellipse_* stuff to join all four quarters of two different sized
+// ellipses and receive horizontal segments of a complete ellipse with
+// specified thickness.
+//
+// Still using integer grid with step 2 at this point (like in quarter_*)
+// to ease angle clipping in future.
+
+typedef struct {
+ quarter_state st_o, st_i;
+ int32_t py, pl, pr;
+ int32_t cy[4], cl[4], cr[4];
+ int8_t bufcnt;
+ int8_t finished;
+ int8_t leftmost;
+} ellipse_state;
+
+void
+ellipse_init(ellipse_state *s, int32_t a, int32_t b, int32_t w) {
+ s->bufcnt = 0;
+ s->leftmost = a % 2;
+ quarter_init(&s->st_o, a, b);
+ if (w < 1 || quarter_next(&s->st_o, &s->pr, &s->py) == -1) {
+ s->finished = 1;
+ } else {
+ s->finished = 0;
+ quarter_init(&s->st_i, a - 2 * (w - 1), b - 2 * (w - 1));
+ s->pl = s->leftmost;
+ }
+}
+
+int8_t
+ellipse_next(ellipse_state *s, int32_t *ret_x0, int32_t *ret_y, int32_t *ret_x1) {
+ if (s->bufcnt == 0) {
+ if (s->finished) {
+ return -1;
+ }
+ int32_t y = s->py;
+ int32_t l = s->pl;
+ int32_t r = s->pr;
+ int32_t cx = 0, cy = 0;
+ int8_t next_ret;
+ while ((next_ret = quarter_next(&s->st_o, &cx, &cy)) != -1 && cy <= y) {
+ }
+ if (next_ret == -1) {
+ s->finished = 1;
+ } else {
+ s->pr = cx;
+ s->py = cy;
+ }
+ while ((next_ret = quarter_next(&s->st_i, &cx, &cy)) != -1 && cy <= y) {
+ l = cx;
+ }
+ s->pl = next_ret == -1 ? s->leftmost : cx;
+
+ if ((l > 0 || l < r) && y > 0) {
+ s->cl[s->bufcnt] = l == 0 ? 2 : l;
+ s->cy[s->bufcnt] = y;
+ s->cr[s->bufcnt] = r;
+ ++s->bufcnt;
+ }
+ if (y > 0) {
+ s->cl[s->bufcnt] = -r;
+ s->cy[s->bufcnt] = y;
+ s->cr[s->bufcnt] = -l;
+ ++s->bufcnt;
+ }
+ if (l > 0 || l < r) {
+ s->cl[s->bufcnt] = l == 0 ? 2 : l;
+ s->cy[s->bufcnt] = -y;
+ s->cr[s->bufcnt] = r;
+ ++s->bufcnt;
+ }
+ s->cl[s->bufcnt] = -r;
+ s->cy[s->bufcnt] = -y;
+ s->cr[s->bufcnt] = -l;
+ ++s->bufcnt;
+ }
+ --s->bufcnt;
+ *ret_x0 = s->cl[s->bufcnt];
+ *ret_y = s->cy[s->bufcnt];
+ *ret_x1 = s->cr[s->bufcnt];
+ return 0;
+}
+
+// Clipping tree consists of half-plane clipping nodes and combining nodes.
+// We can throw a horizontal segment in such a tree and collect an ordered set
+// of resulting disjoint clipped segments organized into a sorted linked list
+// of their end points.
+typedef enum {
+ CT_AND, // intersection
+ CT_OR, // union
+ CT_CLIP // half-plane clipping
+} clip_type;
+
+typedef struct clip_node {
+ clip_type type;
+ double a, b, c; // half-plane coeffs, only used in clipping nodes
+ struct clip_node *l; // child pointers, are only non-NULL in combining nodes
+ struct clip_node *r;
+} clip_node;
+
+// Linked list for the ends of the clipped horizontal segments.
+// Since the segment is always horizontal, we don't need to store Y coordinate.
+typedef struct event_list {
+ int32_t x;
+ int8_t type; // used internally, 1 for the left end (smaller X), -1 for the
+ // right end; pointless in output since the output segments
+ // are disjoint, therefore the types would always come in pairs
+ // and interchange (1 -1 1 -1 ...)
+ struct event_list *next;
+} event_list;
+
+// Mirrors all the clipping nodes of the tree relative to the y = x line.
+void
+clip_tree_transpose(clip_node *root) {
+ if (root != NULL) {
+ if (root->type == CT_CLIP) {
+ double t = root->a;
+ root->a = root->b;
+ root->b = t;
+ }
+ clip_tree_transpose(root->l);
+ clip_tree_transpose(root->r);
+ }
+}
+
+// Outputs a sequence of open-close events (types -1 and 1) for
+// non-intersecting segments sorted by X coordinate.
+// Combining nodes (AND, OR) may also accept sequences for intersecting
+// segments, i.e. something like correct bracket sequences.
+int
+clip_tree_do_clip(
+ clip_node *root, int32_t x0, int32_t y, int32_t x1, event_list **ret) {
+ if (root == NULL) {
+ event_list *start = malloc(sizeof(event_list));
+ if (!start) {
+ ImagingError_MemoryError();
+ return -1;
+ }
+ event_list *end = malloc(sizeof(event_list));
+ if (!end) {
+ free(start);
+ ImagingError_MemoryError();
+ return -1;
+ }
+ start->x = x0;
+ start->type = 1;
+ start->next = end;
+ end->x = x1;
+ end->type = -1;
+ end->next = NULL;
+ *ret = start;
+ return 0;
+ }
+ if (root->type == CT_CLIP) {
+ double eps = 1e-9;
+ double A = root->a;
+ double B = root->b;
+ double C = root->c;
+ if (fabs(A) < eps) {
+ if (B * y + C < -eps) {
+ x0 = 1;
+ x1 = 0;
+ }
+ } else {
+ // X of intersection
+ double ix = -(B * y + C) / A;
+ if (A * x0 + B * y + C < eps) {
+ x0 = lround(fmax(x0, ix));
+ }
+ if (A * x1 + B * y + C < eps) {
+ x1 = lround(fmin(x1, ix));
+ }
+ }
+ if (x0 <= x1) {
+ event_list *start = malloc(sizeof(event_list));
+ if (!start) {
+ ImagingError_MemoryError();
+ return -1;
+ }
+ event_list *end = malloc(sizeof(event_list));
+ if (!end) {
+ free(start);
+ ImagingError_MemoryError();
+ return -1;
+ }
+ start->x = x0;
+ start->type = 1;
+ start->next = end;
+ end->x = x1;
+ end->type = -1;
+ end->next = NULL;
+ *ret = start;
+ } else {
+ *ret = NULL;
+ }
+ return 0;
+ }
+ if (root->type == CT_OR || root->type == CT_AND) {
+ event_list *l1;
+ event_list *l2;
+ if (clip_tree_do_clip(root->l, x0, y, x1, &l1) < 0) {
+ return -1;
+ }
+ if (clip_tree_do_clip(root->r, x0, y, x1, &l2) < 0) {
+ while (l1) {
+ l2 = l1->next;
+ free(l1);
+ l1 = l2;
+ }
+ return -1;
+ }
+ *ret = NULL;
+ event_list *tail = NULL;
+ int32_t k1 = 0;
+ int32_t k2 = 0;
+ while (l1 != NULL || l2 != NULL) {
+ event_list *t;
+ if (l2 == NULL ||
+ (l1 != NULL &&
+ (l1->x < l2->x || (l1->x == l2->x && l1->type > l2->type)))) {
+ t = l1;
+ k1 += t->type;
+ assert(k1 >= 0);
+ l1 = l1->next;
+ } else {
+ t = l2;
+ k2 += t->type;
+ assert(k2 >= 0);
+ l2 = l2->next;
+ }
+ t->next = NULL;
+ if ((root->type == CT_OR &&
+ ((t->type == 1 && (tail == NULL || tail->type == -1)) ||
+ (t->type == -1 && k1 == 0 && k2 == 0))) ||
+ (root->type == CT_AND &&
+ ((t->type == 1 && (tail == NULL || tail->type == -1) && k1 > 0 &&
+ k2 > 0) ||
+ (t->type == -1 && tail != NULL && tail->type == 1 &&
+ (k1 == 0 || k2 == 0))))) {
+ if (tail == NULL) {
+ *ret = t;
+ } else {
+ tail->next = t;
+ }
+ tail = t;
+ } else {
+ free(t);
+ }
+ }
+ return 0;
+ }
+ *ret = NULL;
+ return 0;
+}
+
+// One more layer of processing on top of the regular ellipse.
+// Uses the clipping tree.
+// Used for producing ellipse derivatives such as arc, chord, pie, etc.
+typedef struct {
+ ellipse_state st;
+ clip_node *root;
+ clip_node nodes[7];
+ int32_t node_count;
+ event_list *head;
+ int32_t y;
+} clip_ellipse_state;
+
+typedef void (*clip_ellipse_init)(
+ clip_ellipse_state *, int32_t, int32_t, int32_t, float, float);
+
+void
+debug_clip_tree(clip_node *root, int space) {
+ if (root == NULL) {
+ return;
+ }
+ if (root->type == CT_CLIP) {
+ int t = space;
+ while (t--) {
+ fputc(' ', stderr);
+ }
+ fprintf(stderr, "clip %+fx%+fy%+f > 0\n", root->a, root->b, root->c);
+ } else {
+ debug_clip_tree(root->l, space + 2);
+ int t = space;
+ while (t--) {
+ fputc(' ', stderr);
+ }
+ fprintf(stderr, "%s\n", root->type == CT_AND ? "and" : "or");
+ debug_clip_tree(root->r, space + 2);
+ }
+ if (space == 0) {
+ fputc('\n', stderr);
+ }
+}
+
+// Resulting angles will satisfy 0 <= al < 360, al <= ar <= al + 360
+void
+normalize_angles(float *al, float *ar) {
+ if (*ar - *al >= 360) {
+ *al = 0;
+ *ar = 360;
+ } else {
+ *al = fmod(*al < 0 ? 360 - (fmod(-*al, 360)) : *al, 360);
+ *ar = *al + fmod(*ar < *al ? 360 - fmod(*al - *ar, 360) : *ar - *al, 360);
+ }
+}
+
+// An arc with caps orthogonal to the ellipse curve.
+void
+arc_init(clip_ellipse_state *s, int32_t a, int32_t b, int32_t w, float al, float ar) {
+ if (a < b) {
+ // transpose the coordinate system
+ arc_init(s, b, a, w, 90 - ar, 90 - al);
+ ellipse_init(&s->st, a, b, w);
+ clip_tree_transpose(s->root);
+ } else {
+ // a >= b, based on "wide" ellipse
+ ellipse_init(&s->st, a, b, w);
+
+ s->head = NULL;
+ s->node_count = 0;
+ normalize_angles(&al, &ar);
+
+ // building clipping tree, a lot of different cases
+ if (ar == al + 360) {
+ s->root = NULL;
+ } else {
+ clip_node *lc = s->nodes + s->node_count++;
+ clip_node *rc = s->nodes + s->node_count++;
+ lc->l = lc->r = rc->l = rc->r = NULL;
+ lc->type = rc->type = CT_CLIP;
+ lc->a = -a * sin(al * M_PI / 180.0);
+ lc->b = b * cos(al * M_PI / 180.0);
+ lc->c = (a * a - b * b) * sin(al * M_PI / 90.0) / 2.0;
+ rc->a = a * sin(ar * M_PI / 180.0);
+ rc->b = -b * cos(ar * M_PI / 180.0);
+ rc->c = (b * b - a * a) * sin(ar * M_PI / 90.0) / 2.0;
+ if (fmod(al, 180) == 0 || fmod(ar, 180) == 0) {
+ s->root = s->nodes + s->node_count++;
+ s->root->l = lc;
+ s->root->r = rc;
+ s->root->type = ar - al < 180 ? CT_AND : CT_OR;
+ } else if (((int)(al / 180) + (int)(ar / 180)) % 2 == 1) {
+ s->root = s->nodes + s->node_count++;
+ s->root->l = s->nodes + s->node_count++;
+ s->root->l->l = s->nodes + s->node_count++;
+ s->root->l->r = lc;
+ s->root->r = s->nodes + s->node_count++;
+ s->root->r->l = s->nodes + s->node_count++;
+ s->root->r->r = rc;
+ s->root->type = CT_OR;
+ s->root->l->type = CT_AND;
+ s->root->r->type = CT_AND;
+ s->root->l->l->type = CT_CLIP;
+ s->root->r->l->type = CT_CLIP;
+ s->root->l->l->l = s->root->l->l->r = NULL;
+ s->root->r->l->l = s->root->r->l->r = NULL;
+ s->root->l->l->a = s->root->l->l->c = 0;
+ s->root->r->l->a = s->root->r->l->c = 0;
+ s->root->l->l->b = (int)(al / 180) % 2 == 0 ? 1 : -1;
+ s->root->r->l->b = (int)(ar / 180) % 2 == 0 ? 1 : -1;
+ } else {
+ s->root = s->nodes + s->node_count++;
+ s->root->l = s->nodes + s->node_count++;
+ s->root->r = s->nodes + s->node_count++;
+ s->root->type = s->root->l->type = ar - al < 180 ? CT_AND : CT_OR;
+ s->root->l->l = lc;
+ s->root->l->r = rc;
+ s->root->r->type = CT_CLIP;
+ s->root->r->l = s->root->r->r = NULL;
+ s->root->r->a = s->root->r->c = 0;
+ s->root->r->b = ar < 180 || ar > 540 ? 1 : -1;
+ }
+ }
+ }
+}
+
+// A chord line.
+void
+chord_line_init(
+ clip_ellipse_state *s, int32_t a, int32_t b, int32_t w, float al, float ar) {
+ ellipse_init(&s->st, a, b, a + b + 1);
+
+ s->head = NULL;
+ s->node_count = 0;
+
+ // line equation for chord
+ double xl = a * cos(al * M_PI / 180.0), xr = a * cos(ar * M_PI / 180.0);
+ double yl = b * sin(al * M_PI / 180.0), yr = b * sin(ar * M_PI / 180.0);
+ s->root = s->nodes + s->node_count++;
+ s->root->l = s->nodes + s->node_count++;
+ s->root->r = s->nodes + s->node_count++;
+ s->root->type = CT_AND;
+ s->root->l->type = s->root->r->type = CT_CLIP;
+ s->root->l->l = s->root->l->r = s->root->r->l = s->root->r->r = NULL;
+ s->root->l->a = yr - yl;
+ s->root->l->b = xl - xr;
+ s->root->l->c = -(s->root->l->a * xl + s->root->l->b * yl);
+ s->root->r->a = -s->root->l->a;
+ s->root->r->b = -s->root->l->b;
+ s->root->r->c =
+ 2 * w * sqrt(pow(s->root->l->a, 2.0) + pow(s->root->l->b, 2.0)) - s->root->l->c;
+}
+
+// Pie side.
+void
+pie_side_init(
+ clip_ellipse_state *s, int32_t a, int32_t b, int32_t w, float al, float _) {
+ ellipse_init(&s->st, a, b, a + b + 1);
+
+ s->head = NULL;
+ s->node_count = 0;
+
+ double xl = a * cos(al * M_PI / 180.0);
+ double yl = b * sin(al * M_PI / 180.0);
+ double a1 = -yl;
+ double b1 = xl;
+ double c1 = w * sqrt(a1 * a1 + b1 * b1);
+
+ s->root = s->nodes + s->node_count++;
+ s->root->type = CT_AND;
+ s->root->l = s->nodes + s->node_count++;
+ s->root->l->type = CT_AND;
+
+ clip_node *cnode;
+ cnode = s->nodes + s->node_count++;
+ cnode->l = cnode->r = NULL;
+ cnode->type = CT_CLIP;
+ cnode->a = a1;
+ cnode->b = b1;
+ cnode->c = c1;
+ s->root->l->l = cnode;
+ cnode = s->nodes + s->node_count++;
+ cnode->l = cnode->r = NULL;
+ cnode->type = CT_CLIP;
+ cnode->a = -a1;
+ cnode->b = -b1;
+ cnode->c = c1;
+ s->root->l->r = cnode;
+ cnode = s->nodes + s->node_count++;
+ cnode->l = cnode->r = NULL;
+ cnode->type = CT_CLIP;
+ cnode->a = b1;
+ cnode->b = -a1;
+ cnode->c = 0;
+ s->root->r = cnode;
+}
+
+// A chord.
+void
+chord_init(clip_ellipse_state *s, int32_t a, int32_t b, int32_t w, float al, float ar) {
+ ellipse_init(&s->st, a, b, w);
+
+ s->head = NULL;
+ s->node_count = 0;
+
+ // line equation for chord
+ double xl = a * cos(al * M_PI / 180.0), xr = a * cos(ar * M_PI / 180.0);
+ double yl = b * sin(al * M_PI / 180.0), yr = b * sin(ar * M_PI / 180.0);
+ s->root = s->nodes + s->node_count++;
+ s->root->l = s->root->r = NULL;
+ s->root->type = CT_CLIP;
+ s->root->a = yr - yl;
+ s->root->b = xl - xr;
+ s->root->c = -(s->root->a * xl + s->root->b * yl);
+}
+
+// A pie. Can also be used to draw an arc with ugly sharp caps.
+void
+pie_init(clip_ellipse_state *s, int32_t a, int32_t b, int32_t w, float al, float ar) {
+ ellipse_init(&s->st, a, b, w);
+
+ s->head = NULL;
+ s->node_count = 0;
+
+ // line equations for pie sides
+ double xl = a * cos(al * M_PI / 180.0), xr = a * cos(ar * M_PI / 180.0);
+ double yl = b * sin(al * M_PI / 180.0), yr = b * sin(ar * M_PI / 180.0);
+
+ clip_node *lc = s->nodes + s->node_count++;
+ clip_node *rc = s->nodes + s->node_count++;
+ lc->l = lc->r = rc->l = rc->r = NULL;
+ lc->type = rc->type = CT_CLIP;
+ lc->a = -yl;
+ lc->b = xl;
+ lc->c = 0;
+ rc->a = yr;
+ rc->b = -xr;
+ rc->c = 0;
+
+ s->root = s->nodes + s->node_count++;
+ s->root->l = lc;
+ s->root->r = rc;
+ s->root->type = ar - al < 180 ? CT_AND : CT_OR;
+
+ // add one more semiplane to avoid spikes
+ if (ar - al < 90) {
+ clip_node *old_root = s->root;
+ clip_node *spike_clipper = s->nodes + s->node_count++;
+ s->root = s->nodes + s->node_count++;
+ s->root->l = old_root;
+ s->root->r = spike_clipper;
+ s->root->type = CT_AND;
+
+ spike_clipper->l = spike_clipper->r = NULL;
+ spike_clipper->type = CT_CLIP;
+ spike_clipper->a = (xl + xr) / 2.0;
+ spike_clipper->b = (yl + yr) / 2.0;
+ spike_clipper->c = 0;
+ }
+}
+
+void
+clip_ellipse_free(clip_ellipse_state *s) {
+ while (s->head != NULL) {
+ event_list *t = s->head;
+ s->head = s->head->next;
+ free(t);
+ }
+}
+
+int8_t
+clip_ellipse_next(
+ clip_ellipse_state *s, int32_t *ret_x0, int32_t *ret_y, int32_t *ret_x1) {
+ int32_t x0, y, x1;
+ while (s->head == NULL && ellipse_next(&s->st, &x0, &y, &x1) >= 0) {
+ if (clip_tree_do_clip(s->root, x0, y, x1, &s->head) < 0) {
+ return -2;
+ }
+ s->y = y;
+ }
+ if (s->head != NULL) {
+ *ret_y = s->y;
+ event_list *t = s->head;
+ s->head = s->head->next;
+ *ret_x0 = t->x;
+ free(t);
+ t = s->head;
+ assert(t != NULL);
+ s->head = s->head->next;
+ *ret_x1 = t->x;
+ free(t);
+ return 0;
+ }
+ return -1;
+}
+
+static int
+ellipseNew(
+ Imaging im,
+ int x0,
+ int y0,
+ int x1,
+ int y1,
+ const void *ink_,
+ int fill,
+ int width,
+ int op) {
+ DRAW *draw;
+ INT32 ink;
+ DRAWINIT();
+
+ int a = x1 - x0;
+ int b = y1 - y0;
+ if (a < 0 || b < 0) {
+ return 0;
+ }
+ if (fill) {
+ width = a + b;
+ }
+
+ ellipse_state st;
+ ellipse_init(&st, a, b, width);
+ int32_t X0, Y, X1;
+ while (ellipse_next(&st, &X0, &Y, &X1) != -1) {
+ draw->hline(im, x0 + (X0 + a) / 2, y0 + (Y + b) / 2, x0 + (X1 + a) / 2, ink);
+ }
+ return 0;
+}
+
+static int
+clipEllipseNew(
+ Imaging im,
+ int x0,
+ int y0,
+ int x1,
+ int y1,
+ float start,
+ float end,
+ const void *ink_,
+ int width,
+ int op,
+ clip_ellipse_init init) {
+ DRAW *draw;
+ INT32 ink;
+ DRAWINIT();
+
+ int a = x1 - x0;
+ int b = y1 - y0;
+ if (a < 0 || b < 0) {
+ return 0;
+ }
+
+ clip_ellipse_state st;
+ init(&st, a, b, width, start, end);
+ // debug_clip_tree(st.root, 0);
+ int32_t X0, Y, X1;
+ int next_code;
+ while ((next_code = clip_ellipse_next(&st, &X0, &Y, &X1)) >= 0) {
+ draw->hline(im, x0 + (X0 + a) / 2, y0 + (Y + b) / 2, x0 + (X1 + a) / 2, ink);
+ }
+ clip_ellipse_free(&st);
+ return next_code == -1 ? 0 : -1;
+}
+static int
+arcNew(
+ Imaging im,
+ int x0,
+ int y0,
+ int x1,
+ int y1,
+ float start,
+ float end,
+ const void *ink_,
+ int width,
+ int op) {
+ return clipEllipseNew(im, x0, y0, x1, y1, start, end, ink_, width, op, arc_init);
+}
+
+static int
+chordNew(
+ Imaging im,
+ int x0,
+ int y0,
+ int x1,
+ int y1,
+ float start,
+ float end,
+ const void *ink_,
+ int width,
+ int op) {
+ return clipEllipseNew(im, x0, y0, x1, y1, start, end, ink_, width, op, chord_init);
+}
+
+static int
+chordLineNew(
+ Imaging im,
+ int x0,
+ int y0,
+ int x1,
+ int y1,
+ float start,
+ float end,
+ const void *ink_,
+ int width,
+ int op) {
+ return clipEllipseNew(
+ im, x0, y0, x1, y1, start, end, ink_, width, op, chord_line_init);
+}
+
+static int
+pieNew(
+ Imaging im,
+ int x0,
+ int y0,
+ int x1,
+ int y1,
+ float start,
+ float end,
+ const void *ink_,
+ int width,
+ int op) {
+ return clipEllipseNew(im, x0, y0, x1, y1, start, end, ink_, width, op, pie_init);
+}
+
+static int
+pieSideNew(
+ Imaging im,
+ int x0,
+ int y0,
+ int x1,
+ int y1,
+ float start,
+ const void *ink_,
+ int width,
+ int op) {
+ return clipEllipseNew(im, x0, y0, x1, y1, start, 0, ink_, width, op, pie_side_init);
+}
+
+int
+ImagingDrawEllipse(
+ Imaging im,
+ int x0,
+ int y0,
+ int x1,
+ int y1,
+ const void *ink,
+ int fill,
+ int width,
+ int op) {
+ return ellipseNew(im, x0, y0, x1, y1, ink, fill, width, op);
+}
+
+int
+ImagingDrawArc(
+ Imaging im,
+ int x0,
+ int y0,
+ int x1,
+ int y1,
+ float start,
+ float end,
+ const void *ink,
+ int width,
+ int op) {
+ normalize_angles(&start, &end);
+ if (start + 360 == end) {
+ return ImagingDrawEllipse(im, x0, y0, x1, y1, ink, 0, width, op);
+ }
+ if (start == end) {
+ return 0;
+ }
+ return arcNew(im, x0, y0, x1, y1, start, end, ink, width, op);
+}
+
+int
+ImagingDrawChord(
+ Imaging im,
+ int x0,
+ int y0,
+ int x1,
+ int y1,
+ float start,
+ float end,
+ const void *ink,
+ int fill,
+ int width,
+ int op) {
+ normalize_angles(&start, &end);
+ if (start + 360 == end) {
+ return ImagingDrawEllipse(im, x0, y0, x1, y1, ink, fill, width, op);
+ }
+ if (start == end) {
+ return 0;
+ }
+ if (fill) {
+ return chordNew(im, x0, y0, x1, y1, start, end, ink, x1 - x0 + y1 - y0 + 1, op);
+ } else {
+ if (chordLineNew(im, x0, y0, x1, y1, start, end, ink, width, op)) {
+ return -1;
+ }
+ return chordNew(im, x0, y0, x1, y1, start, end, ink, width, op);
+ }
+}
+
+int
+ImagingDrawPieslice(
+ Imaging im,
+ int x0,
+ int y0,
+ int x1,
+ int y1,
+ float start,
+ float end,
+ const void *ink,
+ int fill,
+ int width,
+ int op) {
+ normalize_angles(&start, &end);
+ if (start + 360 == end) {
+ return ellipseNew(im, x0, y0, x1, y1, ink, fill, width, op);
+ }
+ if (start == end) {
+ return 0;
+ }
+ if (fill) {
+ return pieNew(im, x0, y0, x1, y1, start, end, ink, x1 + y1 - x0 - y0, op);
+ } else {
+ if (pieSideNew(im, x0, y0, x1, y1, start, ink, width, op)) {
+ return -1;
+ }
+ if (pieSideNew(im, x0, y0, x1, y1, end, ink, width, op)) {
+ return -1;
+ }
+ int xc = lround((x0 + x1 - width) / 2.0), yc = lround((y0 + y1 - width) / 2.0);
+ ellipseNew(im, xc, yc, xc + width - 1, yc + width - 1, ink, 1, 0, op);
+ return pieNew(im, x0, y0, x1, y1, start, end, ink, width, op);
+ }
+}
+
+/* -------------------------------------------------------------------- */
+
+/* experimental level 2 ("arrow") graphics stuff. this implements
+ portions of the arrow api on top of the Edge structure. the
+ semantics are ok, except that "curve" flattens the bezier curves by
+ itself */
+
+struct ImagingOutlineInstance {
+ float x0, y0;
+
+ float x, y;
+
+ int count;
+ Edge *edges;
+
+ int size;
+};
+
+ImagingOutline
+ImagingOutlineNew(void) {
+ ImagingOutline outline;
+
+ outline = calloc(1, sizeof(struct ImagingOutlineInstance));
+ if (!outline) {
+ return (ImagingOutline)ImagingError_MemoryError();
+ }
+
+ outline->edges = NULL;
+ outline->count = outline->size = 0;
+
+ ImagingOutlineMove(outline, 0, 0);
+
+ return outline;
+}
+
+void
+ImagingOutlineDelete(ImagingOutline outline) {
+ if (!outline) {
+ return;
+ }
+
+ if (outline->edges) {
+ free(outline->edges);
+ }
+
+ free(outline);
+}
+
+static Edge *
+allocate(ImagingOutline outline, int extra) {
+ Edge *e;
+
+ if (outline->count + extra > outline->size) {
+ /* expand outline buffer */
+ outline->size += extra + 25;
+ if (!outline->edges) {
+ /* malloc check ok, uses calloc for overflow */
+ e = calloc(outline->size, sizeof(Edge));
+ } else {
+ if (outline->size > INT_MAX / (int)sizeof(Edge)) {
+ return NULL;
+ }
+ /* malloc check ok, overflow checked above */
+ e = realloc(outline->edges, outline->size * sizeof(Edge));
+ }
+ if (!e) {
+ return NULL;
+ }
+ outline->edges = e;
+ }
+
+ e = outline->edges + outline->count;
+
+ outline->count += extra;
+
+ return e;
+}
+
+int
+ImagingOutlineMove(ImagingOutline outline, float x0, float y0) {
+ outline->x = outline->x0 = x0;
+ outline->y = outline->y0 = y0;
+
+ return 0;
+}
+
+int
+ImagingOutlineLine(ImagingOutline outline, float x1, float y1) {
+ Edge *e;
+
+ e = allocate(outline, 1);
+ if (!e) {
+ return -1; /* out of memory */
+ }
+
+ add_edge(e, (int)outline->x, (int)outline->y, (int)x1, (int)y1);
+
+ outline->x = x1;
+ outline->y = y1;
+
+ return 0;
+}
+
+int
+ImagingOutlineCurve(
+ ImagingOutline outline,
+ float x1,
+ float y1,
+ float x2,
+ float y2,
+ float x3,
+ float y3) {
+ Edge *e;
+ int i;
+ float xo, yo;
+
+#define STEPS 32
+
+ e = allocate(outline, STEPS);
+ if (!e) {
+ return -1; /* out of memory */
+ }
+
+ xo = outline->x;
+ yo = outline->y;
+
+ /* flatten the bezier segment */
+
+ for (i = 1; i <= STEPS; i++) {
+ float t = ((float)i) / STEPS;
+ float t2 = t * t;
+ float t3 = t2 * t;
+
+ float u = 1.0F - t;
+ float u2 = u * u;
+ float u3 = u2 * u;
+
+ float x = outline->x * u3 + 3 * (x1 * t * u2 + x2 * t2 * u) + x3 * t3 + 0.5;
+ float y = outline->y * u3 + 3 * (y1 * t * u2 + y2 * t2 * u) + y3 * t3 + 0.5;
+
+ add_edge(e++, xo, yo, (int)x, (int)y);
+
+ xo = x, yo = y;
+ }
+
+ outline->x = xo;
+ outline->y = yo;
+
+ return 0;
+}
+
+int
+ImagingOutlineClose(ImagingOutline outline) {
+ if (outline->x == outline->x0 && outline->y == outline->y0) {
+ return 0;
+ }
+ return ImagingOutlineLine(outline, outline->x0, outline->y0);
+}
+
+int
+ImagingOutlineTransform(ImagingOutline outline, double a[6]) {
+ Edge *eIn;
+ Edge *eOut;
+ int i, n;
+ int x0, y0, x1, y1;
+ int X0, Y0, X1, Y1;
+
+ double a0 = a[0];
+ double a1 = a[1];
+ double a2 = a[2];
+ double a3 = a[3];
+ double a4 = a[4];
+ double a5 = a[5];
+
+ eIn = outline->edges;
+ n = outline->count;
+
+ eOut = allocate(outline, n);
+ if (!eOut) {
+ ImagingError_MemoryError();
+ return -1;
+ }
+
+ for (i = 0; i < n; i++) {
+ x0 = eIn->x0;
+ y0 = eIn->y0;
+
+ /* FIXME: ouch! */
+ if (eIn->x0 == eIn->xmin) {
+ x1 = eIn->xmax;
+ } else {
+ x1 = eIn->xmin;
+ }
+ if (eIn->y0 == eIn->ymin) {
+ y1 = eIn->ymax;
+ } else {
+ y1 = eIn->ymin;
+ }
+
+ /* full moon tonight! if this doesn't work, you may need to
+ upgrade your compiler (make sure you have the right service
+ pack) */
+
+ X0 = (int)(a0 * x0 + a1 * y0 + a2);
+ Y0 = (int)(a3 * x0 + a4 * y0 + a5);
+ X1 = (int)(a0 * x1 + a1 * y1 + a2);
+ Y1 = (int)(a3 * x1 + a4 * y1 + a5);
+
+ add_edge(eOut, X0, Y0, X1, Y1);
+
+ eIn++;
+ eOut++;
+ }
+
+ free(outline->edges);
+
+ /* FIXME: ugly! */
+ outline->edges = NULL;
+ outline->count = outline->size = 0;
+
+ return 0;
+}
+
+int
+ImagingDrawOutline(
+ Imaging im, ImagingOutline outline, const void *ink_, int fill, int op) {
+ DRAW *draw;
+ INT32 ink;
+
+ DRAWINIT();
+
+ draw->polygon(im, outline->count, outline->edges, ink, 0);
+
+ return 0;
+}
diff --git a/contrib/python/Pillow/py3/libImaging/Effects.c b/contrib/python/Pillow/py3/libImaging/Effects.c
new file mode 100644
index 00000000000..93e7af0bce9
--- /dev/null
+++ b/contrib/python/Pillow/py3/libImaging/Effects.c
@@ -0,0 +1,160 @@
+/*
+ * The Python Imaging Library
+ * $Id$
+ *
+ * various special effects and image generators
+ *
+ * history:
+ * 1997-05-21 fl Just for fun
+ * 1997-06-05 fl Added mandelbrot generator
+ * 2003-05-24 fl Added perlin_turbulence generator (in progress)
+ *
+ * Copyright (c) 1997-2003 by Fredrik Lundh.
+ * Copyright (c) 1997 by Secret Labs AB.
+ *
+ * See the README file for information on usage and redistribution.
+ */
+
+#include "Imaging.h"
+
+#include <math.h>
+
+Imaging
+ImagingEffectMandelbrot(int xsize, int ysize, double extent[4], int quality) {
+ /* Generate a Mandelbrot set covering the given extent */
+
+ Imaging im;
+ int x, y, k;
+ double width, height;
+ double x1, y1, xi2, yi2, cr, ci, radius;
+ double dr, di;
+
+ /* Check arguments */
+ width = extent[2] - extent[0];
+ height = extent[3] - extent[1];
+ if (width < 0.0 || height < 0.0 || quality < 2) {
+ return (Imaging)ImagingError_ValueError(NULL);
+ }
+
+ im = ImagingNewDirty("L", xsize, ysize);
+ if (!im) {
+ return NULL;
+ }
+
+ dr = width / (xsize - 1);
+ di = height / (ysize - 1);
+
+ radius = 100.0;
+
+ for (y = 0; y < ysize; y++) {
+ UINT8 *buf = im->image8[y];
+ for (x = 0; x < xsize; x++) {
+ x1 = y1 = xi2 = yi2 = 0.0;
+ cr = x * dr + extent[0];
+ ci = y * di + extent[1];
+ for (k = 1;; k++) {
+ y1 = 2 * x1 * y1 + ci;
+ x1 = xi2 - yi2 + cr;
+ xi2 = x1 * x1;
+ yi2 = y1 * y1;
+ if ((xi2 + yi2) > radius) {
+ buf[x] = k * 255 / quality;
+ break;
+ }
+ if (k > quality) {
+ buf[x] = 0;
+ break;
+ }
+ }
+ }
+ }
+ return im;
+}
+
+Imaging
+ImagingEffectNoise(int xsize, int ysize, float sigma) {
+ /* Generate Gaussian noise centered around 128 */
+
+ Imaging imOut;
+ int x, y;
+ int nextok;
+ double this, next;
+
+ imOut = ImagingNewDirty("L", xsize, ysize);
+ if (!imOut) {
+ return NULL;
+ }
+
+ next = 0.0;
+ nextok = 0;
+
+ for (y = 0; y < imOut->ysize; y++) {
+ UINT8 *out = imOut->image8[y];
+ for (x = 0; x < imOut->xsize; x++) {
+ if (nextok) {
+ this = next;
+ nextok = 0;
+ } else {
+ /* after numerical recipes */
+ double v1, v2, radius, factor;
+ do {
+ v1 = rand() * (2.0 / RAND_MAX) - 1.0;
+ v2 = rand() * (2.0 / RAND_MAX) - 1.0;
+ radius = v1 * v1 + v2 * v2;
+ } while (radius >= 1.0);
+ factor = sqrt(-2.0 * log(radius) / radius);
+ this = factor * v1;
+ next = factor * v2;
+ }
+ out[x] = CLIP8(128 + sigma * this);
+ }
+ }
+
+ return imOut;
+}
+
+Imaging
+ImagingEffectSpread(Imaging imIn, int distance) {
+ /* Randomly spread pixels in an image */
+
+ Imaging imOut;
+ int x, y;
+
+ imOut = ImagingNewDirty(imIn->mode, imIn->xsize, imIn->ysize);
+
+ if (!imOut) {
+ return NULL;
+ }
+
+#define SPREAD(type, image) \
+ if (distance == 0) { \
+ for (y = 0; y < imOut->ysize; y++) { \
+ for (x = 0; x < imOut->xsize; x++) { \
+ imOut->image[y][x] = imIn->image[y][x]; \
+ } \
+ } \
+ } else { \
+ for (y = 0; y < imOut->ysize; y++) { \
+ for (x = 0; x < imOut->xsize; x++) { \
+ int xx = x + (rand() % distance) - distance / 2; \
+ int yy = y + (rand() % distance) - distance / 2; \
+ if (xx >= 0 && xx < imIn->xsize && yy >= 0 && yy < imIn->ysize) { \
+ imOut->image[yy][xx] = imIn->image[y][x]; \
+ imOut->image[y][x] = imIn->image[yy][xx]; \
+ } else { \
+ imOut->image[y][x] = imIn->image[y][x]; \
+ } \
+ } \
+ } \
+ }
+
+ if (imIn->image8) {
+ SPREAD(UINT8, image8);
+ } else {
+ SPREAD(INT32, image32);
+ }
+
+ ImagingCopyPalette(imOut, imIn);
+
+ return imOut;
+}
diff --git a/contrib/python/Pillow/py3/libImaging/EpsEncode.c b/contrib/python/Pillow/py3/libImaging/EpsEncode.c
new file mode 100644
index 00000000000..3f2cb33b2d2
--- /dev/null
+++ b/contrib/python/Pillow/py3/libImaging/EpsEncode.c
@@ -0,0 +1,77 @@
+/*
+ * The Python Imaging Library.
+ * $Id$
+ *
+ * encoder for EPS hex data
+ *
+ * history:
+ * 96-04-19 fl created
+ * 96-06-27 fl don't drop last block of encoded data
+ *
+ * notes:
+ * FIXME: rename to HexEncode.c ??
+ *
+ * Copyright (c) Fredrik Lundh 1996.
+ * Copyright (c) Secret Labs AB 1997.
+ *
+ * See the README file for information on usage and redistribution.
+ */
+
+#include "Imaging.h"
+
+int
+ImagingEpsEncode(Imaging im, ImagingCodecState state, UINT8 *buf, int bytes) {
+ enum { HEXBYTE = 1, NEWLINE };
+ const char *hex = "0123456789abcdef";
+
+ UINT8 *ptr = buf;
+ UINT8 *in, i;
+
+ if (!state->state) {
+ state->state = HEXBYTE;
+ state->xsize *= im->pixelsize; /* Hack! */
+ }
+
+ in = (UINT8 *)im->image[state->y];
+
+ for (;;) {
+ if (state->state == NEWLINE) {
+ if (bytes < 1) {
+ break;
+ }
+ *ptr++ = '\n';
+ bytes--;
+ state->state = HEXBYTE;
+ }
+
+ if (bytes < 2) {
+ break;
+ }
+
+ i = in[state->x++];
+ *ptr++ = hex[(i >> 4) & 15];
+ *ptr++ = hex[i & 15];
+ bytes -= 2;
+
+ /* Skip junk bytes */
+ if (im->bands == 3 && (state->x & 3) == 3) {
+ state->x++;
+ }
+
+ if (++state->count >= 79 / 2) {
+ state->state = NEWLINE;
+ state->count = 0;
+ }
+
+ if (state->x >= state->xsize) {
+ state->x = 0;
+ if (++state->y >= state->ysize) {
+ state->errcode = IMAGING_CODEC_END;
+ break;
+ }
+ in = (UINT8 *)im->image[state->y];
+ }
+ }
+
+ return ptr - buf;
+}
diff --git a/contrib/python/Pillow/py3/libImaging/File.c b/contrib/python/Pillow/py3/libImaging/File.c
new file mode 100644
index 00000000000..76d0abccc4f
--- /dev/null
+++ b/contrib/python/Pillow/py3/libImaging/File.c
@@ -0,0 +1,78 @@
+/*
+ * The Python Imaging Library
+ * $Id$
+ *
+ * built-in image file handling
+ *
+ * history:
+ * 1995-11-26 fl Created, supports PGM/PPM
+ * 1996-08-07 fl Write "1" images as PGM
+ * 1999-02-21 fl Don't write non-standard modes
+ *
+ * Copyright (c) 1997-99 by Secret Labs AB.
+ * Copyright (c) 1995-96 by Fredrik Lundh.
+ *
+ * See the README file for information on usage and redistribution.
+ */
+
+#include "Imaging.h"
+
+#include <ctype.h>
+
+int
+ImagingSaveRaw(Imaging im, FILE *fp) {
+ int x, y, i;
+
+ if (strcmp(im->mode, "1") == 0 || strcmp(im->mode, "L") == 0) {
+ /* @PIL227: FIXME: for mode "1", map != 0 to 255 */
+
+ /* PGM "L" */
+ for (y = 0; y < im->ysize; y++) {
+ fwrite(im->image[y], 1, im->xsize, fp);
+ }
+
+ } else {
+ /* PPM "RGB" or other internal format */
+ for (y = 0; y < im->ysize; y++) {
+ for (x = i = 0; x < im->xsize; x++, i += im->pixelsize) {
+ fwrite(im->image[y] + i, 1, im->bands, fp);
+ }
+ }
+ }
+
+ return 1;
+}
+
+int
+ImagingSavePPM(Imaging im, const char *outfile) {
+ FILE *fp;
+
+ if (!im) {
+ (void)ImagingError_ValueError(NULL);
+ return 0;
+ }
+
+ fp = fopen(outfile, "wb");
+ if (!fp) {
+ (void)ImagingError_OSError();
+ return 0;
+ }
+
+ if (strcmp(im->mode, "1") == 0 || strcmp(im->mode, "L") == 0) {
+ /* Write "PGM" */
+ fprintf(fp, "P5\n%d %d\n255\n", im->xsize, im->ysize);
+ } else if (strcmp(im->mode, "RGB") == 0) {
+ /* Write "PPM" */
+ fprintf(fp, "P6\n%d %d\n255\n", im->xsize, im->ysize);
+ } else {
+ fclose(fp);
+ (void)ImagingError_ModeError();
+ return 0;
+ }
+
+ ImagingSaveRaw(im, fp);
+
+ fclose(fp);
+
+ return 1;
+}
diff --git a/contrib/python/Pillow/py3/libImaging/Fill.c b/contrib/python/Pillow/py3/libImaging/Fill.c
new file mode 100644
index 00000000000..5b6bfb89cd8
--- /dev/null
+++ b/contrib/python/Pillow/py3/libImaging/Fill.c
@@ -0,0 +1,139 @@
+/*
+ * The Python Imaging Library
+ * $Id$
+ *
+ * fill image with constant pixel value
+ *
+ * history:
+ * 95-11-26 fl moved from Imaging.c
+ * 96-05-17 fl added radial fill, renamed wedge to linear
+ * 98-06-23 fl changed ImageFill signature
+ *
+ * Copyright (c) Secret Labs AB 1997-98. All rights reserved.
+ * Copyright (c) Fredrik Lundh 1995-96.
+ *
+ * See the README file for information on usage and redistribution.
+ */
+
+#include "Imaging.h"
+
+#include "math.h"
+
+Imaging
+ImagingFill(Imaging im, const void *colour) {
+ int x, y;
+ ImagingSectionCookie cookie;
+
+ /* 0-width or 0-height image. No need to do anything */
+ if (!im->linesize || !im->ysize) {
+ return im;
+ }
+
+ if (im->type == IMAGING_TYPE_SPECIAL) {
+ /* use generic API */
+ ImagingAccess access = ImagingAccessNew(im);
+ if (access) {
+ for (y = 0; y < im->ysize; y++) {
+ for (x = 0; x < im->xsize; x++) {
+ access->put_pixel(im, x, y, colour);
+ }
+ }
+ ImagingAccessDelete(im, access);
+ } else {
+ /* wipe the image */
+ for (y = 0; y < im->ysize; y++) {
+ memset(im->image[y], 0, im->linesize);
+ }
+ }
+ } else {
+ INT32 c = 0L;
+ ImagingSectionEnter(&cookie);
+ memcpy(&c, colour, im->pixelsize);
+ if (im->image32 && c != 0L) {
+ for (y = 0; y < im->ysize; y++) {
+ for (x = 0; x < im->xsize; x++) {
+ im->image32[y][x] = c;
+ }
+ }
+ } else {
+ unsigned char cc = (unsigned char)*(UINT8 *)colour;
+ for (y = 0; y < im->ysize; y++) {
+ memset(im->image[y], cc, im->linesize);
+ }
+ }
+ ImagingSectionLeave(&cookie);
+ }
+
+ return im;
+}
+
+Imaging
+ImagingFillLinearGradient(const char *mode) {
+ Imaging im;
+ int y;
+
+ if (strlen(mode) != 1) {
+ return (Imaging)ImagingError_ModeError();
+ }
+
+ im = ImagingNewDirty(mode, 256, 256);
+ if (!im) {
+ return NULL;
+ }
+
+ if (im->image8) {
+ for (y = 0; y < 256; y++) {
+ memset(im->image8[y], (unsigned char)y, 256);
+ }
+ } else {
+ int x;
+ for (y = 0; y < 256; y++) {
+ for (x = 0; x < 256; x++) {
+ if (im->type == IMAGING_TYPE_FLOAT32) {
+ IMAGING_PIXEL_FLOAT32(im, x, y) = y;
+ } else {
+ IMAGING_PIXEL_INT32(im, x, y) = y;
+ }
+ }
+ }
+ }
+
+ return im;
+}
+
+Imaging
+ImagingFillRadialGradient(const char *mode) {
+ Imaging im;
+ int x, y;
+ int d;
+
+ if (strlen(mode) != 1) {
+ return (Imaging)ImagingError_ModeError();
+ }
+
+ im = ImagingNewDirty(mode, 256, 256);
+ if (!im) {
+ return NULL;
+ }
+
+ for (y = 0; y < 256; y++) {
+ for (x = 0; x < 256; x++) {
+ d = (int)sqrt(
+ (double)((x - 128) * (x - 128) + (y - 128) * (y - 128)) * 2.0);
+ if (d >= 255) {
+ d = 255;
+ }
+ if (im->image8) {
+ im->image8[y][x] = d;
+ } else {
+ if (im->type == IMAGING_TYPE_FLOAT32) {
+ IMAGING_PIXEL_FLOAT32(im, x, y) = d;
+ } else {
+ IMAGING_PIXEL_INT32(im, x, y) = d;
+ }
+ }
+ }
+ }
+
+ return im;
+}
diff --git a/contrib/python/Pillow/py3/libImaging/Filter.c b/contrib/python/Pillow/py3/libImaging/Filter.c
new file mode 100644
index 00000000000..4dcd368ca80
--- /dev/null
+++ b/contrib/python/Pillow/py3/libImaging/Filter.c
@@ -0,0 +1,412 @@
+/*
+ * The Python Imaging Library
+ * $Id$
+ *
+ * apply convolution kernel to image
+ *
+ * history:
+ * 1995-11-26 fl Created, supports 3x3 kernels
+ * 1995-11-27 fl Added 5x5 kernels, copy border
+ * 1999-07-26 fl Eliminated a few compiler warnings
+ * 2002-06-09 fl Moved kernel definitions to Python
+ * 2002-06-11 fl Support floating point kernels
+ * 2003-09-15 fl Added ImagingExpand helper
+ *
+ * Copyright (c) Secret Labs AB 1997-2002. All rights reserved.
+ * Copyright (c) Fredrik Lundh 1995.
+ *
+ * See the README file for information on usage and redistribution.
+ */
+
+/*
+ * FIXME: Support RGB and RGBA/CMYK modes as well
+ * FIXME: Expand image border (current version leaves border as is)
+ * FIXME: Implement image processing gradient filters
+ */
+
+#include "Imaging.h"
+
+static inline UINT8
+clip8(float in) {
+ if (in <= 0.0) {
+ return 0;
+ }
+ if (in >= 255.0) {
+ return 255;
+ }
+ return (UINT8)in;
+}
+
+static inline INT32
+clip32(float in) {
+ if (in <= 0.0) {
+ return 0;
+ }
+ if (in >= pow(2, 31) - 1) {
+ return pow(2, 31) - 1;
+ }
+ return (INT32)in;
+}
+
+Imaging
+ImagingExpand(Imaging imIn, int xmargin, int ymargin) {
+ Imaging imOut;
+ int x, y;
+ ImagingSectionCookie cookie;
+
+ if (xmargin < 0 && ymargin < 0) {
+ return (Imaging)ImagingError_ValueError("bad kernel size");
+ }
+
+ imOut = ImagingNewDirty(
+ imIn->mode, imIn->xsize + 2 * xmargin, imIn->ysize + 2 * ymargin);
+ if (!imOut) {
+ return NULL;
+ }
+
+#define EXPAND_LINE(type, image, yin, yout) \
+ { \
+ for (x = 0; x < xmargin; x++) { \
+ imOut->image[yout][x] = imIn->image[yin][0]; \
+ } \
+ for (x = 0; x < imIn->xsize; x++) { \
+ imOut->image[yout][x + xmargin] = imIn->image[yin][x]; \
+ } \
+ for (x = 0; x < xmargin; x++) { \
+ imOut->image[yout][xmargin + imIn->xsize + x] = \
+ imIn->image[yin][imIn->xsize - 1]; \
+ } \
+ }
+
+#define EXPAND(type, image) \
+ { \
+ for (y = 0; y < ymargin; y++) { \
+ EXPAND_LINE(type, image, 0, y); \
+ } \
+ for (y = 0; y < imIn->ysize; y++) { \
+ EXPAND_LINE(type, image, y, y + ymargin); \
+ } \
+ for (y = 0; y < ymargin; y++) { \
+ EXPAND_LINE(type, image, imIn->ysize - 1, ymargin + imIn->ysize + y); \
+ } \
+ }
+
+ ImagingSectionEnter(&cookie);
+ if (imIn->image8) {
+ EXPAND(UINT8, image8);
+ } else {
+ EXPAND(INT32, image32);
+ }
+ ImagingSectionLeave(&cookie);
+
+ ImagingCopyPalette(imOut, imIn);
+
+ return imOut;
+}
+
+void
+ImagingFilter3x3(Imaging imOut, Imaging im, const float *kernel, float offset) {
+#define KERNEL1x3(in0, x, kernel, d) \
+ (_i2f(in0[x - d]) * (kernel)[0] + _i2f(in0[x]) * (kernel)[1] + \
+ _i2f(in0[x + d]) * (kernel)[2])
+
+ int x = 0, y = 0;
+
+ memcpy(imOut->image[0], im->image[0], im->linesize);
+ if (im->bands == 1) {
+ // Add one time for rounding
+ offset += 0.5;
+ if (im->type == IMAGING_TYPE_INT32) {
+ for (y = 1; y < im->ysize - 1; y++) {
+ INT32 *in_1 = (INT32 *)im->image[y - 1];
+ INT32 *in0 = (INT32 *)im->image[y];
+ INT32 *in1 = (INT32 *)im->image[y + 1];
+ INT32 *out = (INT32 *)imOut->image[y];
+
+ out[0] = in0[0];
+ for (x = 1; x < im->xsize - 1; x++) {
+ float ss = offset;
+ ss += KERNEL1x3(in1, x, &kernel[0], 1);
+ ss += KERNEL1x3(in0, x, &kernel[3], 1);
+ ss += KERNEL1x3(in_1, x, &kernel[6], 1);
+ out[x] = clip32(ss);
+ }
+ out[x] = in0[x];
+ }
+ } else {
+ for (y = 1; y < im->ysize - 1; y++) {
+ UINT8 *in_1 = (UINT8 *)im->image[y - 1];
+ UINT8 *in0 = (UINT8 *)im->image[y];
+ UINT8 *in1 = (UINT8 *)im->image[y + 1];
+ UINT8 *out = (UINT8 *)imOut->image[y];
+
+ out[0] = in0[0];
+ for (x = 1; x < im->xsize - 1; x++) {
+ float ss = offset;
+ ss += KERNEL1x3(in1, x, &kernel[0], 1);
+ ss += KERNEL1x3(in0, x, &kernel[3], 1);
+ ss += KERNEL1x3(in_1, x, &kernel[6], 1);
+ out[x] = clip8(ss);
+ }
+ out[x] = in0[x];
+ }
+ }
+ } else {
+ // Add one time for rounding
+ offset += 0.5;
+ for (y = 1; y < im->ysize - 1; y++) {
+ UINT8 *in_1 = (UINT8 *)im->image[y - 1];
+ UINT8 *in0 = (UINT8 *)im->image[y];
+ UINT8 *in1 = (UINT8 *)im->image[y + 1];
+ UINT8 *out = (UINT8 *)imOut->image[y];
+
+ memcpy(out, in0, sizeof(UINT32));
+ if (im->bands == 2) {
+ for (x = 1; x < im->xsize - 1; x++) {
+ float ss0 = offset;
+ float ss3 = offset;
+ UINT32 v;
+ ss0 += KERNEL1x3(in1, x * 4 + 0, &kernel[0], 4);
+ ss3 += KERNEL1x3(in1, x * 4 + 3, &kernel[0], 4);
+ ss0 += KERNEL1x3(in0, x * 4 + 0, &kernel[3], 4);
+ ss3 += KERNEL1x3(in0, x * 4 + 3, &kernel[3], 4);
+ ss0 += KERNEL1x3(in_1, x * 4 + 0, &kernel[6], 4);
+ ss3 += KERNEL1x3(in_1, x * 4 + 3, &kernel[6], 4);
+ v = MAKE_UINT32(clip8(ss0), 0, 0, clip8(ss3));
+ memcpy(out + x * sizeof(v), &v, sizeof(v));
+ }
+ } else if (im->bands == 3) {
+ for (x = 1; x < im->xsize - 1; x++) {
+ float ss0 = offset;
+ float ss1 = offset;
+ float ss2 = offset;
+ UINT32 v;
+ ss0 += KERNEL1x3(in1, x * 4 + 0, &kernel[0], 4);
+ ss1 += KERNEL1x3(in1, x * 4 + 1, &kernel[0], 4);
+ ss2 += KERNEL1x3(in1, x * 4 + 2, &kernel[0], 4);
+ ss0 += KERNEL1x3(in0, x * 4 + 0, &kernel[3], 4);
+ ss1 += KERNEL1x3(in0, x * 4 + 1, &kernel[3], 4);
+ ss2 += KERNEL1x3(in0, x * 4 + 2, &kernel[3], 4);
+ ss0 += KERNEL1x3(in_1, x * 4 + 0, &kernel[6], 4);
+ ss1 += KERNEL1x3(in_1, x * 4 + 1, &kernel[6], 4);
+ ss2 += KERNEL1x3(in_1, x * 4 + 2, &kernel[6], 4);
+ v = MAKE_UINT32(clip8(ss0), clip8(ss1), clip8(ss2), 0);
+ memcpy(out + x * sizeof(v), &v, sizeof(v));
+ }
+ } else if (im->bands == 4) {
+ for (x = 1; x < im->xsize - 1; x++) {
+ float ss0 = offset;
+ float ss1 = offset;
+ float ss2 = offset;
+ float ss3 = offset;
+ UINT32 v;
+ ss0 += KERNEL1x3(in1, x * 4 + 0, &kernel[0], 4);
+ ss1 += KERNEL1x3(in1, x * 4 + 1, &kernel[0], 4);
+ ss2 += KERNEL1x3(in1, x * 4 + 2, &kernel[0], 4);
+ ss3 += KERNEL1x3(in1, x * 4 + 3, &kernel[0], 4);
+ ss0 += KERNEL1x3(in0, x * 4 + 0, &kernel[3], 4);
+ ss1 += KERNEL1x3(in0, x * 4 + 1, &kernel[3], 4);
+ ss2 += KERNEL1x3(in0, x * 4 + 2, &kernel[3], 4);
+ ss3 += KERNEL1x3(in0, x * 4 + 3, &kernel[3], 4);
+ ss0 += KERNEL1x3(in_1, x * 4 + 0, &kernel[6], 4);
+ ss1 += KERNEL1x3(in_1, x * 4 + 1, &kernel[6], 4);
+ ss2 += KERNEL1x3(in_1, x * 4 + 2, &kernel[6], 4);
+ ss3 += KERNEL1x3(in_1, x * 4 + 3, &kernel[6], 4);
+ v = MAKE_UINT32(clip8(ss0), clip8(ss1), clip8(ss2), clip8(ss3));
+ memcpy(out + x * sizeof(v), &v, sizeof(v));
+ }
+ }
+ memcpy(out + x * sizeof(UINT32), in0 + x * sizeof(UINT32), sizeof(UINT32));
+ }
+ }
+ memcpy(imOut->image[y], im->image[y], im->linesize);
+}
+
+void
+ImagingFilter5x5(Imaging imOut, Imaging im, const float *kernel, float offset) {
+#define KERNEL1x5(in0, x, kernel, d) \
+ (_i2f(in0[x - d - d]) * (kernel)[0] + \
+ _i2f(in0[x - d]) * (kernel)[1] + _i2f(in0[x]) * (kernel)[2] + \
+ _i2f(in0[x + d]) * (kernel)[3] + \
+ _i2f(in0[x + d + d]) * (kernel)[4])
+
+ int x = 0, y = 0;
+
+ memcpy(imOut->image[0], im->image[0], im->linesize);
+ memcpy(imOut->image[1], im->image[1], im->linesize);
+ if (im->bands == 1) {
+ // Add one time for rounding
+ offset += 0.5;
+ if (im->type == IMAGING_TYPE_INT32) {
+ for (y = 2; y < im->ysize - 2; y++) {
+ INT32 *in_2 = (INT32 *)im->image[y - 2];
+ INT32 *in_1 = (INT32 *)im->image[y - 1];
+ INT32 *in0 = (INT32 *)im->image[y];
+ INT32 *in1 = (INT32 *)im->image[y + 1];
+ INT32 *in2 = (INT32 *)im->image[y + 2];
+ INT32 *out = (INT32 *)imOut->image[y];
+
+ out[0] = in0[0];
+ out[1] = in0[1];
+ for (x = 2; x < im->xsize - 2; x++) {
+ float ss = offset;
+ ss += KERNEL1x5(in2, x, &kernel[0], 1);
+ ss += KERNEL1x5(in1, x, &kernel[5], 1);
+ ss += KERNEL1x5(in0, x, &kernel[10], 1);
+ ss += KERNEL1x5(in_1, x, &kernel[15], 1);
+ ss += KERNEL1x5(in_2, x, &kernel[20], 1);
+ out[x] = clip32(ss);
+ }
+ out[x + 0] = in0[x + 0];
+ out[x + 1] = in0[x + 1];
+ }
+ } else {
+ for (y = 2; y < im->ysize - 2; y++) {
+ UINT8 *in_2 = (UINT8 *)im->image[y - 2];
+ UINT8 *in_1 = (UINT8 *)im->image[y - 1];
+ UINT8 *in0 = (UINT8 *)im->image[y];
+ UINT8 *in1 = (UINT8 *)im->image[y + 1];
+ UINT8 *in2 = (UINT8 *)im->image[y + 2];
+ UINT8 *out = (UINT8 *)imOut->image[y];
+
+ out[0] = in0[0];
+ out[1] = in0[1];
+ for (x = 2; x < im->xsize - 2; x++) {
+ float ss = offset;
+ ss += KERNEL1x5(in2, x, &kernel[0], 1);
+ ss += KERNEL1x5(in1, x, &kernel[5], 1);
+ ss += KERNEL1x5(in0, x, &kernel[10], 1);
+ ss += KERNEL1x5(in_1, x, &kernel[15], 1);
+ ss += KERNEL1x5(in_2, x, &kernel[20], 1);
+ out[x] = clip8(ss);
+ }
+ out[x + 0] = in0[x + 0];
+ out[x + 1] = in0[x + 1];
+ }
+ }
+ } else {
+ // Add one time for rounding
+ offset += 0.5;
+ for (y = 2; y < im->ysize - 2; y++) {
+ UINT8 *in_2 = (UINT8 *)im->image[y - 2];
+ UINT8 *in_1 = (UINT8 *)im->image[y - 1];
+ UINT8 *in0 = (UINT8 *)im->image[y];
+ UINT8 *in1 = (UINT8 *)im->image[y + 1];
+ UINT8 *in2 = (UINT8 *)im->image[y + 2];
+ UINT8 *out = (UINT8 *)imOut->image[y];
+
+ memcpy(out, in0, sizeof(UINT32) * 2);
+ if (im->bands == 2) {
+ for (x = 2; x < im->xsize - 2; x++) {
+ float ss0 = offset;
+ float ss3 = offset;
+ UINT32 v;
+ ss0 += KERNEL1x5(in2, x * 4 + 0, &kernel[0], 4);
+ ss3 += KERNEL1x5(in2, x * 4 + 3, &kernel[0], 4);
+ ss0 += KERNEL1x5(in1, x * 4 + 0, &kernel[5], 4);
+ ss3 += KERNEL1x5(in1, x * 4 + 3, &kernel[5], 4);
+ ss0 += KERNEL1x5(in0, x * 4 + 0, &kernel[10], 4);
+ ss3 += KERNEL1x5(in0, x * 4 + 3, &kernel[10], 4);
+ ss0 += KERNEL1x5(in_1, x * 4 + 0, &kernel[15], 4);
+ ss3 += KERNEL1x5(in_1, x * 4 + 3, &kernel[15], 4);
+ ss0 += KERNEL1x5(in_2, x * 4 + 0, &kernel[20], 4);
+ ss3 += KERNEL1x5(in_2, x * 4 + 3, &kernel[20], 4);
+ v = MAKE_UINT32(clip8(ss0), 0, 0, clip8(ss3));
+ memcpy(out + x * sizeof(v), &v, sizeof(v));
+ }
+ } else if (im->bands == 3) {
+ for (x = 2; x < im->xsize - 2; x++) {
+ float ss0 = offset;
+ float ss1 = offset;
+ float ss2 = offset;
+ UINT32 v;
+ ss0 += KERNEL1x5(in2, x * 4 + 0, &kernel[0], 4);
+ ss1 += KERNEL1x5(in2, x * 4 + 1, &kernel[0], 4);
+ ss2 += KERNEL1x5(in2, x * 4 + 2, &kernel[0], 4);
+ ss0 += KERNEL1x5(in1, x * 4 + 0, &kernel[5], 4);
+ ss1 += KERNEL1x5(in1, x * 4 + 1, &kernel[5], 4);
+ ss2 += KERNEL1x5(in1, x * 4 + 2, &kernel[5], 4);
+ ss0 += KERNEL1x5(in0, x * 4 + 0, &kernel[10], 4);
+ ss1 += KERNEL1x5(in0, x * 4 + 1, &kernel[10], 4);
+ ss2 += KERNEL1x5(in0, x * 4 + 2, &kernel[10], 4);
+ ss0 += KERNEL1x5(in_1, x * 4 + 0, &kernel[15], 4);
+ ss1 += KERNEL1x5(in_1, x * 4 + 1, &kernel[15], 4);
+ ss2 += KERNEL1x5(in_1, x * 4 + 2, &kernel[15], 4);
+ ss0 += KERNEL1x5(in_2, x * 4 + 0, &kernel[20], 4);
+ ss1 += KERNEL1x5(in_2, x * 4 + 1, &kernel[20], 4);
+ ss2 += KERNEL1x5(in_2, x * 4 + 2, &kernel[20], 4);
+ v = MAKE_UINT32(clip8(ss0), clip8(ss1), clip8(ss2), 0);
+ memcpy(out + x * sizeof(v), &v, sizeof(v));
+ }
+ } else if (im->bands == 4) {
+ for (x = 2; x < im->xsize - 2; x++) {
+ float ss0 = offset;
+ float ss1 = offset;
+ float ss2 = offset;
+ float ss3 = offset;
+ UINT32 v;
+ ss0 += KERNEL1x5(in2, x * 4 + 0, &kernel[0], 4);
+ ss1 += KERNEL1x5(in2, x * 4 + 1, &kernel[0], 4);
+ ss2 += KERNEL1x5(in2, x * 4 + 2, &kernel[0], 4);
+ ss3 += KERNEL1x5(in2, x * 4 + 3, &kernel[0], 4);
+ ss0 += KERNEL1x5(in1, x * 4 + 0, &kernel[5], 4);
+ ss1 += KERNEL1x5(in1, x * 4 + 1, &kernel[5], 4);
+ ss2 += KERNEL1x5(in1, x * 4 + 2, &kernel[5], 4);
+ ss3 += KERNEL1x5(in1, x * 4 + 3, &kernel[5], 4);
+ ss0 += KERNEL1x5(in0, x * 4 + 0, &kernel[10], 4);
+ ss1 += KERNEL1x5(in0, x * 4 + 1, &kernel[10], 4);
+ ss2 += KERNEL1x5(in0, x * 4 + 2, &kernel[10], 4);
+ ss3 += KERNEL1x5(in0, x * 4 + 3, &kernel[10], 4);
+ ss0 += KERNEL1x5(in_1, x * 4 + 0, &kernel[15], 4);
+ ss1 += KERNEL1x5(in_1, x * 4 + 1, &kernel[15], 4);
+ ss2 += KERNEL1x5(in_1, x * 4 + 2, &kernel[15], 4);
+ ss3 += KERNEL1x5(in_1, x * 4 + 3, &kernel[15], 4);
+ ss0 += KERNEL1x5(in_2, x * 4 + 0, &kernel[20], 4);
+ ss1 += KERNEL1x5(in_2, x * 4 + 1, &kernel[20], 4);
+ ss2 += KERNEL1x5(in_2, x * 4 + 2, &kernel[20], 4);
+ ss3 += KERNEL1x5(in_2, x * 4 + 3, &kernel[20], 4);
+ v = MAKE_UINT32(clip8(ss0), clip8(ss1), clip8(ss2), clip8(ss3));
+ memcpy(out + x * sizeof(v), &v, sizeof(v));
+ }
+ }
+ memcpy(
+ out + x * sizeof(UINT32), in0 + x * sizeof(UINT32), sizeof(UINT32) * 2);
+ }
+ }
+ memcpy(imOut->image[y], im->image[y], im->linesize);
+ memcpy(imOut->image[y + 1], im->image[y + 1], im->linesize);
+}
+
+Imaging
+ImagingFilter(Imaging im, int xsize, int ysize, const FLOAT32 *kernel, FLOAT32 offset) {
+ Imaging imOut;
+ ImagingSectionCookie cookie;
+
+ if (im->type != IMAGING_TYPE_UINT8 && im->type != IMAGING_TYPE_INT32) {
+ return (Imaging)ImagingError_ModeError();
+ }
+
+ if (im->xsize < xsize || im->ysize < ysize) {
+ return ImagingCopy(im);
+ }
+
+ if ((xsize != 3 && xsize != 5) || xsize != ysize) {
+ return (Imaging)ImagingError_ValueError("bad kernel size");
+ }
+
+ imOut = ImagingNewDirty(im->mode, im->xsize, im->ysize);
+ if (!imOut) {
+ return NULL;
+ }
+
+ ImagingSectionEnter(&cookie);
+ if (xsize == 3) {
+ /* 3x3 kernel. */
+ ImagingFilter3x3(imOut, im, kernel, offset);
+ } else {
+ /* 5x5 kernel. */
+ ImagingFilter5x5(imOut, im, kernel, offset);
+ }
+ ImagingSectionLeave(&cookie);
+ return imOut;
+}
diff --git a/contrib/python/Pillow/py3/libImaging/FliDecode.c b/contrib/python/Pillow/py3/libImaging/FliDecode.c
new file mode 100644
index 00000000000..d6e4ea0ff9d
--- /dev/null
+++ b/contrib/python/Pillow/py3/libImaging/FliDecode.c
@@ -0,0 +1,268 @@
+/*
+ * The Python Imaging Library.
+ * $Id$
+ *
+ * decoder for Autodesk Animator FLI/FLC animations
+ *
+ * history:
+ * 97-01-03 fl Created
+ * 97-01-17 fl Added SS2 support (FLC)
+ *
+ * Copyright (c) Fredrik Lundh 1997.
+ * Copyright (c) Secret Labs AB 1997.
+ *
+ * See the README file for information on usage and redistribution.
+ */
+
+#include "Imaging.h"
+
+#define I16(ptr) ((ptr)[0] + ((ptr)[1] << 8))
+
+#define I32(ptr) ((ptr)[0] + ((ptr)[1] << 8) + ((ptr)[2] << 16) + ((ptr)[3] << 24))
+
+#define ERR_IF_DATA_OOB(offset) \
+ if ((data + (offset)) > ptr + bytes) { \
+ state->errcode = IMAGING_CODEC_OVERRUN; \
+ return -1; \
+ }
+
+int
+ImagingFliDecode(Imaging im, ImagingCodecState state, UINT8 *buf, Py_ssize_t bytes) {
+ UINT8 *ptr;
+ int framesize;
+ int c, chunks, advance;
+ int l, lines;
+ int i, j, x = 0, y, ymax;
+
+ /* If not even the chunk size is present, we'd better leave */
+
+ if (bytes < 4) {
+ return 0;
+ }
+
+ /* We don't decode anything unless we have a full chunk in the
+ input buffer */
+
+ ptr = buf;
+
+ framesize = I32(ptr);
+ // there can be one pad byte in the framesize
+ if (bytes + (bytes % 2) < framesize) {
+ return 0;
+ }
+
+ /* Make sure this is a frame chunk. The Python driver takes
+ case of other chunk types. */
+
+ if (bytes < 8) {
+ state->errcode = IMAGING_CODEC_OVERRUN;
+ return -1;
+ }
+ if (I16(ptr + 4) != 0xF1FA) {
+ state->errcode = IMAGING_CODEC_UNKNOWN;
+ return -1;
+ }
+
+ chunks = I16(ptr + 6);
+ ptr += 16;
+ bytes -= 16;
+
+ /* Process subchunks */
+ for (c = 0; c < chunks; c++) {
+ UINT8 *data;
+ if (bytes < 10) {
+ state->errcode = IMAGING_CODEC_OVERRUN;
+ return -1;
+ }
+ data = ptr + 6;
+ switch (I16(ptr + 4)) {
+ case 4:
+ case 11:
+ /* FLI COLOR chunk */
+ break; /* ignored; handled by Python code */
+ case 7:
+ /* FLI SS2 chunk (word delta) */
+ /* OOB ok, we've got 4 bytes min on entry */
+ lines = I16(data);
+ data += 2;
+ for (l = y = 0; l < lines && y < state->ysize; l++, y++) {
+ UINT8 *local_buf = (UINT8 *)im->image[y];
+ int p, packets;
+ ERR_IF_DATA_OOB(2)
+ packets = I16(data);
+ data += 2;
+ while (packets & 0x8000) {
+ /* flag word */
+ if (packets & 0x4000) {
+ y += 65536 - packets; /* skip lines */
+ if (y >= state->ysize) {
+ state->errcode = IMAGING_CODEC_OVERRUN;
+ return -1;
+ }
+ local_buf = (UINT8 *)im->image[y];
+ } else {
+ /* store last byte (used if line width is odd) */
+ local_buf[state->xsize - 1] = (UINT8)packets;
+ }
+ ERR_IF_DATA_OOB(2)
+ packets = I16(data);
+ data += 2;
+ }
+ for (p = x = 0; p < packets; p++) {
+ ERR_IF_DATA_OOB(2)
+ x += data[0]; /* pixel skip */
+ if (data[1] >= 128) {
+ ERR_IF_DATA_OOB(4)
+ i = 256 - data[1]; /* run */
+ if (x + i + i > state->xsize) {
+ break;
+ }
+ for (j = 0; j < i; j++) {
+ local_buf[x++] = data[2];
+ local_buf[x++] = data[3];
+ }
+ data += 2 + 2;
+ } else {
+ i = 2 * (int)data[1]; /* chunk */
+ if (x + i > state->xsize) {
+ break;
+ }
+ ERR_IF_DATA_OOB(2 + i)
+ memcpy(local_buf + x, data + 2, i);
+ data += 2 + i;
+ x += i;
+ }
+ }
+ if (p < packets) {
+ break; /* didn't process all packets */
+ }
+ }
+ if (l < lines) {
+ /* didn't process all lines */
+ state->errcode = IMAGING_CODEC_OVERRUN;
+ return -1;
+ }
+ break;
+ case 12:
+ /* FLI LC chunk (byte delta) */
+ /* OOB Check ok, we have 4 bytes min here */
+ y = I16(data);
+ ymax = y + I16(data + 2);
+ data += 4;
+ for (; y < ymax && y < state->ysize; y++) {
+ UINT8 *out = (UINT8 *)im->image[y];
+ ERR_IF_DATA_OOB(1)
+ int p, packets = *data++;
+ for (p = x = 0; p < packets; p++, x += i) {
+ ERR_IF_DATA_OOB(2)
+ x += data[0]; /* skip pixels */
+ if (data[1] & 0x80) {
+ i = 256 - data[1]; /* run */
+ if (x + i > state->xsize) {
+ break;
+ }
+ ERR_IF_DATA_OOB(3)
+ memset(out + x, data[2], i);
+ data += 3;
+ } else {
+ i = data[1]; /* chunk */
+ if (x + i > state->xsize) {
+ break;
+ }
+ ERR_IF_DATA_OOB(2 + i)
+ memcpy(out + x, data + 2, i);
+ data += i + 2;
+ }
+ }
+ if (p < packets) {
+ break; /* didn't process all packets */
+ }
+ }
+ if (y < ymax) {
+ /* didn't process all lines */
+ state->errcode = IMAGING_CODEC_OVERRUN;
+ return -1;
+ }
+ break;
+ case 13:
+ /* FLI BLACK chunk */
+ for (y = 0; y < state->ysize; y++) {
+ memset(im->image[y], 0, state->xsize);
+ }
+ break;
+ case 15:
+ /* FLI BRUN chunk */
+ /* OOB, ok, we've got 4 bytes min on entry */
+ for (y = 0; y < state->ysize; y++) {
+ UINT8 *out = (UINT8 *)im->image[y];
+ data += 1; /* ignore packetcount byte */
+ for (x = 0; x < state->xsize; x += i) {
+ ERR_IF_DATA_OOB(2)
+ if (data[0] & 0x80) {
+ i = 256 - data[0];
+ if (x + i > state->xsize) {
+ break; /* safety first */
+ }
+ ERR_IF_DATA_OOB(i + 1)
+ memcpy(out + x, data + 1, i);
+ data += i + 1;
+ } else {
+ i = data[0];
+ if (x + i > state->xsize) {
+ break; /* safety first */
+ }
+ memset(out + x, data[1], i);
+ data += 2;
+ }
+ }
+ if (x != state->xsize) {
+ /* didn't unpack whole line */
+ state->errcode = IMAGING_CODEC_OVERRUN;
+ return -1;
+ }
+ }
+ break;
+ case 16:
+ /* COPY chunk */
+ if (INT32_MAX / state->xsize < state->ysize) {
+ /* Integer overflow, bail */
+ state->errcode = IMAGING_CODEC_OVERRUN;
+ return -1;
+ }
+ /* Note, have to check Data + size, not just ptr + size) */
+ if (data + (state->xsize * state->ysize) > ptr + bytes) {
+ /* not enough data for frame */
+ /* UNDONE Unclear that we're actually going to leave the buffer at the right place. */
+ return ptr - buf; /* bytes consumed */
+ }
+ for (y = 0; y < state->ysize; y++) {
+ UINT8 *local_buf = (UINT8 *)im->image[y];
+ memcpy(local_buf, data, state->xsize);
+ data += state->xsize;
+ }
+ break;
+ case 18:
+ /* PSTAMP chunk */
+ break; /* ignored */
+ default:
+ /* unknown chunk */
+ /* printf("unknown FLI/FLC chunk: %d\n", I16(ptr+4)); */
+ state->errcode = IMAGING_CODEC_UNKNOWN;
+ return -1;
+ }
+ advance = I32(ptr);
+ if (advance == 0 ) {
+ // If there's no advance, we're in an infinite loop
+ state->errcode = IMAGING_CODEC_BROKEN;
+ return -1;
+ }
+ if (advance < 0 || advance > bytes) {
+ state->errcode = IMAGING_CODEC_OVERRUN;
+ return -1;
+ }
+ ptr += advance;
+ bytes -= advance;
+ }
+
+ return -1; /* end of frame */
+}
diff --git a/contrib/python/Pillow/py3/libImaging/Geometry.c b/contrib/python/Pillow/py3/libImaging/Geometry.c
new file mode 100644
index 00000000000..0c591579217
--- /dev/null
+++ b/contrib/python/Pillow/py3/libImaging/Geometry.c
@@ -0,0 +1,1157 @@
+#include "Imaging.h"
+
+/* For large images rotation is an inefficient operation in terms of CPU cache.
+ One row in the source image affects each column in destination.
+ Rotating in chunks that fit in the cache can speed up rotation
+ 8x on a modern CPU. A chunk size of 128 requires only 65k and is large enough
+ that the overhead from the extra loops are not apparent. */
+#define ROTATE_CHUNK 512
+#define ROTATE_SMALL_CHUNK 8
+
+#define COORD(v) ((v) < 0.0 ? -1 : ((int)(v)))
+#define FLOOR(v) ((v) < 0.0 ? ((int)floor(v)) : ((int)(v)))
+
+/* -------------------------------------------------------------------- */
+/* Transpose operations */
+
+Imaging
+ImagingFlipLeftRight(Imaging imOut, Imaging imIn) {
+ ImagingSectionCookie cookie;
+ int x, y, xr;
+
+ if (!imOut || !imIn || strcmp(imIn->mode, imOut->mode) != 0) {
+ return (Imaging)ImagingError_ModeError();
+ }
+ if (imIn->xsize != imOut->xsize || imIn->ysize != imOut->ysize) {
+ return (Imaging)ImagingError_Mismatch();
+ }
+
+ ImagingCopyPalette(imOut, imIn);
+
+#define FLIP_LEFT_RIGHT(INT, image) \
+ for (y = 0; y < imIn->ysize; y++) { \
+ INT *in = (INT *)imIn->image[y]; \
+ INT *out = (INT *)imOut->image[y]; \
+ xr = imIn->xsize - 1; \
+ for (x = 0; x < imIn->xsize; x++, xr--) { \
+ out[xr] = in[x]; \
+ } \
+ }
+
+ ImagingSectionEnter(&cookie);
+
+ if (imIn->image8) {
+ if (strncmp(imIn->mode, "I;16", 4) == 0) {
+ FLIP_LEFT_RIGHT(UINT16, image8)
+ } else {
+ FLIP_LEFT_RIGHT(UINT8, image8)
+ }
+ } else {
+ FLIP_LEFT_RIGHT(INT32, image32)
+ }
+
+ ImagingSectionLeave(&cookie);
+
+#undef FLIP_LEFT_RIGHT
+
+ return imOut;
+}
+
+Imaging
+ImagingFlipTopBottom(Imaging imOut, Imaging imIn) {
+ ImagingSectionCookie cookie;
+ int y, yr;
+
+ if (!imOut || !imIn || strcmp(imIn->mode, imOut->mode) != 0) {
+ return (Imaging)ImagingError_ModeError();
+ }
+ if (imIn->xsize != imOut->xsize || imIn->ysize != imOut->ysize) {
+ return (Imaging)ImagingError_Mismatch();
+ }
+
+ ImagingCopyPalette(imOut, imIn);
+
+ ImagingSectionEnter(&cookie);
+
+ yr = imIn->ysize - 1;
+ for (y = 0; y < imIn->ysize; y++, yr--) {
+ memcpy(imOut->image[yr], imIn->image[y], imIn->linesize);
+ }
+
+ ImagingSectionLeave(&cookie);
+
+ return imOut;
+}
+
+Imaging
+ImagingRotate90(Imaging imOut, Imaging imIn) {
+ ImagingSectionCookie cookie;
+ int x, y, xx, yy, xr, xxsize, yysize;
+ int xxx, yyy, xxxsize, yyysize;
+
+ if (!imOut || !imIn || strcmp(imIn->mode, imOut->mode) != 0) {
+ return (Imaging)ImagingError_ModeError();
+ }
+ if (imIn->xsize != imOut->ysize || imIn->ysize != imOut->xsize) {
+ return (Imaging)ImagingError_Mismatch();
+ }
+
+ ImagingCopyPalette(imOut, imIn);
+
+#define ROTATE_90(INT, image) \
+ for (y = 0; y < imIn->ysize; y += ROTATE_CHUNK) { \
+ for (x = 0; x < imIn->xsize; x += ROTATE_CHUNK) { \
+ yysize = y + ROTATE_CHUNK < imIn->ysize ? y + ROTATE_CHUNK : imIn->ysize; \
+ xxsize = x + ROTATE_CHUNK < imIn->xsize ? x + ROTATE_CHUNK : imIn->xsize; \
+ for (yy = y; yy < yysize; yy += ROTATE_SMALL_CHUNK) { \
+ for (xx = x; xx < xxsize; xx += ROTATE_SMALL_CHUNK) { \
+ yyysize = yy + ROTATE_SMALL_CHUNK < imIn->ysize \
+ ? yy + ROTATE_SMALL_CHUNK \
+ : imIn->ysize; \
+ xxxsize = xx + ROTATE_SMALL_CHUNK < imIn->xsize \
+ ? xx + ROTATE_SMALL_CHUNK \
+ : imIn->xsize; \
+ for (yyy = yy; yyy < yyysize; yyy++) { \
+ INT *in = (INT *)imIn->image[yyy]; \
+ xr = imIn->xsize - 1 - xx; \
+ for (xxx = xx; xxx < xxxsize; xxx++, xr--) { \
+ INT *out = (INT *)imOut->image[xr]; \
+ out[yyy] = in[xxx]; \
+ } \
+ } \
+ } \
+ } \
+ } \
+ }
+
+ ImagingSectionEnter(&cookie);
+
+ if (imIn->image8) {
+ if (strncmp(imIn->mode, "I;16", 4) == 0) {
+ ROTATE_90(UINT16, image8);
+ } else {
+ ROTATE_90(UINT8, image8);
+ }
+ } else {
+ ROTATE_90(INT32, image32);
+ }
+
+ ImagingSectionLeave(&cookie);
+
+#undef ROTATE_90
+
+ return imOut;
+}
+
+Imaging
+ImagingTranspose(Imaging imOut, Imaging imIn) {
+ ImagingSectionCookie cookie;
+ int x, y, xx, yy, xxsize, yysize;
+ int xxx, yyy, xxxsize, yyysize;
+
+ if (!imOut || !imIn || strcmp(imIn->mode, imOut->mode) != 0) {
+ return (Imaging)ImagingError_ModeError();
+ }
+ if (imIn->xsize != imOut->ysize || imIn->ysize != imOut->xsize) {
+ return (Imaging)ImagingError_Mismatch();
+ }
+
+ ImagingCopyPalette(imOut, imIn);
+
+#define TRANSPOSE(INT, image) \
+ for (y = 0; y < imIn->ysize; y += ROTATE_CHUNK) { \
+ for (x = 0; x < imIn->xsize; x += ROTATE_CHUNK) { \
+ yysize = y + ROTATE_CHUNK < imIn->ysize ? y + ROTATE_CHUNK : imIn->ysize; \
+ xxsize = x + ROTATE_CHUNK < imIn->xsize ? x + ROTATE_CHUNK : imIn->xsize; \
+ for (yy = y; yy < yysize; yy += ROTATE_SMALL_CHUNK) { \
+ for (xx = x; xx < xxsize; xx += ROTATE_SMALL_CHUNK) { \
+ yyysize = yy + ROTATE_SMALL_CHUNK < imIn->ysize \
+ ? yy + ROTATE_SMALL_CHUNK \
+ : imIn->ysize; \
+ xxxsize = xx + ROTATE_SMALL_CHUNK < imIn->xsize \
+ ? xx + ROTATE_SMALL_CHUNK \
+ : imIn->xsize; \
+ for (yyy = yy; yyy < yyysize; yyy++) { \
+ INT *in = (INT *)imIn->image[yyy]; \
+ for (xxx = xx; xxx < xxxsize; xxx++) { \
+ INT *out = (INT *)imOut->image[xxx]; \
+ out[yyy] = in[xxx]; \
+ } \
+ } \
+ } \
+ } \
+ } \
+ }
+
+ ImagingSectionEnter(&cookie);
+
+ if (imIn->image8) {
+ if (strncmp(imIn->mode, "I;16", 4) == 0) {
+ TRANSPOSE(UINT16, image8);
+ } else {
+ TRANSPOSE(UINT8, image8);
+ }
+ } else {
+ TRANSPOSE(INT32, image32);
+ }
+
+ ImagingSectionLeave(&cookie);
+
+#undef TRANSPOSE
+
+ return imOut;
+}
+
+Imaging
+ImagingTransverse(Imaging imOut, Imaging imIn) {
+ ImagingSectionCookie cookie;
+ int x, y, xr, yr, xx, yy, xxsize, yysize;
+ int xxx, yyy, xxxsize, yyysize;
+
+ if (!imOut || !imIn || strcmp(imIn->mode, imOut->mode) != 0) {
+ return (Imaging)ImagingError_ModeError();
+ }
+ if (imIn->xsize != imOut->ysize || imIn->ysize != imOut->xsize) {
+ return (Imaging)ImagingError_Mismatch();
+ }
+
+ ImagingCopyPalette(imOut, imIn);
+
+#define TRANSVERSE(INT, image) \
+ for (y = 0; y < imIn->ysize; y += ROTATE_CHUNK) { \
+ for (x = 0; x < imIn->xsize; x += ROTATE_CHUNK) { \
+ yysize = y + ROTATE_CHUNK < imIn->ysize ? y + ROTATE_CHUNK : imIn->ysize; \
+ xxsize = x + ROTATE_CHUNK < imIn->xsize ? x + ROTATE_CHUNK : imIn->xsize; \
+ for (yy = y; yy < yysize; yy += ROTATE_SMALL_CHUNK) { \
+ for (xx = x; xx < xxsize; xx += ROTATE_SMALL_CHUNK) { \
+ yyysize = yy + ROTATE_SMALL_CHUNK < imIn->ysize \
+ ? yy + ROTATE_SMALL_CHUNK \
+ : imIn->ysize; \
+ xxxsize = xx + ROTATE_SMALL_CHUNK < imIn->xsize \
+ ? xx + ROTATE_SMALL_CHUNK \
+ : imIn->xsize; \
+ yr = imIn->ysize - 1 - yy; \
+ for (yyy = yy; yyy < yyysize; yyy++, yr--) { \
+ INT *in = (INT *)imIn->image[yyy]; \
+ xr = imIn->xsize - 1 - xx; \
+ for (xxx = xx; xxx < xxxsize; xxx++, xr--) { \
+ INT *out = (INT *)imOut->image[xr]; \
+ out[yr] = in[xxx]; \
+ } \
+ } \
+ } \
+ } \
+ } \
+ }
+
+ ImagingSectionEnter(&cookie);
+
+ if (imIn->image8) {
+ if (strncmp(imIn->mode, "I;16", 4) == 0) {
+ TRANSVERSE(UINT16, image8);
+ } else {
+ TRANSVERSE(UINT8, image8);
+ }
+ } else {
+ TRANSVERSE(INT32, image32);
+ }
+
+ ImagingSectionLeave(&cookie);
+
+#undef TRANSVERSE
+
+ return imOut;
+}
+
+Imaging
+ImagingRotate180(Imaging imOut, Imaging imIn) {
+ ImagingSectionCookie cookie;
+ int x, y, xr, yr;
+
+ if (!imOut || !imIn || strcmp(imIn->mode, imOut->mode) != 0) {
+ return (Imaging)ImagingError_ModeError();
+ }
+ if (imIn->xsize != imOut->xsize || imIn->ysize != imOut->ysize) {
+ return (Imaging)ImagingError_Mismatch();
+ }
+
+ ImagingCopyPalette(imOut, imIn);
+
+#define ROTATE_180(INT, image) \
+ for (y = 0; y < imIn->ysize; y++, yr--) { \
+ INT *in = (INT *)imIn->image[y]; \
+ INT *out = (INT *)imOut->image[yr]; \
+ xr = imIn->xsize - 1; \
+ for (x = 0; x < imIn->xsize; x++, xr--) { \
+ out[xr] = in[x]; \
+ } \
+ }
+
+ ImagingSectionEnter(&cookie);
+
+ yr = imIn->ysize - 1;
+ if (imIn->image8) {
+ if (strncmp(imIn->mode, "I;16", 4) == 0) {
+ ROTATE_180(UINT16, image8)
+ } else {
+ ROTATE_180(UINT8, image8)
+ }
+ } else {
+ ROTATE_180(INT32, image32)
+ }
+
+ ImagingSectionLeave(&cookie);
+
+#undef ROTATE_180
+
+ return imOut;
+}
+
+Imaging
+ImagingRotate270(Imaging imOut, Imaging imIn) {
+ ImagingSectionCookie cookie;
+ int x, y, xx, yy, yr, xxsize, yysize;
+ int xxx, yyy, xxxsize, yyysize;
+
+ if (!imOut || !imIn || strcmp(imIn->mode, imOut->mode) != 0) {
+ return (Imaging)ImagingError_ModeError();
+ }
+ if (imIn->xsize != imOut->ysize || imIn->ysize != imOut->xsize) {
+ return (Imaging)ImagingError_Mismatch();
+ }
+
+ ImagingCopyPalette(imOut, imIn);
+
+#define ROTATE_270(INT, image) \
+ for (y = 0; y < imIn->ysize; y += ROTATE_CHUNK) { \
+ for (x = 0; x < imIn->xsize; x += ROTATE_CHUNK) { \
+ yysize = y + ROTATE_CHUNK < imIn->ysize ? y + ROTATE_CHUNK : imIn->ysize; \
+ xxsize = x + ROTATE_CHUNK < imIn->xsize ? x + ROTATE_CHUNK : imIn->xsize; \
+ for (yy = y; yy < yysize; yy += ROTATE_SMALL_CHUNK) { \
+ for (xx = x; xx < xxsize; xx += ROTATE_SMALL_CHUNK) { \
+ yyysize = yy + ROTATE_SMALL_CHUNK < imIn->ysize \
+ ? yy + ROTATE_SMALL_CHUNK \
+ : imIn->ysize; \
+ xxxsize = xx + ROTATE_SMALL_CHUNK < imIn->xsize \
+ ? xx + ROTATE_SMALL_CHUNK \
+ : imIn->xsize; \
+ yr = imIn->ysize - 1 - yy; \
+ for (yyy = yy; yyy < yyysize; yyy++, yr--) { \
+ INT *in = (INT *)imIn->image[yyy]; \
+ for (xxx = xx; xxx < xxxsize; xxx++) { \
+ INT *out = (INT *)imOut->image[xxx]; \
+ out[yr] = in[xxx]; \
+ } \
+ } \
+ } \
+ } \
+ } \
+ }
+
+ ImagingSectionEnter(&cookie);
+
+ if (imIn->image8) {
+ if (strncmp(imIn->mode, "I;16", 4) == 0) {
+ ROTATE_270(UINT16, image8);
+ } else {
+ ROTATE_270(UINT8, image8);
+ }
+ } else {
+ ROTATE_270(INT32, image32);
+ }
+
+ ImagingSectionLeave(&cookie);
+
+#undef ROTATE_270
+
+ return imOut;
+}
+
+/* -------------------------------------------------------------------- */
+/* Transforms */
+
+/* transform primitives (ImagingTransformMap) */
+
+static int
+affine_transform(double *xout, double *yout, int x, int y, void *data) {
+ /* full moon tonight. your compiler will generate bogus code
+ for simple expressions, unless you reorganize the code, or
+ install Service Pack 3 */
+
+ double *a = (double *)data;
+ double a0 = a[0];
+ double a1 = a[1];
+ double a2 = a[2];
+ double a3 = a[3];
+ double a4 = a[4];
+ double a5 = a[5];
+
+ double xin = x + 0.5;
+ double yin = y + 0.5;
+
+ xout[0] = a0 * xin + a1 * yin + a2;
+ yout[0] = a3 * xin + a4 * yin + a5;
+
+ return 1;
+}
+
+static int
+perspective_transform(double *xout, double *yout, int x, int y, void *data) {
+ double *a = (double *)data;
+ double a0 = a[0];
+ double a1 = a[1];
+ double a2 = a[2];
+ double a3 = a[3];
+ double a4 = a[4];
+ double a5 = a[5];
+ double a6 = a[6];
+ double a7 = a[7];
+
+ double xin = x + 0.5;
+ double yin = y + 0.5;
+
+ xout[0] = (a0 * xin + a1 * yin + a2) / (a6 * xin + a7 * yin + 1);
+ yout[0] = (a3 * xin + a4 * yin + a5) / (a6 * xin + a7 * yin + 1);
+
+ return 1;
+}
+
+static int
+quad_transform(double *xout, double *yout, int x, int y, void *data) {
+ /* quad warp: map quadrilateral to rectangle */
+
+ double *a = (double *)data;
+ double a0 = a[0];
+ double a1 = a[1];
+ double a2 = a[2];
+ double a3 = a[3];
+ double a4 = a[4];
+ double a5 = a[5];
+ double a6 = a[6];
+ double a7 = a[7];
+
+ double xin = x + 0.5;
+ double yin = y + 0.5;
+
+ xout[0] = a0 + a1 * xin + a2 * yin + a3 * xin * yin;
+ yout[0] = a4 + a5 * xin + a6 * yin + a7 * xin * yin;
+
+ return 1;
+}
+
+/* transform filters (ImagingTransformFilter) */
+
+static int
+nearest_filter8(void *out, Imaging im, double xin, double yin) {
+ int x = COORD(xin);
+ int y = COORD(yin);
+ if (x < 0 || x >= im->xsize || y < 0 || y >= im->ysize) {
+ return 0;
+ }
+ ((UINT8 *)out)[0] = im->image8[y][x];
+ return 1;
+}
+
+static int
+nearest_filter16(void *out, Imaging im, double xin, double yin) {
+ int x = COORD(xin);
+ int y = COORD(yin);
+ if (x < 0 || x >= im->xsize || y < 0 || y >= im->ysize) {
+ return 0;
+ }
+ memcpy(out, im->image8[y] + x * sizeof(INT16), sizeof(INT16));
+ return 1;
+}
+
+static int
+nearest_filter32(void *out, Imaging im, double xin, double yin) {
+ int x = COORD(xin);
+ int y = COORD(yin);
+ if (x < 0 || x >= im->xsize || y < 0 || y >= im->ysize) {
+ return 0;
+ }
+ memcpy(out, &im->image32[y][x], sizeof(INT32));
+ return 1;
+}
+
+#define XCLIP(im, x) (((x) < 0) ? 0 : ((x) < im->xsize) ? (x) : im->xsize - 1)
+#define YCLIP(im, y) (((y) < 0) ? 0 : ((y) < im->ysize) ? (y) : im->ysize - 1)
+
+#define BILINEAR(v, a, b, d) (v = (a) + ((b) - (a)) * (d))
+
+#define BILINEAR_HEAD(type) \
+ int x, y; \
+ int x0, x1; \
+ double v1, v2; \
+ double dx, dy; \
+ type *in; \
+ if (xin < 0.0 || xin >= im->xsize || yin < 0.0 || yin >= im->ysize) { \
+ return 0; \
+ } \
+ xin -= 0.5; \
+ yin -= 0.5; \
+ x = FLOOR(xin); \
+ y = FLOOR(yin); \
+ dx = xin - x; \
+ dy = yin - y;
+
+#define BILINEAR_BODY(type, image, step, offset) \
+ { \
+ in = (type *)((image)[YCLIP(im, y)] + offset); \
+ x0 = XCLIP(im, x + 0) * step; \
+ x1 = XCLIP(im, x + 1) * step; \
+ BILINEAR(v1, in[x0], in[x1], dx); \
+ if (y + 1 >= 0 && y + 1 < im->ysize) { \
+ in = (type *)((image)[y + 1] + offset); \
+ BILINEAR(v2, in[x0], in[x1], dx); \
+ } else { \
+ v2 = v1; \
+ } \
+ BILINEAR(v1, v1, v2, dy); \
+ }
+
+static int
+bilinear_filter8(void *out, Imaging im, double xin, double yin) {
+ BILINEAR_HEAD(UINT8);
+ BILINEAR_BODY(UINT8, im->image8, 1, 0);
+ ((UINT8 *)out)[0] = (UINT8)v1;
+ return 1;
+}
+
+static int
+bilinear_filter32I(void *out, Imaging im, double xin, double yin) {
+ INT32 k;
+ BILINEAR_HEAD(INT32);
+ BILINEAR_BODY(INT32, im->image32, 1, 0);
+ k = v1;
+ memcpy(out, &k, sizeof(k));
+ return 1;
+}
+
+static int
+bilinear_filter32F(void *out, Imaging im, double xin, double yin) {
+ FLOAT32 k;
+ BILINEAR_HEAD(FLOAT32);
+ BILINEAR_BODY(FLOAT32, im->image32, 1, 0);
+ k = v1;
+ memcpy(out, &k, sizeof(k));
+ return 1;
+}
+
+static int
+bilinear_filter32LA(void *out, Imaging im, double xin, double yin) {
+ BILINEAR_HEAD(UINT8);
+ BILINEAR_BODY(UINT8, im->image, 4, 0);
+ ((UINT8 *)out)[0] = (UINT8)v1;
+ ((UINT8 *)out)[1] = (UINT8)v1;
+ ((UINT8 *)out)[2] = (UINT8)v1;
+ BILINEAR_BODY(UINT8, im->image, 4, 3);
+ ((UINT8 *)out)[3] = (UINT8)v1;
+ return 1;
+}
+
+static int
+bilinear_filter32RGB(void *out, Imaging im, double xin, double yin) {
+ int b;
+ BILINEAR_HEAD(UINT8);
+ for (b = 0; b < im->bands; b++) {
+ BILINEAR_BODY(UINT8, im->image, 4, b);
+ ((UINT8 *)out)[b] = (UINT8)v1;
+ }
+ return 1;
+}
+
+#undef BILINEAR
+#undef BILINEAR_HEAD
+#undef BILINEAR_BODY
+
+#define BICUBIC(v, v1, v2, v3, v4, d) \
+ { \
+ double p1 = v2; \
+ double p2 = -v1 + v3; \
+ double p3 = 2 * (v1 - v2) + v3 - v4; \
+ double p4 = -v1 + v2 - v3 + v4; \
+ v = p1 + (d) * (p2 + (d) * (p3 + (d)*p4)); \
+ }
+
+#define BICUBIC_HEAD(type) \
+ int x = FLOOR(xin); \
+ int y = FLOOR(yin); \
+ int x0, x1, x2, x3; \
+ double v1, v2, v3, v4; \
+ double dx, dy; \
+ type *in; \
+ if (xin < 0.0 || xin >= im->xsize || yin < 0.0 || yin >= im->ysize) { \
+ return 0; \
+ } \
+ xin -= 0.5; \
+ yin -= 0.5; \
+ x = FLOOR(xin); \
+ y = FLOOR(yin); \
+ dx = xin - x; \
+ dy = yin - y; \
+ x--; \
+ y--;
+
+#define BICUBIC_BODY(type, image, step, offset) \
+ { \
+ in = (type *)((image)[YCLIP(im, y)] + offset); \
+ x0 = XCLIP(im, x + 0) * step; \
+ x1 = XCLIP(im, x + 1) * step; \
+ x2 = XCLIP(im, x + 2) * step; \
+ x3 = XCLIP(im, x + 3) * step; \
+ BICUBIC(v1, in[x0], in[x1], in[x2], in[x3], dx); \
+ if (y + 1 >= 0 && y + 1 < im->ysize) { \
+ in = (type *)((image)[y + 1] + offset); \
+ BICUBIC(v2, in[x0], in[x1], in[x2], in[x3], dx); \
+ } else { \
+ v2 = v1; \
+ } \
+ if (y + 2 >= 0 && y + 2 < im->ysize) { \
+ in = (type *)((image)[y + 2] + offset); \
+ BICUBIC(v3, in[x0], in[x1], in[x2], in[x3], dx); \
+ } else { \
+ v3 = v2; \
+ } \
+ if (y + 3 >= 0 && y + 3 < im->ysize) { \
+ in = (type *)((image)[y + 3] + offset); \
+ BICUBIC(v4, in[x0], in[x1], in[x2], in[x3], dx); \
+ } else { \
+ v4 = v3; \
+ } \
+ BICUBIC(v1, v1, v2, v3, v4, dy); \
+ }
+
+static int
+bicubic_filter8(void *out, Imaging im, double xin, double yin) {
+ BICUBIC_HEAD(UINT8);
+ BICUBIC_BODY(UINT8, im->image8, 1, 0);
+ if (v1 <= 0.0) {
+ ((UINT8 *)out)[0] = 0;
+ } else if (v1 >= 255.0) {
+ ((UINT8 *)out)[0] = 255;
+ } else {
+ ((UINT8 *)out)[0] = (UINT8)v1;
+ }
+ return 1;
+}
+
+static int
+bicubic_filter32I(void *out, Imaging im, double xin, double yin) {
+ INT32 k;
+ BICUBIC_HEAD(INT32);
+ BICUBIC_BODY(INT32, im->image32, 1, 0);
+ k = v1;
+ memcpy(out, &k, sizeof(k));
+ return 1;
+}
+
+static int
+bicubic_filter32F(void *out, Imaging im, double xin, double yin) {
+ FLOAT32 k;
+ BICUBIC_HEAD(FLOAT32);
+ BICUBIC_BODY(FLOAT32, im->image32, 1, 0);
+ k = v1;
+ memcpy(out, &k, sizeof(k));
+ return 1;
+}
+
+static int
+bicubic_filter32LA(void *out, Imaging im, double xin, double yin) {
+ BICUBIC_HEAD(UINT8);
+ BICUBIC_BODY(UINT8, im->image, 4, 0);
+ if (v1 <= 0.0) {
+ ((UINT8 *)out)[0] = 0;
+ ((UINT8 *)out)[1] = 0;
+ ((UINT8 *)out)[2] = 0;
+ } else if (v1 >= 255.0) {
+ ((UINT8 *)out)[0] = 255;
+ ((UINT8 *)out)[1] = 255;
+ ((UINT8 *)out)[2] = 255;
+ } else {
+ ((UINT8 *)out)[0] = (UINT8)v1;
+ ((UINT8 *)out)[1] = (UINT8)v1;
+ ((UINT8 *)out)[2] = (UINT8)v1;
+ }
+ BICUBIC_BODY(UINT8, im->image, 4, 3);
+ if (v1 <= 0.0) {
+ ((UINT8 *)out)[3] = 0;
+ } else if (v1 >= 255.0) {
+ ((UINT8 *)out)[3] = 255;
+ } else {
+ ((UINT8 *)out)[3] = (UINT8)v1;
+ }
+ return 1;
+}
+
+static int
+bicubic_filter32RGB(void *out, Imaging im, double xin, double yin) {
+ int b;
+ BICUBIC_HEAD(UINT8);
+ for (b = 0; b < im->bands; b++) {
+ BICUBIC_BODY(UINT8, im->image, 4, b);
+ if (v1 <= 0.0) {
+ ((UINT8 *)out)[b] = 0;
+ } else if (v1 >= 255.0) {
+ ((UINT8 *)out)[b] = 255;
+ } else {
+ ((UINT8 *)out)[b] = (UINT8)v1;
+ }
+ }
+ return 1;
+}
+
+#undef BICUBIC
+#undef BICUBIC_HEAD
+#undef BICUBIC_BODY
+
+static ImagingTransformFilter
+getfilter(Imaging im, int filterid) {
+ switch (filterid) {
+ case IMAGING_TRANSFORM_NEAREST:
+ if (im->image8) {
+ switch (im->type) {
+ case IMAGING_TYPE_UINT8:
+ return nearest_filter8;
+ case IMAGING_TYPE_SPECIAL:
+ switch (im->pixelsize) {
+ case 1:
+ return nearest_filter8;
+ case 2:
+ return nearest_filter16;
+ case 4:
+ return nearest_filter32;
+ }
+ }
+ } else {
+ return nearest_filter32;
+ }
+ break;
+ case IMAGING_TRANSFORM_BILINEAR:
+ if (im->image8) {
+ return bilinear_filter8;
+ } else if (im->image32) {
+ switch (im->type) {
+ case IMAGING_TYPE_UINT8:
+ if (im->bands == 2) {
+ return bilinear_filter32LA;
+ } else {
+ return bilinear_filter32RGB;
+ }
+ case IMAGING_TYPE_INT32:
+ return bilinear_filter32I;
+ case IMAGING_TYPE_FLOAT32:
+ return bilinear_filter32F;
+ }
+ }
+ break;
+ case IMAGING_TRANSFORM_BICUBIC:
+ if (im->image8) {
+ return bicubic_filter8;
+ } else if (im->image32) {
+ switch (im->type) {
+ case IMAGING_TYPE_UINT8:
+ if (im->bands == 2) {
+ return bicubic_filter32LA;
+ } else {
+ return bicubic_filter32RGB;
+ }
+ case IMAGING_TYPE_INT32:
+ return bicubic_filter32I;
+ case IMAGING_TYPE_FLOAT32:
+ return bicubic_filter32F;
+ }
+ }
+ break;
+ }
+ /* no such filter */
+ return NULL;
+}
+
+/* transformation engines */
+
+Imaging
+ImagingGenericTransform(
+ Imaging imOut,
+ Imaging imIn,
+ int x0,
+ int y0,
+ int x1,
+ int y1,
+ ImagingTransformMap transform,
+ void *transform_data,
+ int filterid,
+ int fill) {
+ /* slow generic transformation. use ImagingTransformAffine or
+ ImagingScaleAffine where possible. */
+
+ ImagingSectionCookie cookie;
+ int x, y;
+ char *out;
+ double xx, yy;
+
+ ImagingTransformFilter filter = getfilter(imIn, filterid);
+ if (!filter) {
+ return (Imaging)ImagingError_ValueError("bad filter number");
+ }
+
+ if (!imOut || !imIn || strcmp(imIn->mode, imOut->mode) != 0) {
+ return (Imaging)ImagingError_ModeError();
+ }
+
+ ImagingCopyPalette(imOut, imIn);
+
+ ImagingSectionEnter(&cookie);
+
+ if (x0 < 0) {
+ x0 = 0;
+ }
+ if (y0 < 0) {
+ y0 = 0;
+ }
+ if (x1 > imOut->xsize) {
+ x1 = imOut->xsize;
+ }
+ if (y1 > imOut->ysize) {
+ y1 = imOut->ysize;
+ }
+
+ for (y = y0; y < y1; y++) {
+ out = imOut->image[y] + x0 * imOut->pixelsize;
+ for (x = x0; x < x1; x++) {
+ if (!transform(&xx, &yy, x - x0, y - y0, transform_data) ||
+ !filter(out, imIn, xx, yy)) {
+ if (fill) {
+ memset(out, 0, imOut->pixelsize);
+ }
+ }
+ out += imOut->pixelsize;
+ }
+ }
+
+ ImagingSectionLeave(&cookie);
+
+ return imOut;
+}
+
+static Imaging
+ImagingScaleAffine(
+ Imaging imOut,
+ Imaging imIn,
+ int x0,
+ int y0,
+ int x1,
+ int y1,
+ double a[6],
+ int fill) {
+ /* scale, nearest neighbour resampling */
+
+ ImagingSectionCookie cookie;
+ int x, y;
+ int xin;
+ double xo, yo;
+ int xmin, xmax;
+ int *xintab;
+
+ if (!imOut || !imIn || strcmp(imIn->mode, imOut->mode) != 0) {
+ return (Imaging)ImagingError_ModeError();
+ }
+
+ ImagingCopyPalette(imOut, imIn);
+
+ if (x0 < 0) {
+ x0 = 0;
+ }
+ if (y0 < 0) {
+ y0 = 0;
+ }
+ if (x1 > imOut->xsize) {
+ x1 = imOut->xsize;
+ }
+ if (y1 > imOut->ysize) {
+ y1 = imOut->ysize;
+ }
+
+ /* malloc check ok, uses calloc for overflow */
+ xintab = (int *)calloc(imOut->xsize, sizeof(int));
+ if (!xintab) {
+ ImagingDelete(imOut);
+ return (Imaging)ImagingError_MemoryError();
+ }
+
+ xo = a[2] + a[0] * 0.5;
+ yo = a[5] + a[4] * 0.5;
+
+ xmin = x1;
+ xmax = x0;
+
+ /* Pretabulate horizontal pixel positions */
+ for (x = x0; x < x1; x++) {
+ xin = COORD(xo);
+ if (xin >= 0 && xin < (int)imIn->xsize) {
+ xmax = x + 1;
+ if (x < xmin) {
+ xmin = x;
+ }
+ xintab[x] = xin;
+ }
+ xo += a[0];
+ }
+
+#define AFFINE_SCALE(pixel, image) \
+ for (y = y0; y < y1; y++) { \
+ int yi = COORD(yo); \
+ pixel *in, *out; \
+ out = imOut->image[y]; \
+ if (fill && x1 > x0) { \
+ memset(out + x0, 0, (x1 - x0) * sizeof(pixel)); \
+ } \
+ if (yi >= 0 && yi < imIn->ysize) { \
+ in = imIn->image[yi]; \
+ for (x = xmin; x < xmax; x++) { \
+ out[x] = in[xintab[x]]; \
+ } \
+ } \
+ yo += a[4]; \
+ }
+
+ ImagingSectionEnter(&cookie);
+
+ if (imIn->image8) {
+ AFFINE_SCALE(UINT8, image8);
+ } else {
+ AFFINE_SCALE(INT32, image32);
+ }
+
+ ImagingSectionLeave(&cookie);
+
+#undef AFFINE_SCALE
+
+ free(xintab);
+
+ return imOut;
+}
+
+static inline int
+check_fixed(double a[6], int x, int y) {
+ return (
+ fabs(x * a[0] + y * a[1] + a[2]) < 32768.0 &&
+ fabs(x * a[3] + y * a[4] + a[5]) < 32768.0);
+}
+
+static inline Imaging
+affine_fixed(
+ Imaging imOut,
+ Imaging imIn,
+ int x0,
+ int y0,
+ int x1,
+ int y1,
+ double a[6],
+ int filterid,
+ int fill) {
+ /* affine transform, nearest neighbour resampling, fixed point
+ arithmetics */
+
+ ImagingSectionCookie cookie;
+ int x, y;
+ int xin, yin;
+ int xsize, ysize;
+ int xx, yy;
+ int a0, a1, a2, a3, a4, a5;
+
+ ImagingCopyPalette(imOut, imIn);
+
+ xsize = (int)imIn->xsize;
+ ysize = (int)imIn->ysize;
+
+/* use 16.16 fixed point arithmetics */
+#define FIX(v) FLOOR((v)*65536.0 + 0.5)
+
+ a0 = FIX(a[0]);
+ a1 = FIX(a[1]);
+ a3 = FIX(a[3]);
+ a4 = FIX(a[4]);
+ a2 = FIX(a[2] + a[0] * 0.5 + a[1] * 0.5);
+ a5 = FIX(a[5] + a[3] * 0.5 + a[4] * 0.5);
+
+#undef FIX
+
+#define AFFINE_TRANSFORM_FIXED(pixel, image) \
+ for (y = y0; y < y1; y++) { \
+ pixel *out; \
+ xx = a2; \
+ yy = a5; \
+ out = imOut->image[y]; \
+ if (fill && x1 > x0) { \
+ memset(out + x0, 0, (x1 - x0) * sizeof(pixel)); \
+ } \
+ for (x = x0; x < x1; x++, out++) { \
+ xin = xx >> 16; \
+ if (xin >= 0 && xin < xsize) { \
+ yin = yy >> 16; \
+ if (yin >= 0 && yin < ysize) { \
+ *out = imIn->image[yin][xin]; \
+ } \
+ } \
+ xx += a0; \
+ yy += a3; \
+ } \
+ a2 += a1; \
+ a5 += a4; \
+ }
+
+ ImagingSectionEnter(&cookie);
+
+ if (imIn->image8) {
+ AFFINE_TRANSFORM_FIXED(UINT8, image8)
+ } else {
+ AFFINE_TRANSFORM_FIXED(INT32, image32)
+ }
+
+ ImagingSectionLeave(&cookie);
+
+#undef AFFINE_TRANSFORM_FIXED
+
+ return imOut;
+}
+
+Imaging
+ImagingTransformAffine(
+ Imaging imOut,
+ Imaging imIn,
+ int x0,
+ int y0,
+ int x1,
+ int y1,
+ double a[6],
+ int filterid,
+ int fill) {
+ /* affine transform, nearest neighbour resampling, floating point
+ arithmetics*/
+
+ ImagingSectionCookie cookie;
+ int x, y;
+ int xin, yin;
+ int xsize, ysize;
+ double xx, yy;
+ double xo, yo;
+
+ if (filterid || imIn->type == IMAGING_TYPE_SPECIAL) {
+ return ImagingGenericTransform(
+ imOut, imIn, x0, y0, x1, y1, affine_transform, a, filterid, fill);
+ }
+
+ if (a[1] == 0 && a[3] == 0) {
+ /* Scaling */
+ return ImagingScaleAffine(imOut, imIn, x0, y0, x1, y1, a, fill);
+ }
+
+ if (!imOut || !imIn || strcmp(imIn->mode, imOut->mode) != 0) {
+ return (Imaging)ImagingError_ModeError();
+ }
+
+ if (x0 < 0) {
+ x0 = 0;
+ }
+ if (y0 < 0) {
+ y0 = 0;
+ }
+ if (x1 > imOut->xsize) {
+ x1 = imOut->xsize;
+ }
+ if (y1 > imOut->ysize) {
+ y1 = imOut->ysize;
+ }
+
+ /* translate all four corners to check if they are within the
+ range that can be represented by the fixed point arithmetics */
+
+ if (check_fixed(a, 0, 0) && check_fixed(a, x1 - x0, y1 - y0) &&
+ check_fixed(a, 0, y1 - y0) && check_fixed(a, x1 - x0, 0)) {
+ return affine_fixed(imOut, imIn, x0, y0, x1, y1, a, filterid, fill);
+ }
+
+ /* FIXME: cannot really think of any reasonable case when the
+ following code is used. maybe we should fall back on the slow
+ generic transform engine in this case? */
+
+ ImagingCopyPalette(imOut, imIn);
+
+ xsize = (int)imIn->xsize;
+ ysize = (int)imIn->ysize;
+
+ xo = a[2] + a[1] * 0.5 + a[0] * 0.5;
+ yo = a[5] + a[4] * 0.5 + a[3] * 0.5;
+
+#define AFFINE_TRANSFORM(pixel, image) \
+ for (y = y0; y < y1; y++) { \
+ pixel *out; \
+ xx = xo; \
+ yy = yo; \
+ out = imOut->image[y]; \
+ if (fill && x1 > x0) { \
+ memset(out + x0, 0, (x1 - x0) * sizeof(pixel)); \
+ } \
+ for (x = x0; x < x1; x++, out++) { \
+ xin = COORD(xx); \
+ if (xin >= 0 && xin < xsize) { \
+ yin = COORD(yy); \
+ if (yin >= 0 && yin < ysize) { \
+ *out = imIn->image[yin][xin]; \
+ } \
+ } \
+ xx += a[0]; \
+ yy += a[3]; \
+ } \
+ xo += a[1]; \
+ yo += a[4]; \
+ }
+
+ ImagingSectionEnter(&cookie);
+
+ if (imIn->image8) {
+ AFFINE_TRANSFORM(UINT8, image8)
+ } else {
+ AFFINE_TRANSFORM(INT32, image32)
+ }
+
+ ImagingSectionLeave(&cookie);
+
+#undef AFFINE_TRANSFORM
+
+ return imOut;
+}
+
+Imaging
+ImagingTransform(
+ Imaging imOut,
+ Imaging imIn,
+ int method,
+ int x0,
+ int y0,
+ int x1,
+ int y1,
+ double a[8],
+ int filterid,
+ int fill) {
+ ImagingTransformMap transform;
+
+ switch (method) {
+ case IMAGING_TRANSFORM_AFFINE:
+ return ImagingTransformAffine(
+ imOut, imIn, x0, y0, x1, y1, a, filterid, fill);
+ break;
+ case IMAGING_TRANSFORM_PERSPECTIVE:
+ transform = perspective_transform;
+ break;
+ case IMAGING_TRANSFORM_QUAD:
+ transform = quad_transform;
+ break;
+ default:
+ return (Imaging)ImagingError_ValueError("bad transform method");
+ }
+
+ return ImagingGenericTransform(
+ imOut, imIn, x0, y0, x1, y1, transform, a, filterid, fill);
+}
diff --git a/contrib/python/Pillow/py3/libImaging/GetBBox.c b/contrib/python/Pillow/py3/libImaging/GetBBox.c
new file mode 100644
index 00000000000..86c687ca0a8
--- /dev/null
+++ b/contrib/python/Pillow/py3/libImaging/GetBBox.c
@@ -0,0 +1,356 @@
+/*
+ * The Python Imaging Library
+ * $Id$
+ *
+ * helpers to bounding boxes, min/max values, number of colors, etc.
+ *
+ * history:
+ * 1996-07-22 fl Created
+ * 1996-12-30 fl Added projection stuff
+ * 1998-07-12 fl Added extrema stuff
+ * 2004-09-17 fl Added colors stuff
+ *
+ * Copyright (c) 1997-2004 by Secret Labs AB.
+ * Copyright (c) 1996-2004 by Fredrik Lundh.
+ *
+ * See the README file for details on usage and redistribution.
+ */
+
+#include "Imaging.h"
+
+int
+ImagingGetBBox(Imaging im, int bbox[4], int alpha_only) {
+ /* Get the bounding box for any non-zero data in the image.*/
+
+ int x, y;
+ int has_data;
+
+ /* Initialize bounding box to max values */
+ bbox[0] = im->xsize;
+ bbox[1] = -1;
+ bbox[2] = bbox[3] = 0;
+
+#define GETBBOX(image, mask) \
+ for (y = 0; y < im->ysize; y++) { \
+ has_data = 0; \
+ for (x = 0; x < im->xsize; x++) { \
+ if (im->image[y][x] & mask) { \
+ has_data = 1; \
+ if (x < bbox[0]) { \
+ bbox[0] = x; \
+ } \
+ if (x >= bbox[2]) { \
+ bbox[2] = x + 1; \
+ } \
+ } \
+ } \
+ if (has_data) { \
+ if (bbox[1] < 0) { \
+ bbox[1] = y; \
+ } \
+ bbox[3] = y + 1; \
+ } \
+ }
+
+ if (im->image8) {
+ GETBBOX(image8, 0xff);
+ } else {
+ INT32 mask = 0xffffffff;
+ if (im->bands == 3) {
+ ((UINT8 *)&mask)[3] = 0;
+ } else if (alpha_only && (
+ strcmp(im->mode, "RGBa") == 0 || strcmp(im->mode, "RGBA") == 0 ||
+ strcmp(im->mode, "La") == 0 || strcmp(im->mode, "LA") == 0 ||
+ strcmp(im->mode, "PA") == 0
+ )) {
+#ifdef WORDS_BIGENDIAN
+ mask = 0x000000ff;
+#else
+ mask = 0xff000000;
+#endif
+ }
+ GETBBOX(image32, mask);
+ }
+
+ /* Check that we got a box */
+ if (bbox[1] < 0) {
+ return 0; /* no data */
+ }
+
+ return 1; /* ok */
+}
+
+int
+ImagingGetProjection(Imaging im, UINT8 *xproj, UINT8 *yproj) {
+ /* Get projection arrays for non-zero data in the image.*/
+
+ int x, y;
+ int has_data;
+
+ /* Initialize projection arrays */
+ memset(xproj, 0, im->xsize);
+ memset(yproj, 0, im->ysize);
+
+#define GETPROJ(image, mask) \
+ for (y = 0; y < im->ysize; y++) { \
+ has_data = 0; \
+ for (x = 0; x < im->xsize; x++) { \
+ if (im->image[y][x] & mask) { \
+ has_data = 1; \
+ xproj[x] = 1; \
+ } \
+ } \
+ if (has_data) { \
+ yproj[y] = 1; \
+ } \
+ }
+
+ if (im->image8) {
+ GETPROJ(image8, 0xff);
+ } else {
+ INT32 mask = 0xffffffff;
+ if (im->bands == 3) {
+ ((UINT8 *)&mask)[3] = 0;
+ }
+ GETPROJ(image32, mask);
+ }
+
+ return 1; /* ok */
+}
+
+int
+ImagingGetExtrema(Imaging im, void *extrema) {
+ int x, y;
+ INT32 imin, imax;
+ FLOAT32 fmin, fmax;
+
+ if (im->bands != 1) {
+ (void)ImagingError_ModeError();
+ return -1; /* mismatch */
+ }
+
+ if (!im->xsize || !im->ysize) {
+ return 0; /* zero size */
+ }
+
+ switch (im->type) {
+ case IMAGING_TYPE_UINT8:
+ imin = imax = im->image8[0][0];
+ for (y = 0; y < im->ysize; y++) {
+ UINT8 *in = im->image8[y];
+ for (x = 0; x < im->xsize; x++) {
+ if (imin > in[x]) {
+ imin = in[x];
+ } else if (imax < in[x]) {
+ imax = in[x];
+ }
+ }
+ }
+ ((UINT8 *)extrema)[0] = (UINT8)imin;
+ ((UINT8 *)extrema)[1] = (UINT8)imax;
+ break;
+ case IMAGING_TYPE_INT32:
+ imin = imax = im->image32[0][0];
+ for (y = 0; y < im->ysize; y++) {
+ INT32 *in = im->image32[y];
+ for (x = 0; x < im->xsize; x++) {
+ if (imin > in[x]) {
+ imin = in[x];
+ } else if (imax < in[x]) {
+ imax = in[x];
+ }
+ }
+ }
+ memcpy(extrema, &imin, sizeof(imin));
+ memcpy(((char *)extrema) + sizeof(imin), &imax, sizeof(imax));
+ break;
+ case IMAGING_TYPE_FLOAT32:
+ fmin = fmax = ((FLOAT32 *)im->image32[0])[0];
+ for (y = 0; y < im->ysize; y++) {
+ FLOAT32 *in = (FLOAT32 *)im->image32[y];
+ for (x = 0; x < im->xsize; x++) {
+ if (fmin > in[x]) {
+ fmin = in[x];
+ } else if (fmax < in[x]) {
+ fmax = in[x];
+ }
+ }
+ }
+ memcpy(extrema, &fmin, sizeof(fmin));
+ memcpy(((char *)extrema) + sizeof(fmin), &fmax, sizeof(fmax));
+ break;
+ case IMAGING_TYPE_SPECIAL:
+ if (strcmp(im->mode, "I;16") == 0) {
+ UINT16 v;
+ UINT8 *pixel = *im->image8;
+#ifdef WORDS_BIGENDIAN
+ v = pixel[0] + (pixel[1] << 8);
+#else
+ memcpy(&v, pixel, sizeof(v));
+#endif
+ imin = imax = v;
+ for (y = 0; y < im->ysize; y++) {
+ for (x = 0; x < im->xsize; x++) {
+ pixel = (UINT8 *)im->image[y] + x * sizeof(v);
+#ifdef WORDS_BIGENDIAN
+ v = pixel[0] + (pixel[1] << 8);
+#else
+ memcpy(&v, pixel, sizeof(v));
+#endif
+ if (imin > v) {
+ imin = v;
+ } else if (imax < v) {
+ imax = v;
+ }
+ }
+ }
+ v = (UINT16)imin;
+ memcpy(extrema, &v, sizeof(v));
+ v = (UINT16)imax;
+ memcpy(((char *)extrema) + sizeof(v), &v, sizeof(v));
+ break;
+ }
+ /* FALL THROUGH */
+ default:
+ (void)ImagingError_ModeError();
+ return -1;
+ }
+ return 1; /* ok */
+}
+
+/* static ImagingColorItem* getcolors8(Imaging im, int maxcolors, int* size);*/
+static ImagingColorItem *
+getcolors32(Imaging im, int maxcolors, int *size);
+
+ImagingColorItem *
+ImagingGetColors(Imaging im, int maxcolors, int *size) {
+ /* FIXME: add support for 8-bit images */
+ return getcolors32(im, maxcolors, size);
+}
+
+static ImagingColorItem *
+getcolors32(Imaging im, int maxcolors, int *size) {
+ unsigned int h;
+ unsigned int i, incr;
+ int colors;
+ INT32 pixel_mask;
+ int x, y;
+ ImagingColorItem *table;
+ ImagingColorItem *v;
+
+ unsigned int code_size;
+ unsigned int code_poly;
+ unsigned int code_mask;
+
+ /* note: the hash algorithm used here is based on the dictionary
+ code in Python 2.1.3; the exact implementation is borrowed from
+ Python's Unicode property database (written by yours truly) /F */
+
+ static int SIZES[] = {
+ 4, 3, 8, 3, 16, 3, 32, 5, 64, 3,
+ 128, 3, 256, 29, 512, 17, 1024, 9, 2048, 5,
+ 4096, 83, 8192, 27, 16384, 43, 32768, 3, 65536, 45,
+ 131072, 9, 262144, 39, 524288, 39, 1048576, 9, 2097152, 5,
+ 4194304, 3, 8388608, 33, 16777216, 27, 33554432, 9, 67108864, 71,
+ 134217728, 39, 268435456, 9, 536870912, 5, 1073741824, 83, 0};
+
+ code_size = code_poly = code_mask = 0;
+
+ for (i = 0; SIZES[i]; i += 2) {
+ if (SIZES[i] > maxcolors) {
+ code_size = SIZES[i];
+ code_poly = SIZES[i + 1];
+ code_mask = code_size - 1;
+ break;
+ }
+ }
+
+ /* printf("code_size=%d\n", code_size); */
+ /* printf("code_poly=%d\n", code_poly); */
+
+ if (!code_size) {
+ return ImagingError_MemoryError(); /* just give up */
+ }
+
+ if (!im->image32) {
+ return ImagingError_ModeError();
+ }
+
+ table = calloc(code_size + 1, sizeof(ImagingColorItem));
+ if (!table) {
+ return ImagingError_MemoryError();
+ }
+
+ pixel_mask = 0xffffffff;
+ if (im->bands == 3) {
+ ((UINT8 *)&pixel_mask)[3] = 0;
+ }
+
+ colors = 0;
+
+ for (y = 0; y < im->ysize; y++) {
+ INT32 *p = im->image32[y];
+ for (x = 0; x < im->xsize; x++) {
+ INT32 pixel = p[x] & pixel_mask;
+ h = (pixel); /* null hashing */
+ i = (~h) & code_mask;
+ v = &table[i];
+ if (!v->count) {
+ /* add to table */
+ if (colors++ == maxcolors) {
+ goto overflow;
+ }
+ v->x = x;
+ v->y = y;
+ v->pixel = pixel;
+ v->count = 1;
+ continue;
+ } else if (v->pixel == pixel) {
+ v->count++;
+ continue;
+ }
+ incr = (h ^ (h >> 3)) & code_mask;
+ if (!incr) {
+ incr = code_mask;
+ }
+ for (;;) {
+ i = (i + incr) & code_mask;
+ v = &table[i];
+ if (!v->count) {
+ /* add to table */
+ if (colors++ == maxcolors) {
+ goto overflow;
+ }
+ v->x = x;
+ v->y = y;
+ v->pixel = pixel;
+ v->count = 1;
+ break;
+ } else if (v->pixel == pixel) {
+ v->count++;
+ break;
+ }
+ incr = incr << 1;
+ if (incr > code_mask) {
+ incr = incr ^ code_poly;
+ }
+ }
+ }
+ }
+
+overflow:
+
+ /* pack the table */
+ for (x = y = 0; x < (int)code_size; x++)
+ if (table[x].count) {
+ if (x != y) {
+ table[y] = table[x];
+ }
+ y++;
+ }
+ table[y].count = 0; /* mark end of table */
+
+ *size = colors;
+
+ return table;
+}
diff --git a/contrib/python/Pillow/py3/libImaging/Gif.h b/contrib/python/Pillow/py3/libImaging/Gif.h
new file mode 100644
index 00000000000..5d7e2bdaa96
--- /dev/null
+++ b/contrib/python/Pillow/py3/libImaging/Gif.h
@@ -0,0 +1,100 @@
+/*
+ * The Python Imaging Library.
+ * $Id$
+ *
+ * Declarations for a fast, suspendable GIF decoder.
+ *
+ * Copyright (c) Fredrik Lundh 1995-96.
+ */
+
+/* Max size for a LZW code word. */
+
+#define GIFBITS 12
+
+#define GIFTABLE (1<<GIFBITS)
+#define GIFBUFFER (1<<GIFBITS)
+
+typedef struct {
+ /* CONFIGURATION */
+
+ /* Initial number of bits. The caller should clear all fields in
+ this structure and set this field before calling the decoder
+ the first time. */
+ int bits;
+
+ /* If set, this is an interlaced image. Process it the following way:
+ * 1st pass: start at top line, lines are 8 pixels high, step 8 pixels
+ * 2nd pass: start at line 4, lines are 4 pixels high, step 8 pixels
+ * 3rd pass: start at line 2, lines are 2 pixels high, step 4 pixels
+ * 4th pass: start at line 1, lines are 1 pixels high, step 2 pixels
+ */
+ int interlace;
+
+ /* The transparent palette index, or -1 for no transparency */
+ int transparency;
+
+ /* PRIVATE CONTEXT (set by decoder) */
+
+ /* Interlace parameters */
+ int step, repeat;
+
+ /* Input bit buffer */
+ INT32 bitbuffer;
+ int bitcount;
+ int blocksize;
+
+ /* Code buffer */
+ int codesize;
+ int codemask;
+
+ /* Constant symbol codes */
+ int clear, end;
+
+ /* Symbol history */
+ int lastcode;
+ unsigned char lastdata;
+
+ /* History buffer */
+ int bufferindex;
+ unsigned char buffer[GIFTABLE];
+
+ /* Symbol table */
+ UINT16 link[GIFTABLE];
+ unsigned char data[GIFTABLE];
+ int next;
+
+} GIFDECODERSTATE;
+
+/* For GIF LZW encoder. */
+#define TABLE_SIZE 8192
+
+typedef struct {
+ /* CONFIGURATION */
+
+ /* Initial number of bits. The caller should clear all fields in
+ this structure and set this field before calling the encoder
+ the first time. */
+ int bits;
+
+ /* NOTE: the expanding encoder ignores this field */
+
+ /* If set, write an interlaced image (see above) */
+ int interlace;
+
+ /* PRIVATE CONTEXT (set by encoder) */
+
+ /* Interlace parameters */
+ int step;
+
+ /* For GIF LZW encoder. */
+ UINT32 put_state;
+ UINT32 entry_state;
+ UINT32 clear_code, end_code, next_code, max_code;
+ UINT32 code_width, code_bits_left, buf_bits_left;
+ UINT32 code_buffer;
+ UINT32 head, tail;
+ int probe;
+ UINT32 code;
+ UINT32 codes[TABLE_SIZE];
+
+} GIFENCODERSTATE;
diff --git a/contrib/python/Pillow/py3/libImaging/GifDecode.c b/contrib/python/Pillow/py3/libImaging/GifDecode.c
new file mode 100644
index 00000000000..92b2607b4d9
--- /dev/null
+++ b/contrib/python/Pillow/py3/libImaging/GifDecode.c
@@ -0,0 +1,285 @@
+/*
+ * The Python Imaging Library.
+ * $Id$
+ *
+ * a fast, suspendable GIF decoder
+ *
+ * history:
+ * 95-09-03 fl Created
+ * 95-09-05 fl Fixed sign problem on 16-bit platforms
+ * 95-09-13 fl Added some storage shortcuts
+ * 96-03-28 fl Revised API, integrated with PIL
+ * 96-12-10 fl Added interlace support
+ * 96-12-16 fl Fixed premature termination bug introduced by last fix
+ * 97-01-05 fl Don't mess up on bogus configuration
+ * 97-01-17 fl Don't mess up on very small, interlaced files
+ * 99-02-07 fl Minor speedups
+ *
+ * Copyright (c) Secret Labs AB 1997-99.
+ * Copyright (c) Fredrik Lundh 1995-97.
+ *
+ * See the README file for information on usage and redistribution.
+ */
+
+#include "Imaging.h"
+
+#include <stdio.h>
+#include <memory.h> /* memcpy() */
+
+#include "Gif.h"
+
+#define NEWLINE(state, context) \
+ { \
+ state->x = 0; \
+ state->y += context->step; \
+ while (state->y >= state->ysize) switch (context->interlace) { \
+ case 1: \
+ context->repeat = state->y = 4; \
+ context->interlace = 2; \
+ break; \
+ case 2: \
+ context->step = 4; \
+ context->repeat = state->y = 2; \
+ context->interlace = 3; \
+ break; \
+ case 3: \
+ context->step = 2; \
+ context->repeat = state->y = 1; \
+ context->interlace = 0; \
+ break; \
+ default: \
+ return -1; \
+ } \
+ if (state->y < state->ysize) { \
+ out = im->image8[state->y + state->yoff] + state->xoff; \
+ } \
+ }
+
+int
+ImagingGifDecode(Imaging im, ImagingCodecState state, UINT8 *buffer, Py_ssize_t bytes) {
+ UINT8 *p;
+ UINT8 *out;
+ int c, i;
+ int thiscode;
+ GIFDECODERSTATE *context = (GIFDECODERSTATE *)state->context;
+
+ UINT8 *ptr = buffer;
+
+ if (!state->state) {
+ /* Initialise state */
+ if (context->bits < 0 || context->bits > 12) {
+ state->errcode = IMAGING_CODEC_CONFIG;
+ return -1;
+ }
+
+ /* Clear code */
+ context->clear = 1 << context->bits;
+
+ /* End code */
+ context->end = context->clear + 1;
+
+ /* Interlace */
+ if (context->interlace) {
+ context->interlace = 1;
+ context->step = context->repeat = 8;
+ } else {
+ context->step = 1;
+ }
+
+ state->state = 1;
+ }
+
+ out = im->image8[state->y + state->yoff] + state->xoff + state->x;
+
+ for (;;) {
+ if (state->state == 1) {
+ /* First free entry in table */
+ context->next = context->clear + 2;
+
+ /* Initial code size */
+ context->codesize = context->bits + 1;
+ context->codemask = (1 << context->codesize) - 1;
+
+ /* Buffer pointer. We fill the buffer from right, which
+ allows us to return all of it in one operation. */
+ context->bufferindex = GIFBUFFER;
+
+ state->state = 2;
+ }
+
+ if (context->bufferindex < GIFBUFFER) {
+ /* Return whole buffer in one chunk */
+ i = GIFBUFFER - context->bufferindex;
+ p = &context->buffer[context->bufferindex];
+
+ context->bufferindex = GIFBUFFER;
+
+ } else {
+ /* Get current symbol */
+
+ while (context->bitcount < context->codesize) {
+ if (context->blocksize > 0) {
+ /* Read next byte */
+ c = *ptr++;
+ bytes--;
+
+ context->blocksize--;
+
+ /* New bits are shifted in from the left. */
+ context->bitbuffer |= (INT32)c << context->bitcount;
+ context->bitcount += 8;
+
+ } else {
+ /* New GIF block */
+
+ /* We don't start decoding unless we have a full block */
+ if (bytes < 1) {
+ return ptr - buffer;
+ }
+ c = *ptr;
+ if (bytes < c + 1) {
+ return ptr - buffer;
+ }
+
+ context->blocksize = c;
+
+ ptr++;
+ bytes--;
+ }
+ }
+
+ /* Extract current symbol from bit buffer. */
+ c = (int)context->bitbuffer & context->codemask;
+
+ /* Adjust buffer */
+ context->bitbuffer >>= context->codesize;
+ context->bitcount -= context->codesize;
+
+ /* If c is less than "clear", it's a data byte. Otherwise,
+ it's either clear/end or a code symbol which should be
+ expanded. */
+
+ if (c == context->clear) {
+ if (state->state != 2) {
+ state->state = 1;
+ }
+ continue;
+ }
+
+ if (c == context->end) {
+ break;
+ }
+
+ i = 1;
+ p = &context->lastdata;
+
+ if (state->state == 2) {
+ /* First valid symbol after clear; use as is */
+ if (c > context->clear) {
+ state->errcode = IMAGING_CODEC_BROKEN;
+ return -1;
+ }
+
+ context->lastdata = context->lastcode = c;
+ state->state = 3;
+
+ } else {
+ thiscode = c;
+
+ if (c > context->next) {
+ state->errcode = IMAGING_CODEC_BROKEN;
+ return -1;
+ }
+
+ if (c == context->next) {
+ /* c == next is allowed. not sure why. */
+
+ if (context->bufferindex <= 0) {
+ state->errcode = IMAGING_CODEC_BROKEN;
+ return -1;
+ }
+
+ context->buffer[--context->bufferindex] = context->lastdata;
+
+ c = context->lastcode;
+ }
+
+ while (c >= context->clear) {
+ /* Copy data string to buffer (beginning from right) */
+
+ if (context->bufferindex <= 0 || c >= GIFTABLE) {
+ state->errcode = IMAGING_CODEC_BROKEN;
+ return -1;
+ }
+
+ context->buffer[--context->bufferindex] = context->data[c];
+
+ c = context->link[c];
+ }
+
+ context->lastdata = c;
+
+ if (context->next < GIFTABLE) {
+ /* We'll only add this symbol if we have room
+ for it (take the advice, Netscape!) */
+ context->data[context->next] = c;
+ context->link[context->next] = context->lastcode;
+
+ if (context->next == context->codemask &&
+ context->codesize < GIFBITS) {
+ /* Expand code size */
+ context->codesize++;
+ context->codemask = (1 << context->codesize) - 1;
+ }
+
+ context->next++;
+ }
+
+ context->lastcode = thiscode;
+ }
+ }
+
+ /* Copy the bytes into the image */
+ if (state->y >= state->ysize) {
+ state->errcode = IMAGING_CODEC_OVERRUN;
+ return -1;
+ }
+
+ /* To squeeze some extra pixels out of this loop, we test for
+ some common cases and handle them separately. */
+
+ /* This cannot be used if there is transparency */
+ if (context->transparency == -1) {
+ if (i == 1) {
+ if (state->x < state->xsize - 1) {
+ /* Single pixel, not at the end of the line. */
+ *out++ = p[0];
+ state->x++;
+ continue;
+ }
+ } else if (state->x + i <= state->xsize) {
+ /* This string fits into current line. */
+ memcpy(out, p, i);
+ out += i;
+ state->x += i;
+ if (state->x == state->xsize) {
+ NEWLINE(state, context);
+ }
+ continue;
+ }
+ }
+
+ /* No shortcut, copy pixel by pixel */
+ for (c = 0; c < i; c++) {
+ if (p[c] != context->transparency) {
+ *out = p[c];
+ }
+ out++;
+ if (++state->x >= state->xsize) {
+ NEWLINE(state, context);
+ }
+ }
+ }
+
+ return ptr - buffer;
+}
diff --git a/contrib/python/Pillow/py3/libImaging/GifEncode.c b/contrib/python/Pillow/py3/libImaging/GifEncode.c
new file mode 100644
index 00000000000..f232454052a
--- /dev/null
+++ b/contrib/python/Pillow/py3/libImaging/GifEncode.c
@@ -0,0 +1,361 @@
+/*
+ * The Python Imaging Library.
+ * $Id$
+ *
+ * encoder for uncompressed GIF data
+ *
+ * history:
+ * 97-01-05 fl created (writes uncompressed data)
+ * 97-08-27 fl fixed off-by-one error in buffer size test
+ * 98-07-09 fl added interlace write support
+ * 99-02-07 fl rewritten, now uses a run-length encoding strategy
+ * 99-02-08 fl improved run-length encoding for long runs
+ * 2020-12-12 rdg Reworked for LZW compression.
+ *
+ * Copyright (c) Secret Labs AB 1997-99.
+ * Copyright (c) Fredrik Lundh 1997.
+ *
+ * See the README file for information on usage and redistribution.
+ */
+
+#include "Imaging.h"
+
+#include "Gif.h"
+
+enum { INIT, ENCODE, FINISH };
+
+/* GIF LZW encoder by Raymond Gardner. */
+/* Released here under PIL license. */
+
+/* This LZW encoder conforms to the GIF LZW format specified in the original
+ * Compuserve GIF 87a and GIF 89a specifications (see e.g.
+ * https://www.w3.org/Graphics/GIF/spec-gif87.txt Appendix C and
+ * https://www.w3.org/Graphics/GIF/spec-gif89a.txt Appendix F).
+ */
+
+/* Return values */
+#define GLZW_OK 0
+#define GLZW_NO_INPUT_AVAIL 1
+#define GLZW_NO_OUTPUT_AVAIL 2
+#define GLZW_INTERNAL_ERROR 3
+
+#define CODE_LIMIT 4096
+
+/* Values of entry_state */
+enum { LZW_INITIAL, LZW_TRY_IN1, LZW_TRY_IN2, LZW_TRY_OUT1, LZW_TRY_OUT2,
+ LZW_FINISHED };
+
+/* Values of control_state */
+enum { PUT_HEAD, PUT_INIT_CLEAR, PUT_CLEAR, PUT_LAST_HEAD, PUT_END };
+
+static void glzwe_reset(GIFENCODERSTATE *st) {
+ st->next_code = st->end_code + 1;
+ st->max_code = 2 * st->clear_code - 1;
+ st->code_width = st->bits + 1;
+ memset(st->codes, 0, sizeof(st->codes));
+}
+
+static void glzwe_init(GIFENCODERSTATE *st) {
+ st->clear_code = 1 << st->bits;
+ st->end_code = st->clear_code + 1;
+ glzwe_reset(st);
+ st->entry_state = LZW_INITIAL;
+ st->buf_bits_left = 8;
+ st->code_buffer = 0;
+}
+
+static int glzwe(GIFENCODERSTATE *st, const UINT8 *in_ptr, UINT8 *out_ptr,
+ UINT32 *in_avail, UINT32 *out_avail,
+ UINT32 end_of_data) {
+ switch (st->entry_state) {
+
+ case LZW_TRY_IN1:
+get_first_byte:
+ if (!*in_avail) {
+ if (end_of_data) {
+ goto end_of_data;
+ }
+ st->entry_state = LZW_TRY_IN1;
+ return GLZW_NO_INPUT_AVAIL;
+ }
+ st->head = *in_ptr++;
+ (*in_avail)--;
+
+ case LZW_TRY_IN2:
+encode_loop:
+ if (!*in_avail) {
+ if (end_of_data) {
+ st->code = st->head;
+ st->put_state = PUT_LAST_HEAD;
+ goto put_code;
+ }
+ st->entry_state = LZW_TRY_IN2;
+ return GLZW_NO_INPUT_AVAIL;
+ }
+ st->tail = *in_ptr++;
+ (*in_avail)--;
+
+ /* Knuth TAOCP vol 3 sec. 6.4 algorithm D. */
+ /* Hash found experimentally to be pretty good. */
+ /* This works ONLY with TABLE_SIZE a power of 2. */
+ st->probe = ((st->head ^ (st->tail << 6)) * 31) & (TABLE_SIZE - 1);
+ while (st->codes[st->probe]) {
+ if ((st->codes[st->probe] & 0xFFFFF) ==
+ ((st->head << 8) | st->tail)) {
+ st->head = st->codes[st->probe] >> 20;
+ goto encode_loop;
+ } else {
+ /* Reprobe decrement must be nonzero and relatively prime to table
+ * size. So, any odd positive number for power-of-2 size. */
+ if ((st->probe -= ((st->tail << 2) | 1)) < 0) {
+ st->probe += TABLE_SIZE;
+ }
+ }
+ }
+ /* Key not found, probe is at empty slot. */
+ st->code = st->head;
+ st->put_state = PUT_HEAD;
+ goto put_code;
+insert_code_or_clear: /* jump here after put_code */
+ if (st->next_code < CODE_LIMIT) {
+ st->codes[st->probe] = (st->next_code << 20) |
+ (st->head << 8) | st->tail;
+ if (st->next_code > st->max_code) {
+ st->max_code = st->max_code * 2 + 1;
+ st->code_width++;
+ }
+ st->next_code++;
+ } else {
+ st->code = st->clear_code;
+ st->put_state = PUT_CLEAR;
+ goto put_code;
+reset_after_clear: /* jump here after put_code */
+ glzwe_reset(st);
+ }
+ st->head = st->tail;
+ goto encode_loop;
+
+ case LZW_INITIAL:
+ glzwe_reset(st);
+ st->code = st->clear_code;
+ st->put_state = PUT_INIT_CLEAR;
+put_code:
+ st->code_bits_left = st->code_width;
+check_buf_bits:
+ if (!st->buf_bits_left) { /* out buffer full */
+
+ case LZW_TRY_OUT1:
+ if (!*out_avail) {
+ st->entry_state = LZW_TRY_OUT1;
+ return GLZW_NO_OUTPUT_AVAIL;
+ }
+ *out_ptr++ = st->code_buffer;
+ (*out_avail)--;
+ st->code_buffer = 0;
+ st->buf_bits_left = 8;
+ }
+ /* code bits to pack */
+ UINT32 n = st->buf_bits_left < st->code_bits_left
+ ? st->buf_bits_left : st->code_bits_left;
+ st->code_buffer |=
+ (st->code & ((1 << n) - 1)) << (8 - st->buf_bits_left);
+ st->code >>= n;
+ st->buf_bits_left -= n;
+ st->code_bits_left -= n;
+ if (st->code_bits_left) {
+ goto check_buf_bits;
+ }
+ switch (st->put_state) {
+ case PUT_INIT_CLEAR:
+ goto get_first_byte;
+ case PUT_HEAD:
+ goto insert_code_or_clear;
+ case PUT_CLEAR:
+ goto reset_after_clear;
+ case PUT_LAST_HEAD:
+ goto end_of_data;
+ case PUT_END:
+ goto flush_code_buffer;
+ default:
+ return GLZW_INTERNAL_ERROR;
+ }
+
+end_of_data:
+ st->code = st->end_code;
+ st->put_state = PUT_END;
+ goto put_code;
+flush_code_buffer: /* jump here after put_code */
+ if (st->buf_bits_left < 8) {
+
+ case LZW_TRY_OUT2:
+ if (!*out_avail) {
+ st->entry_state = LZW_TRY_OUT2;
+ return GLZW_NO_OUTPUT_AVAIL;
+ }
+ *out_ptr++ = st->code_buffer;
+ (*out_avail)--;
+ }
+ st->entry_state = LZW_FINISHED;
+ return GLZW_OK;
+
+ case LZW_FINISHED:
+ return GLZW_OK;
+
+ default:
+ return GLZW_INTERNAL_ERROR;
+ }
+}
+/* -END- GIF LZW encoder. */
+
+int
+ImagingGifEncode(Imaging im, ImagingCodecState state, UINT8* buf, int bytes) {
+ UINT8* ptr;
+ UINT8* sub_block_ptr;
+ UINT8* sub_block_limit;
+ UINT8* buf_limit;
+ GIFENCODERSTATE *context = (GIFENCODERSTATE*) state->context;
+ int r;
+
+ UINT32 in_avail, in_used;
+ UINT32 out_avail, out_used;
+
+ if (state->state == INIT) {
+ state->state = ENCODE;
+ glzwe_init(context);
+
+ if (context->interlace) {
+ context->interlace = 1;
+ context->step = 8;
+ } else {
+ context->step = 1;
+ }
+
+ /* Need at least 2 bytes for data sub-block; 5 for empty image */
+ if (bytes < 5) {
+ state->errcode = IMAGING_CODEC_CONFIG;
+ return 0;
+ }
+ /* sanity check */
+ if (state->xsize <= 0 || state->ysize <= 0) {
+ /* Is this better than an error return? */
+ /* This will handle any legal "LZW Minimum Code Size" */
+ memset(buf, 0, 5);
+ in_avail = 0;
+ out_avail = 5;
+ r = glzwe(context, (const UINT8 *)"", buf + 1, &in_avail, &out_avail, 1);
+ if (r == GLZW_OK) {
+ r = 5 - out_avail;
+ if (r < 1 || r > 3) {
+ state->errcode = IMAGING_CODEC_BROKEN;
+ return 0;
+ }
+ buf[0] = r;
+ state->errcode = IMAGING_CODEC_END;
+ return r + 2;
+ } else {
+ /* Should not be possible unless something external to this
+ * routine messes with our state data */
+ state->errcode = IMAGING_CODEC_BROKEN;
+ return 0;
+ }
+ }
+ /* Init state->x to make if() below true the first time through. */
+ state->x = state->xsize;
+ }
+
+ buf_limit = buf + bytes;
+ sub_block_limit = sub_block_ptr = ptr = buf;
+
+ /* On entry, buf is output buffer, bytes is space available in buf.
+ * Loop here getting input until buf is full or image is all encoded. */
+ for (;;) {
+ /* Set up sub-block ptr and limit. sub_block_ptr stays at beginning
+ * of sub-block until it is full. ptr will advance when any data is
+ * placed in buf.
+ */
+ if (ptr >= sub_block_limit) {
+ if (buf_limit - ptr < 2) { /* Need at least 2 for data sub-block */
+ return ptr - buf;
+ }
+ sub_block_ptr = ptr;
+ sub_block_limit = sub_block_ptr +
+ (256 < buf_limit - sub_block_ptr ?
+ 256 : buf_limit - sub_block_ptr);
+ *ptr++ = 0;
+ }
+
+ /* Get next row of pixels. */
+ /* This if() originally tested state->x==0 for the first time through.
+ * This no longer works, as the loop will not advance state->x if
+ * glzwe() does not consume any input; this would advance the row
+ * spuriously. Now pre-init state->x above for first time, and avoid
+ * entering if() when state->state is FINISH, or it will loop
+ * infinitely.
+ */
+ if (state->x >= state->xsize && state->state == ENCODE) {
+ if (!context->interlace && state->y >= state->ysize) {
+ state->state = FINISH;
+ continue;
+ }
+
+ /* get another line of data */
+ state->shuffle(
+ state->buffer,
+ (UINT8*) im->image[state->y + state->yoff] +
+ state->xoff * im->pixelsize, state->xsize
+ );
+ state->x = 0;
+
+ /* step forward, according to the interlace settings */
+ state->y += context->step;
+ while (context->interlace && state->y >= state->ysize) {
+ switch (context->interlace) {
+ case 1:
+ state->y = 4;
+ context->interlace = 2;
+ break;
+ case 2:
+ context->step = 4;
+ state->y = 2;
+ context->interlace = 3;
+ break;
+ case 3:
+ context->step = 2;
+ state->y = 1;
+ context->interlace = 0;
+ break;
+ default:
+ /* just make sure we don't loop forever */
+ context->interlace = 0;
+ }
+ }
+ }
+
+ in_avail = state->xsize - state->x; /* bytes left in line */
+ out_avail = sub_block_limit - ptr; /* bytes left in sub-block */
+ r = glzwe(context, &state->buffer[state->x], ptr, &in_avail,
+ &out_avail, state->state == FINISH);
+ out_used = sub_block_limit - ptr - out_avail;
+ *sub_block_ptr += out_used;
+ ptr += out_used;
+ in_used = state->xsize - state->x - in_avail;
+ state->x += in_used;
+
+ if (r == GLZW_OK) {
+ /* Should not be possible when end-of-data flag is false. */
+ state->errcode = IMAGING_CODEC_END;
+ return ptr - buf;
+ } else if (r == GLZW_NO_INPUT_AVAIL) {
+ /* Used all the input line; get another line */
+ continue;
+ } else if (r == GLZW_NO_OUTPUT_AVAIL) {
+ /* subblock is full */
+ continue;
+ } else {
+ /* Should not be possible unless something external to this
+ * routine messes with our state data */
+ state->errcode = IMAGING_CODEC_BROKEN;
+ return 0;
+ }
+ }
+}
diff --git a/contrib/python/Pillow/py3/libImaging/HexDecode.c b/contrib/python/Pillow/py3/libImaging/HexDecode.c
new file mode 100644
index 00000000000..bd16cdbe1da
--- /dev/null
+++ b/contrib/python/Pillow/py3/libImaging/HexDecode.c
@@ -0,0 +1,63 @@
+/*
+ * The Python Imaging Library.
+ * $Id$
+ *
+ * decoder for hex encoded image data
+ *
+ * history:
+ * 96-05-16 fl Created
+ *
+ * Copyright (c) Fredrik Lundh 1996.
+ * Copyright (c) Secret Labs AB 1997.
+ *
+ * See the README file for information on usage and redistribution.
+ */
+
+#include "Imaging.h"
+
+#define HEX(v) \
+ ((v >= '0' && v <= '9') ? v - '0' \
+ : (v >= 'a' && v <= 'f') ? v - 'a' + 10 \
+ : (v >= 'A' && v <= 'F') ? v - 'A' + 10 \
+ : -1)
+
+int
+ImagingHexDecode(Imaging im, ImagingCodecState state, UINT8 *buf, Py_ssize_t bytes) {
+ UINT8 *ptr;
+ int a, b;
+
+ ptr = buf;
+
+ for (;;) {
+ if (bytes < 2) {
+ return ptr - buf;
+ }
+
+ a = HEX(ptr[0]);
+ b = HEX(ptr[1]);
+
+ if (a < 0 || b < 0) {
+ ptr++;
+ bytes--;
+
+ } else {
+ ptr += 2;
+ bytes -= 2;
+
+ state->buffer[state->x] = (a << 4) + b;
+
+ if (++state->x >= state->bytes) {
+ /* Got a full line, unpack it */
+ state->shuffle(
+ (UINT8 *)im->image[state->y], state->buffer, state->xsize);
+
+ state->x = 0;
+
+ if (++state->y >= state->ysize) {
+ /* End of file (errcode = 0) */
+ return -1;
+ }
+ }
+ }
+ }
+}
diff --git a/contrib/python/Pillow/py3/libImaging/Histo.c b/contrib/python/Pillow/py3/libImaging/Histo.c
new file mode 100644
index 00000000000..c5a547a647b
--- /dev/null
+++ b/contrib/python/Pillow/py3/libImaging/Histo.c
@@ -0,0 +1,201 @@
+/*
+ * The Python Imaging Library
+ * $Id$
+ *
+ * histogram support
+ *
+ * history:
+ * 1995-06-15 fl Created.
+ * 1996-04-05 fl Fixed histogram for multiband images.
+ * 1997-02-23 fl Added mask support
+ * 1998-07-01 fl Added basic 32-bit float/integer support
+ *
+ * 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.
+ */
+
+#include "Imaging.h"
+
+/* HISTOGRAM */
+/* --------------------------------------------------------------------
+ * Take a histogram of an image. Returns a histogram object containing
+ * 256 slots per band in the input image.
+ */
+
+void
+ImagingHistogramDelete(ImagingHistogram h) {
+ if (h) {
+ if (h->histogram) {
+ free(h->histogram);
+ }
+ free(h);
+ }
+}
+
+ImagingHistogram
+ImagingHistogramNew(Imaging im) {
+ ImagingHistogram h;
+
+ /* Create histogram descriptor */
+ h = calloc(1, sizeof(struct ImagingHistogramInstance));
+ if (!h) {
+ return (ImagingHistogram)ImagingError_MemoryError();
+ }
+ strncpy(h->mode, im->mode, IMAGING_MODE_LENGTH - 1);
+ h->mode[IMAGING_MODE_LENGTH - 1] = 0;
+
+ h->bands = im->bands;
+ h->histogram = calloc(im->pixelsize, 256 * sizeof(long));
+ if (!h->histogram) {
+ free(h);
+ return (ImagingHistogram)ImagingError_MemoryError();
+ }
+
+ return h;
+}
+
+ImagingHistogram
+ImagingGetHistogram(Imaging im, Imaging imMask, void *minmax) {
+ ImagingSectionCookie cookie;
+ int x, y, i;
+ ImagingHistogram h;
+ INT32 imin, imax;
+ FLOAT32 fmin, fmax, scale;
+
+ if (!im) {
+ return ImagingError_ModeError();
+ }
+
+ if (imMask) {
+ /* Validate mask */
+ if (im->xsize != imMask->xsize || im->ysize != imMask->ysize) {
+ return ImagingError_Mismatch();
+ }
+ if (strcmp(imMask->mode, "1") != 0 && strcmp(imMask->mode, "L") != 0) {
+ return ImagingError_ValueError("bad transparency mask");
+ }
+ }
+
+ h = ImagingHistogramNew(im);
+ if (!h) {
+ return NULL;
+ }
+
+ if (imMask) {
+ /* mask */
+ if (im->image8) {
+ ImagingSectionEnter(&cookie);
+ for (y = 0; y < im->ysize; y++) {
+ for (x = 0; x < im->xsize; x++) {
+ if (imMask->image8[y][x] != 0) {
+ h->histogram[im->image8[y][x]]++;
+ }
+ }
+ }
+ ImagingSectionLeave(&cookie);
+ } else { /* yes, we need the braces. C isn't Python! */
+ if (im->type != IMAGING_TYPE_UINT8) {
+ ImagingHistogramDelete(h);
+ return ImagingError_ModeError();
+ }
+ ImagingSectionEnter(&cookie);
+ for (y = 0; y < im->ysize; y++) {
+ UINT8 *in = (UINT8 *)im->image32[y];
+ for (x = 0; x < im->xsize; x++) {
+ if (imMask->image8[y][x] != 0) {
+ h->histogram[(*in++)]++;
+ h->histogram[(*in++) + 256]++;
+ h->histogram[(*in++) + 512]++;
+ h->histogram[(*in++) + 768]++;
+ } else {
+ in += 4;
+ }
+ }
+ }
+ ImagingSectionLeave(&cookie);
+ }
+ } else {
+ /* mask not given; process pixels in image */
+ if (im->image8) {
+ ImagingSectionEnter(&cookie);
+ for (y = 0; y < im->ysize; y++) {
+ for (x = 0; x < im->xsize; x++) {
+ h->histogram[im->image8[y][x]]++;
+ }
+ }
+ ImagingSectionLeave(&cookie);
+ } else {
+ switch (im->type) {
+ case IMAGING_TYPE_UINT8:
+ ImagingSectionEnter(&cookie);
+ for (y = 0; y < im->ysize; y++) {
+ UINT8 *in = (UINT8 *)im->image[y];
+ for (x = 0; x < im->xsize; x++) {
+ h->histogram[(*in++)]++;
+ h->histogram[(*in++) + 256]++;
+ h->histogram[(*in++) + 512]++;
+ h->histogram[(*in++) + 768]++;
+ }
+ }
+ ImagingSectionLeave(&cookie);
+ break;
+ case IMAGING_TYPE_INT32:
+ if (!minmax) {
+ ImagingHistogramDelete(h);
+ return ImagingError_ValueError("min/max not given");
+ }
+ if (!im->xsize || !im->ysize) {
+ break;
+ }
+ memcpy(&imin, minmax, sizeof(imin));
+ memcpy(&imax, ((char *)minmax) + sizeof(imin), sizeof(imax));
+ if (imin >= imax) {
+ break;
+ }
+ ImagingSectionEnter(&cookie);
+ scale = 255.0F / (imax - imin);
+ for (y = 0; y < im->ysize; y++) {
+ INT32 *in = im->image32[y];
+ for (x = 0; x < im->xsize; x++) {
+ i = (int)(((*in++) - imin) * scale);
+ if (i >= 0 && i < 256) {
+ h->histogram[i]++;
+ }
+ }
+ }
+ ImagingSectionLeave(&cookie);
+ break;
+ case IMAGING_TYPE_FLOAT32:
+ if (!minmax) {
+ ImagingHistogramDelete(h);
+ return ImagingError_ValueError("min/max not given");
+ }
+ if (!im->xsize || !im->ysize) {
+ break;
+ }
+ memcpy(&fmin, minmax, sizeof(fmin));
+ memcpy(&fmax, ((char *)minmax) + sizeof(fmin), sizeof(fmax));
+ if (fmin >= fmax) {
+ break;
+ }
+ ImagingSectionEnter(&cookie);
+ scale = 255.0F / (fmax - fmin);
+ for (y = 0; y < im->ysize; y++) {
+ FLOAT32 *in = (FLOAT32 *)im->image32[y];
+ for (x = 0; x < im->xsize; x++) {
+ i = (int)(((*in++) - fmin) * scale);
+ if (i >= 0 && i < 256) {
+ h->histogram[i]++;
+ }
+ }
+ }
+ ImagingSectionLeave(&cookie);
+ break;
+ }
+ }
+ }
+
+ return h;
+}
diff --git a/contrib/python/Pillow/py3/libImaging/ImDib.h b/contrib/python/Pillow/py3/libImaging/ImDib.h
new file mode 100644
index 00000000000..91ff3f322ff
--- /dev/null
+++ b/contrib/python/Pillow/py3/libImaging/ImDib.h
@@ -0,0 +1,64 @@
+/*
+ * The Python Imaging Library
+ * $Id$
+ *
+ * Windows DIB specifics
+ *
+ * Copyright (c) Secret Labs AB 1997-98.
+ * Copyright (c) Fredrik Lundh 1996.
+ *
+ * See the README file for information on usage and redistribution.
+ */
+
+#ifdef _WIN32
+
+#include "ImPlatform.h"
+
+#if defined(__cplusplus)
+extern "C" {
+#endif
+
+struct ImagingDIBInstance {
+ /* Windows interface */
+ HDC dc;
+ HBITMAP bitmap;
+ HGDIOBJ old_bitmap;
+ BITMAPINFO *info;
+ UINT8 *bits;
+ HPALETTE palette;
+ /* Used by cut and paste */
+ char mode[4];
+ int xsize, ysize;
+ int pixelsize;
+ int linesize;
+ ImagingShuffler pack;
+ ImagingShuffler unpack;
+};
+
+typedef struct ImagingDIBInstance *ImagingDIB;
+
+extern char *
+ImagingGetModeDIB(int size_out[2]);
+
+extern ImagingDIB
+ImagingNewDIB(const char *mode, int xsize, int ysize);
+
+extern void
+ImagingDeleteDIB(ImagingDIB im);
+
+extern void
+ImagingDrawDIB(ImagingDIB dib, void *dc, int dst[4], int src[4]);
+extern void
+ImagingExposeDIB(ImagingDIB dib, void *dc);
+
+extern int
+ImagingQueryPaletteDIB(ImagingDIB dib, void *dc);
+
+extern void
+ImagingPasteDIB(ImagingDIB dib, Imaging im, int xy[4]);
+
+#if defined(__cplusplus)
+}
+#endif
+
+#endif
diff --git a/contrib/python/Pillow/py3/libImaging/ImPlatform.h b/contrib/python/Pillow/py3/libImaging/ImPlatform.h
new file mode 100644
index 00000000000..f6e7fb6b921
--- /dev/null
+++ b/contrib/python/Pillow/py3/libImaging/ImPlatform.h
@@ -0,0 +1,98 @@
+/*
+ * The Python Imaging Library
+ * $Id$
+ *
+ * platform declarations for the imaging core library
+ *
+ * Copyright (c) Fredrik Lundh 1995-2003.
+ */
+
+#include "Python.h"
+
+/* Check that we have an ANSI compliant compiler */
+#ifndef HAVE_PROTOTYPES
+#error Sorry, this library requires support for ANSI prototypes.
+#endif
+#ifndef STDC_HEADERS
+#error Sorry, this library requires ANSI header files.
+#endif
+
+#if defined(PIL_NO_INLINE)
+#define inline
+#else
+#if defined(_MSC_VER) && !defined(__GNUC__)
+#define inline __inline
+#endif
+#endif
+
+#if defined(_WIN32) || defined(__CYGWIN__) /* WIN */
+
+#define WIN32_LEAN_AND_MEAN
+#include <Windows.h>
+
+#ifdef __CYGWIN__
+#undef _WIN64
+#undef _WIN32
+#undef __WIN32__
+#undef WIN32
+#endif
+
+#else /* not WIN */
+/* For System that are not Windows, we'll need to define these. */
+/* We have to define them instead of using typedef because the JPEG lib also
+ defines their own types with the same names, so we need to be able to undef
+ ours before including the JPEG code. */
+
+#if __STDC_VERSION__ >= 199901L /* C99+ */
+
+#include <stdint.h>
+
+#define INT8 int8_t
+#define UINT8 uint8_t
+#define INT16 int16_t
+#define UINT16 uint16_t
+#define INT32 int32_t
+#define UINT32 uint32_t
+
+#else /* < C99 */
+
+#define INT8 signed char
+
+#if SIZEOF_SHORT == 2
+#define INT16 short
+#elif SIZEOF_INT == 2
+#define INT16 int
+#else
+#error Cannot find required 16-bit integer type
+#endif
+
+#if SIZEOF_SHORT == 4
+#define INT32 short
+#elif SIZEOF_INT == 4
+#define INT32 int
+#elif SIZEOF_LONG == 4
+#define INT32 long
+#else
+#error Cannot find required 32-bit integer type
+#endif
+
+#define UINT8 unsigned char
+#define UINT16 unsigned INT16
+#define UINT32 unsigned INT32
+
+#endif /* < C99 */
+
+#endif /* not WIN */
+
+/* assume IEEE; tweak if necessary (patches are welcome) */
+#define FLOAT16 UINT16
+#define FLOAT32 float
+#define FLOAT64 double
+
+#ifdef _MSC_VER
+typedef signed __int64 int64_t;
+#endif
+
+#ifdef __GNUC__
+#define GCC_VERSION (__GNUC__ * 10000 + __GNUC_MINOR__ * 100 + __GNUC_PATCHLEVEL__)
+#endif
diff --git a/contrib/python/Pillow/py3/libImaging/Imaging.h b/contrib/python/Pillow/py3/libImaging/Imaging.h
new file mode 100644
index 00000000000..afcd2229bde
--- /dev/null
+++ b/contrib/python/Pillow/py3/libImaging/Imaging.h
@@ -0,0 +1,693 @@
+/*
+ * The Python Imaging Library
+ * $Id$
+ *
+ * declarations for the imaging core library
+ *
+ * Copyright (c) 1997-2005 by Secret Labs AB
+ * Copyright (c) 1995-2005 by Fredrik Lundh
+ *
+ * See the README file for information on usage and redistribution.
+ */
+
+#include "ImPlatform.h"
+
+#if defined(__cplusplus)
+extern "C" {
+#endif
+
+#ifndef M_PI
+#define M_PI 3.1415926535897932384626433832795
+#endif
+
+/* -------------------------------------------------------------------- */
+
+/*
+ * Image data organization:
+ *
+ * mode bytes byte order
+ * -------------------------------
+ * 1 1 1
+ * L 1 L
+ * P 1 P
+ * I 4 I (32-bit integer, native byte order)
+ * F 4 F (32-bit IEEE float, native byte order)
+ * RGB 4 R, G, B, -
+ * RGBA 4 R, G, B, A
+ * CMYK 4 C, M, Y, K
+ * YCbCr 4 Y, Cb, Cr, -
+ * Lab 4 L, a, b, -
+ *
+ * experimental modes (incomplete):
+ * LA 4 L, -, -, A
+ * PA 4 P, -, -, A
+ * I;16 2 I (16-bit integer, native byte order)
+ *
+ * "P" is an 8-bit palette mode, which should be mapped through the
+ * palette member to get an output image. Check palette->mode to
+ * find the corresponding "real" mode.
+ *
+ * For information on how to access Imaging objects from your own C
+ * extensions, see http://www.effbot.org/zone/pil-extending.htm
+ */
+
+/* Handles */
+
+typedef struct ImagingMemoryInstance *Imaging;
+
+typedef struct ImagingAccessInstance *ImagingAccess;
+typedef struct ImagingHistogramInstance *ImagingHistogram;
+typedef struct ImagingOutlineInstance *ImagingOutline;
+typedef struct ImagingPaletteInstance *ImagingPalette;
+
+/* handle magics (used with PyCObject). */
+#define IMAGING_MAGIC "PIL Imaging"
+
+/* pixel types */
+#define IMAGING_TYPE_UINT8 0
+#define IMAGING_TYPE_INT32 1
+#define IMAGING_TYPE_FLOAT32 2
+#define IMAGING_TYPE_SPECIAL 3 /* check mode for details */
+
+#define IMAGING_MODE_LENGTH \
+ 6 + 1 /* Band names ("1", "L", "P", "RGB", "RGBA", "CMYK", "YCbCr", "BGR;xy") */
+
+typedef struct {
+ char *ptr;
+ int size;
+} ImagingMemoryBlock;
+
+struct ImagingMemoryInstance {
+ /* Format */
+ char mode[IMAGING_MODE_LENGTH]; /* Band names ("1", "L", "P", "RGB", "RGBA", "CMYK",
+ "YCbCr", "BGR;xy") */
+ int type; /* Data type (IMAGING_TYPE_*) */
+ int depth; /* Depth (ignored in this version) */
+ int bands; /* Number of bands (1, 2, 3, or 4) */
+ int xsize; /* Image dimension. */
+ int ysize;
+
+ /* Colour palette (for "P" images only) */
+ ImagingPalette palette;
+
+ /* Data pointers */
+ UINT8 **image8; /* Set for 8-bit images (pixelsize=1). */
+ INT32 **image32; /* Set for 32-bit images (pixelsize=4). */
+
+ /* Internals */
+ char **image; /* Actual raster data. */
+ char *block; /* Set if data is allocated in a single block. */
+ ImagingMemoryBlock *blocks; /* Memory blocks for pixel storage */
+
+ int pixelsize; /* Size of a pixel, in bytes (1, 2 or 4) */
+ int linesize; /* Size of a line, in bytes (xsize * pixelsize) */
+
+ /* Virtual methods */
+ void (*destroy)(Imaging im);
+};
+
+#define IMAGING_PIXEL_1(im, x, y) ((im)->image8[(y)][(x)])
+#define IMAGING_PIXEL_L(im, x, y) ((im)->image8[(y)][(x)])
+#define IMAGING_PIXEL_LA(im, x, y) ((im)->image[(y)][(x)*4])
+#define IMAGING_PIXEL_P(im, x, y) ((im)->image8[(y)][(x)])
+#define IMAGING_PIXEL_PA(im, x, y) ((im)->image[(y)][(x)*4])
+#define IMAGING_PIXEL_I(im, x, y) ((im)->image32[(y)][(x)])
+#define IMAGING_PIXEL_F(im, x, y) (((FLOAT32 *)(im)->image32[y])[x])
+#define IMAGING_PIXEL_RGB(im, x, y) ((im)->image[(y)][(x)*4])
+#define IMAGING_PIXEL_RGBA(im, x, y) ((im)->image[(y)][(x)*4])
+#define IMAGING_PIXEL_CMYK(im, x, y) ((im)->image[(y)][(x)*4])
+#define IMAGING_PIXEL_YCbCr(im, x, y) ((im)->image[(y)][(x)*4])
+
+#define IMAGING_PIXEL_UINT8(im, x, y) ((im)->image8[(y)][(x)])
+#define IMAGING_PIXEL_INT32(im, x, y) ((im)->image32[(y)][(x)])
+#define IMAGING_PIXEL_FLOAT32(im, x, y) (((FLOAT32 *)(im)->image32[y])[x])
+
+struct ImagingAccessInstance {
+ const char *mode;
+ void (*get_pixel)(Imaging im, int x, int y, void *pixel);
+ void (*put_pixel)(Imaging im, int x, int y, const void *pixel);
+};
+
+struct ImagingHistogramInstance {
+ /* Format */
+ char mode[IMAGING_MODE_LENGTH]; /* Band names (of corresponding source image) */
+ int bands; /* Number of bands (1, 3, or 4) */
+
+ /* Data */
+ long *histogram; /* Histogram (bands*256 longs) */
+};
+
+struct ImagingPaletteInstance {
+ /* Format */
+ char mode[IMAGING_MODE_LENGTH]; /* Band names */
+
+ /* Data */
+ int size;
+ UINT8 palette[1024]; /* Palette data (same format as image data) */
+
+ INT16 *cache; /* Palette cache (used for predefined palettes) */
+ int keep_cache; /* This palette will be reused; keep cache */
+};
+
+typedef struct ImagingMemoryArena {
+ int alignment; /* Alignment in memory of each line of an image */
+ int block_size; /* Preferred block size, bytes */
+ int blocks_max; /* Maximum number of cached blocks */
+ int blocks_cached; /* Current number of blocks not associated with images */
+ ImagingMemoryBlock *blocks_pool;
+ int stats_new_count; /* Number of new allocated images */
+ int stats_allocated_blocks; /* Number of allocated blocks */
+ int stats_reused_blocks; /* Number of blocks which were retrieved from a pool */
+ int stats_reallocated_blocks; /* Number of blocks which were actually reallocated
+ after retrieving */
+ int stats_freed_blocks; /* Number of freed blocks */
+} * ImagingMemoryArena;
+
+/* Objects */
+/* ------- */
+
+extern struct ImagingMemoryArena ImagingDefaultArena;
+extern int
+ImagingMemorySetBlocksMax(ImagingMemoryArena arena, int blocks_max);
+extern void
+ImagingMemoryClearCache(ImagingMemoryArena arena, int new_size);
+
+extern Imaging
+ImagingNew(const char *mode, int xsize, int ysize);
+extern Imaging
+ImagingNewDirty(const char *mode, int xsize, int ysize);
+extern Imaging
+ImagingNew2Dirty(const char *mode, Imaging imOut, Imaging imIn);
+extern void
+ImagingDelete(Imaging im);
+
+extern Imaging
+ImagingNewBlock(const char *mode, int xsize, int ysize);
+
+extern Imaging
+ImagingNewPrologue(const char *mode, int xsize, int ysize);
+extern Imaging
+ImagingNewPrologueSubtype(const char *mode, int xsize, int ysize, int structure_size);
+
+extern void
+ImagingCopyPalette(Imaging destination, Imaging source);
+
+extern void
+ImagingHistogramDelete(ImagingHistogram histogram);
+
+extern void
+ImagingAccessInit(void);
+extern ImagingAccess
+ImagingAccessNew(Imaging im);
+extern void
+_ImagingAccessDelete(Imaging im, ImagingAccess access);
+#define ImagingAccessDelete(im, access) /* nop, for now */
+
+extern ImagingPalette
+ImagingPaletteNew(const char *mode);
+extern ImagingPalette
+ImagingPaletteNewBrowser(void);
+extern ImagingPalette
+ImagingPaletteDuplicate(ImagingPalette palette);
+extern void
+ImagingPaletteDelete(ImagingPalette palette);
+
+extern int
+ImagingPaletteCachePrepare(ImagingPalette palette);
+extern void
+ImagingPaletteCacheUpdate(ImagingPalette palette, int r, int g, int b);
+extern void
+ImagingPaletteCacheDelete(ImagingPalette palette);
+
+#define ImagingPaletteCache(p, r, g, b) \
+ p->cache[(r >> 2) + (g >> 2) * 64 + (b >> 2) * 64 * 64]
+
+extern Imaging
+ImagingQuantize(Imaging im, int colours, int mode, int kmeans);
+
+/* Threading */
+/* --------- */
+
+typedef void *ImagingSectionCookie;
+
+extern void
+ImagingSectionEnter(ImagingSectionCookie *cookie);
+extern void
+ImagingSectionLeave(ImagingSectionCookie *cookie);
+
+/* Exceptions */
+/* ---------- */
+
+extern void *
+ImagingError_OSError(void);
+extern void *
+ImagingError_MemoryError(void);
+extern void *
+ImagingError_ModeError(void); /* maps to ValueError by default */
+extern void *
+ImagingError_Mismatch(void); /* maps to ValueError by default */
+extern void *
+ImagingError_ValueError(const char *message);
+extern void
+ImagingError_Clear(void);
+
+/* Transform callbacks */
+/* ------------------- */
+
+/* standard transforms */
+#define IMAGING_TRANSFORM_AFFINE 0
+#define IMAGING_TRANSFORM_PERSPECTIVE 2
+#define IMAGING_TRANSFORM_QUAD 3
+
+/* standard filters */
+#define IMAGING_TRANSFORM_NEAREST 0
+#define IMAGING_TRANSFORM_BOX 4
+#define IMAGING_TRANSFORM_BILINEAR 2
+#define IMAGING_TRANSFORM_HAMMING 5
+#define IMAGING_TRANSFORM_BICUBIC 3
+#define IMAGING_TRANSFORM_LANCZOS 1
+
+typedef int (*ImagingTransformMap)(double *X, double *Y, int x, int y, void *data);
+typedef int (*ImagingTransformFilter)(void *out, Imaging im, double x, double y);
+
+/* Image Manipulation Methods */
+/* -------------------------- */
+
+extern Imaging
+ImagingAlphaComposite(Imaging imIn1, Imaging imIn2);
+extern Imaging
+ImagingBlend(Imaging imIn1, Imaging imIn2, float alpha);
+extern Imaging
+ImagingCopy(Imaging im);
+extern Imaging
+ImagingConvert(Imaging im, const char *mode, ImagingPalette palette, int dither);
+extern Imaging
+ImagingConvertInPlace(Imaging im, const char *mode);
+extern Imaging
+ImagingConvertMatrix(Imaging im, const char *mode, float m[]);
+extern Imaging
+ImagingConvertTransparent(Imaging im, const char *mode, int r, int g, int b);
+extern Imaging
+ImagingCrop(Imaging im, int x0, int y0, int x1, int y1);
+extern Imaging
+ImagingExpand(Imaging im, int x, int y);
+extern Imaging
+ImagingFill(Imaging im, const void *ink);
+extern int
+ImagingFill2(
+ Imaging into, const void *ink, Imaging mask, int x0, int y0, int x1, int y1);
+extern Imaging
+ImagingFillBand(Imaging im, int band, int color);
+extern Imaging
+ImagingFillLinearGradient(const char *mode);
+extern Imaging
+ImagingFillRadialGradient(const char *mode);
+extern Imaging
+ImagingFilter(Imaging im, int xsize, int ysize, const FLOAT32 *kernel, FLOAT32 offset);
+extern Imaging
+ImagingFlipLeftRight(Imaging imOut, Imaging imIn);
+extern Imaging
+ImagingFlipTopBottom(Imaging imOut, Imaging imIn);
+extern Imaging
+ImagingGaussianBlur(Imaging imOut, Imaging imIn, float xradius, float yradius, int passes);
+extern Imaging
+ImagingGetBand(Imaging im, int band);
+extern Imaging
+ImagingMerge(const char *mode, Imaging bands[4]);
+extern int
+ImagingSplit(Imaging im, Imaging bands[4]);
+extern int
+ImagingGetBBox(Imaging im, int bbox[4], int alpha_only);
+typedef struct {
+ int x, y;
+ INT32 count;
+ INT32 pixel;
+} ImagingColorItem;
+extern ImagingColorItem *
+ImagingGetColors(Imaging im, int maxcolors, int *colors);
+extern int
+ImagingGetExtrema(Imaging im, void *extrema);
+extern int
+ImagingGetProjection(Imaging im, UINT8 *xproj, UINT8 *yproj);
+extern ImagingHistogram
+ImagingGetHistogram(Imaging im, Imaging mask, void *extrema);
+extern Imaging
+ImagingModeFilter(Imaging im, int size);
+extern Imaging
+ImagingNegative(Imaging im);
+extern Imaging
+ImagingOffset(Imaging im, int xoffset, int yoffset);
+extern int
+ImagingPaste(Imaging into, Imaging im, Imaging mask, int x0, int y0, int x1, int y1);
+extern Imaging
+ImagingPoint(Imaging im, const char *tablemode, const void *table);
+extern Imaging
+ImagingPointTransform(Imaging imIn, double scale, double offset);
+extern Imaging
+ImagingPutBand(Imaging im, Imaging imIn, int band);
+extern Imaging
+ImagingRankFilter(Imaging im, int size, int rank);
+extern Imaging
+ImagingRotate90(Imaging imOut, Imaging imIn);
+extern Imaging
+ImagingRotate180(Imaging imOut, Imaging imIn);
+extern Imaging
+ImagingRotate270(Imaging imOut, Imaging imIn);
+extern Imaging
+ImagingTranspose(Imaging imOut, Imaging imIn);
+extern Imaging
+ImagingTransverse(Imaging imOut, Imaging imIn);
+extern Imaging
+ImagingResample(Imaging imIn, int xsize, int ysize, int filter, float box[4]);
+extern Imaging
+ImagingReduce(Imaging imIn, int xscale, int yscale, int box[4]);
+extern Imaging
+ImagingTransform(
+ Imaging imOut,
+ Imaging imIn,
+ int method,
+ int x0,
+ int y0,
+ int x1,
+ int y1,
+ double a[8],
+ int filter,
+ int fill);
+extern Imaging
+ImagingUnsharpMask(Imaging imOut, Imaging im, float radius, int percent, int threshold);
+extern Imaging
+ImagingBoxBlur(Imaging imOut, Imaging imIn, float xradius, float yradius, int n);
+extern Imaging
+ImagingColorLUT3D_linear(
+ Imaging imOut,
+ Imaging imIn,
+ int table_channels,
+ int size1D,
+ int size2D,
+ int size3D,
+ INT16 *table);
+
+extern Imaging
+ImagingCopy2(Imaging imOut, Imaging imIn);
+extern Imaging
+ImagingConvert2(Imaging imOut, Imaging imIn);
+
+/* Channel operations */
+/* any mode, except "F" */
+extern Imaging
+ImagingChopLighter(Imaging imIn1, Imaging imIn2);
+extern Imaging
+ImagingChopDarker(Imaging imIn1, Imaging imIn2);
+extern Imaging
+ImagingChopDifference(Imaging imIn1, Imaging imIn2);
+extern Imaging
+ImagingChopMultiply(Imaging imIn1, Imaging imIn2);
+extern Imaging
+ImagingChopScreen(Imaging imIn1, Imaging imIn2);
+extern Imaging
+ImagingChopAdd(Imaging imIn1, Imaging imIn2, float scale, int offset);
+extern Imaging
+ImagingChopSubtract(Imaging imIn1, Imaging imIn2, float scale, int offset);
+extern Imaging
+ImagingChopAddModulo(Imaging imIn1, Imaging imIn2);
+extern Imaging
+ImagingChopSubtractModulo(Imaging imIn1, Imaging imIn2);
+extern Imaging
+ImagingChopSoftLight(Imaging imIn1, Imaging imIn2);
+extern Imaging
+ImagingChopHardLight(Imaging imIn1, Imaging imIn2);
+extern Imaging
+ImagingOverlay(Imaging imIn1, Imaging imIn2);
+
+/* "1" images only */
+extern Imaging
+ImagingChopAnd(Imaging imIn1, Imaging imIn2);
+extern Imaging
+ImagingChopOr(Imaging imIn1, Imaging imIn2);
+extern Imaging
+ImagingChopXor(Imaging imIn1, Imaging imIn2);
+
+/* Graphics */
+extern int
+ImagingDrawArc(
+ Imaging im,
+ int x0,
+ int y0,
+ int x1,
+ int y1,
+ float start,
+ float end,
+ const void *ink,
+ int width,
+ int op);
+extern int
+ImagingDrawBitmap(Imaging im, int x0, int y0, Imaging bitmap, const void *ink, int op);
+extern int
+ImagingDrawChord(
+ Imaging im,
+ int x0,
+ int y0,
+ int x1,
+ int y1,
+ float start,
+ float end,
+ const void *ink,
+ int fill,
+ int width,
+ int op);
+extern int
+ImagingDrawEllipse(
+ Imaging im,
+ int x0,
+ int y0,
+ int x1,
+ int y1,
+ const void *ink,
+ int fill,
+ int width,
+ int op);
+extern int
+ImagingDrawLine(Imaging im, int x0, int y0, int x1, int y1, const void *ink, int op);
+extern int
+ImagingDrawWideLine(
+ Imaging im, int x0, int y0, int x1, int y1, const void *ink, int width, int op);
+extern int
+ImagingDrawPieslice(
+ Imaging im,
+ int x0,
+ int y0,
+ int x1,
+ int y1,
+ float start,
+ float end,
+ const void *ink,
+ int fill,
+ int width,
+ int op);
+extern int
+ImagingDrawPoint(Imaging im, int x, int y, const void *ink, int op);
+extern int
+ImagingDrawPolygon(Imaging im, int points, int *xy, const void *ink, int fill, int width, int op);
+extern int
+ImagingDrawRectangle(
+ Imaging im,
+ int x0,
+ int y0,
+ int x1,
+ int y1,
+ const void *ink,
+ int fill,
+ int width,
+ int op);
+
+/* Level 2 graphics (WORK IN PROGRESS) */
+extern ImagingOutline
+ImagingOutlineNew(void);
+extern void
+ImagingOutlineDelete(ImagingOutline outline);
+
+extern int
+ImagingDrawOutline(
+ Imaging im, ImagingOutline outline, const void *ink, int fill, int op);
+
+extern int
+ImagingOutlineMove(ImagingOutline outline, float x, float y);
+extern int
+ImagingOutlineLine(ImagingOutline outline, float x, float y);
+extern int
+ImagingOutlineCurve(
+ ImagingOutline outline, float x1, float y1, float x2, float y2, float x3, float y3);
+extern int
+ImagingOutlineTransform(ImagingOutline outline, double a[6]);
+
+extern int
+ImagingOutlineClose(ImagingOutline outline);
+
+/* Special effects */
+extern Imaging
+ImagingEffectSpread(Imaging imIn, int distance);
+extern Imaging
+ImagingEffectNoise(int xsize, int ysize, float sigma);
+extern Imaging
+ImagingEffectMandelbrot(int xsize, int ysize, double extent[4], int quality);
+
+/* File I/O */
+/* -------- */
+
+/* Built-in drivers */
+extern Imaging
+ImagingOpenPPM(const char *filename);
+extern int
+ImagingSavePPM(Imaging im, const char *filename);
+
+/* Codecs */
+typedef struct ImagingCodecStateInstance *ImagingCodecState;
+typedef int (*ImagingCodec)(
+ Imaging im, ImagingCodecState state, UINT8 *buffer, int bytes);
+
+extern int
+ImagingBcnDecode(Imaging im, ImagingCodecState state, UINT8 *buffer, Py_ssize_t bytes);
+extern int
+ImagingBitDecode(Imaging im, ImagingCodecState state, UINT8 *buffer, Py_ssize_t bytes);
+extern int
+ImagingEpsEncode(Imaging im, ImagingCodecState state, UINT8 *buffer, int bytes);
+extern int
+ImagingFliDecode(Imaging im, ImagingCodecState state, UINT8 *buffer, Py_ssize_t bytes);
+extern int
+ImagingGifDecode(Imaging im, ImagingCodecState state, UINT8 *buffer, Py_ssize_t bytes);
+extern int
+ImagingGifEncode(Imaging im, ImagingCodecState state, UINT8 *buffer, int bytes);
+extern int
+ImagingHexDecode(Imaging im, ImagingCodecState state, UINT8 *buffer, Py_ssize_t bytes);
+#ifdef HAVE_LIBJPEG
+extern int
+ImagingJpegDecode(Imaging im, ImagingCodecState state, UINT8 *buffer, Py_ssize_t bytes);
+extern int
+ImagingJpegDecodeCleanup(ImagingCodecState state);
+extern int
+ImagingJpegUseJCSExtensions(void);
+
+extern int
+ImagingJpegEncode(Imaging im, ImagingCodecState state, UINT8 *buffer, int bytes);
+#endif
+#ifdef HAVE_OPENJPEG
+extern int
+ImagingJpeg2KDecode(
+ Imaging im, ImagingCodecState state, UINT8 *buffer, Py_ssize_t bytes);
+extern int
+ImagingJpeg2KDecodeCleanup(ImagingCodecState state);
+extern int
+ImagingJpeg2KEncode(Imaging im, ImagingCodecState state, UINT8 *buffer, int bytes);
+extern int
+ImagingJpeg2KEncodeCleanup(ImagingCodecState state);
+#endif
+#ifdef HAVE_LIBTIFF
+extern int
+ImagingLibTiffDecode(
+ Imaging im, ImagingCodecState state, UINT8 *buffer, Py_ssize_t bytes);
+extern int
+ImagingLibTiffEncode(Imaging im, ImagingCodecState state, UINT8 *buffer, int bytes);
+#endif
+#ifdef HAVE_LIBMPEG
+extern int
+ImagingMpegDecode(Imaging im, ImagingCodecState state, UINT8 *buffer, Py_ssize_t bytes);
+#endif
+extern int
+ImagingMspDecode(Imaging im, ImagingCodecState state, UINT8 *buffer, Py_ssize_t bytes);
+extern int
+ImagingPackbitsDecode(
+ Imaging im, ImagingCodecState state, UINT8 *buffer, Py_ssize_t bytes);
+extern int
+ImagingPcdDecode(Imaging im, ImagingCodecState state, UINT8 *buffer, Py_ssize_t bytes);
+extern int
+ImagingPcxDecode(Imaging im, ImagingCodecState state, UINT8 *buffer, Py_ssize_t bytes);
+extern int
+ImagingPcxEncode(Imaging im, ImagingCodecState state, UINT8 *buffer, int bytes);
+extern int
+ImagingRawDecode(Imaging im, ImagingCodecState state, UINT8 *buffer, Py_ssize_t bytes);
+extern int
+ImagingRawEncode(Imaging im, ImagingCodecState state, UINT8 *buffer, int bytes);
+extern int
+ImagingSgiRleDecode(
+ Imaging im, ImagingCodecState state, UINT8 *buffer, Py_ssize_t bytes);
+extern int
+ImagingSunRleDecode(
+ Imaging im, ImagingCodecState state, UINT8 *buffer, Py_ssize_t bytes);
+extern int
+ImagingTgaRleDecode(
+ Imaging im, ImagingCodecState state, UINT8 *buffer, Py_ssize_t bytes);
+extern int
+ImagingTgaRleEncode(Imaging im, ImagingCodecState state, UINT8 *buffer, int bytes);
+extern int
+ImagingXbmDecode(Imaging im, ImagingCodecState state, UINT8 *buffer, Py_ssize_t bytes);
+extern int
+ImagingXbmEncode(Imaging im, ImagingCodecState state, UINT8 *buffer, int bytes);
+#ifdef HAVE_LIBZ
+extern int
+ImagingZipDecode(Imaging im, ImagingCodecState state, UINT8 *buffer, Py_ssize_t bytes);
+extern int
+ImagingZipDecodeCleanup(ImagingCodecState state);
+extern int
+ImagingZipEncode(Imaging im, ImagingCodecState state, UINT8 *buffer, int bytes);
+extern int
+ImagingZipEncodeCleanup(ImagingCodecState state);
+#endif
+
+typedef void (*ImagingShuffler)(UINT8 *out, const UINT8 *in, int pixels);
+
+/* Public shufflers */
+extern void
+ImagingPackBGR(UINT8 *out, const UINT8 *in, int pixels);
+extern void
+ImagingUnpackYCC(UINT8 *out, const UINT8 *in, int pixels);
+extern void
+ImagingUnpackYCCA(UINT8 *out, const UINT8 *in, int pixels);
+
+extern void
+ImagingConvertRGB2YCbCr(UINT8 *out, const UINT8 *in, int pixels);
+extern void
+ImagingConvertYCbCr2RGB(UINT8 *out, const UINT8 *in, int pixels);
+
+extern ImagingShuffler
+ImagingFindUnpacker(const char *mode, const char *rawmode, int *bits_out);
+extern ImagingShuffler
+ImagingFindPacker(const char *mode, const char *rawmode, int *bits_out);
+
+struct ImagingCodecStateInstance {
+ int count;
+ int state;
+ int errcode;
+ int x, y;
+ int ystep;
+ int xsize, ysize, xoff, yoff;
+ ImagingShuffler shuffle;
+ int bits, bytes;
+ UINT8 *buffer;
+ void *context;
+ PyObject *fd;
+};
+
+/* Codec read/write python fd */
+extern Py_ssize_t
+_imaging_read_pyFd(PyObject *fd, char *dest, Py_ssize_t bytes);
+extern Py_ssize_t
+_imaging_write_pyFd(PyObject *fd, char *src, Py_ssize_t bytes);
+extern int
+_imaging_seek_pyFd(PyObject *fd, Py_ssize_t offset, int whence);
+extern Py_ssize_t
+_imaging_tell_pyFd(PyObject *fd);
+
+/* Errcodes */
+#define IMAGING_CODEC_END 1
+#define IMAGING_CODEC_OVERRUN -1
+#define IMAGING_CODEC_BROKEN -2
+#define IMAGING_CODEC_UNKNOWN -3
+#define IMAGING_CODEC_CONFIG -8
+#define IMAGING_CODEC_MEMORY -9
+
+#include "ImagingUtils.h"
+extern UINT8 *clip8_lookups;
+
+#if defined(__cplusplus)
+}
+#endif
diff --git a/contrib/python/Pillow/py3/libImaging/ImagingUtils.h b/contrib/python/Pillow/py3/libImaging/ImagingUtils.h
new file mode 100644
index 00000000000..0c0c1eda917
--- /dev/null
+++ b/contrib/python/Pillow/py3/libImaging/ImagingUtils.h
@@ -0,0 +1,42 @@
+#ifdef WORDS_BIGENDIAN
+#define MAKE_UINT32(u0, u1, u2, u3) \
+ ((UINT32)(u3) | ((UINT32)(u2) << 8) | ((UINT32)(u1) << 16) | ((UINT32)(u0) << 24))
+#define MASK_UINT32_CHANNEL_0 0xff000000
+#define MASK_UINT32_CHANNEL_1 0x00ff0000
+#define MASK_UINT32_CHANNEL_2 0x0000ff00
+#define MASK_UINT32_CHANNEL_3 0x000000ff
+#else
+#define MAKE_UINT32(u0, u1, u2, u3) \
+ ((UINT32)(u0) | ((UINT32)(u1) << 8) | ((UINT32)(u2) << 16) | ((UINT32)(u3) << 24))
+#define MASK_UINT32_CHANNEL_0 0x000000ff
+#define MASK_UINT32_CHANNEL_1 0x0000ff00
+#define MASK_UINT32_CHANNEL_2 0x00ff0000
+#define MASK_UINT32_CHANNEL_3 0xff000000
+#endif
+
+#define SHIFTFORDIV255(a) ((((a) >> 8) + a) >> 8)
+
+/* like (a * b + 127) / 255), but much faster on most platforms */
+#define MULDIV255(a, b, tmp) (tmp = (a) * (b) + 128, SHIFTFORDIV255(tmp))
+
+#define DIV255(a, tmp) (tmp = (a) + 128, SHIFTFORDIV255(tmp))
+
+#define BLEND(mask, in1, in2, tmp1) DIV255(in1 *(255 - mask) + in2 * mask, tmp1)
+
+#define PREBLEND(mask, in1, in2, tmp1) (MULDIV255(in1, (255 - mask), tmp1) + in2)
+
+#define CLIP8(v) ((v) <= 0 ? 0 : (v) < 256 ? (v) : 255)
+
+/* This is to work around a bug in GCC prior 4.9 in 64 bit mode.
+ GCC generates code with partial dependency which is 3 times slower.
+ See: https://stackoverflow.com/a/26588074/253146 */
+#if defined(__x86_64__) && defined(__SSE__) && !defined(__NO_INLINE__) && \
+ !defined(__clang__) && defined(GCC_VERSION) && (GCC_VERSION < 40900)
+static float __attribute__((always_inline)) inline _i2f(int v) {
+ float x;
+ __asm__("xorps %0, %0; cvtsi2ss %1, %0" : "=x"(x) : "r"(v));
+ return x;
+}
+#else
+static float inline _i2f(int v) { return (float)v; }
+#endif
diff --git a/contrib/python/Pillow/py3/libImaging/Jpeg.h b/contrib/python/Pillow/py3/libImaging/Jpeg.h
new file mode 100644
index 00000000000..1d755081871
--- /dev/null
+++ b/contrib/python/Pillow/py3/libImaging/Jpeg.h
@@ -0,0 +1,116 @@
+/*
+ * The Python Imaging Library.
+ * $Id$
+ *
+ * declarations for the IJG JPEG codec interface.
+ *
+ * Copyright (c) 1995-2001 by Secret Labs AB
+ * Copyright (c) 1995-1996 by Fredrik Lundh
+ */
+
+#include "jpeglib.h"
+
+#include <setjmp.h>
+
+typedef struct {
+ struct jpeg_error_mgr pub; /* "public" fields */
+ jmp_buf setjmp_buffer; /* for return to caller */
+} JPEGERROR;
+
+/* -------------------------------------------------------------------- */
+/* Decoder */
+
+typedef struct {
+ struct jpeg_source_mgr pub;
+ int skip;
+} JPEGSOURCE;
+
+typedef struct {
+ /* CONFIGURATION */
+
+ /* Jpeg file mode (empty if not known) */
+ char jpegmode[8 + 1];
+
+ /* Converter output mode (input to the shuffler). If empty,
+ convert conversions are disabled */
+ char rawmode[8 + 1];
+
+ /* If set, trade quality for speed */
+ int draft;
+
+ /* Scale factor (1, 2, 4, 8) */
+ int scale;
+
+ /* PRIVATE CONTEXT (set by decoder) */
+
+ struct jpeg_decompress_struct cinfo;
+
+ JPEGERROR error;
+
+ JPEGSOURCE source;
+
+} JPEGSTATE;
+
+/* -------------------------------------------------------------------- */
+/* Encoder */
+
+typedef struct {
+ struct jpeg_destination_mgr pub;
+ /* might add something some other day */
+} JPEGDESTINATION;
+
+typedef struct {
+ /* CONFIGURATION */
+
+ /* Quality (0-100, -1 means default) */
+ int quality;
+
+ /* Progressive mode */
+ int progressive;
+
+ /* Smoothing factor (1-100, 0 means none) */
+ int smooth;
+
+ /* Optimize Huffman tables (slow) */
+ int optimize;
+
+ /* Stream type (0=full, 1=tables only, 2=image only) */
+ int streamtype;
+
+ /* DPI setting (0=square pixels, otherwise DPI) */
+ int xdpi, ydpi;
+
+ /* Chroma Subsampling (-1=default, 0=none, 1=medium, 2=high) */
+ int subsampling;
+
+ /* Converter input mode (input to the shuffler) */
+ char rawmode[8 + 1];
+
+ /* Custom quantization tables () */
+ unsigned int *qtables;
+
+ /* in factors of DCTSIZE2 */
+ int qtablesLen;
+
+ /* Comment */
+ char *comment;
+ size_t comment_size;
+
+ /* Extra data (to be injected after header) */
+ char *extra;
+ int extra_size;
+
+ /* PRIVATE CONTEXT (set by encoder) */
+
+ struct jpeg_compress_struct cinfo;
+
+ JPEGERROR error;
+
+ JPEGDESTINATION destination;
+
+ int extra_offset;
+
+ size_t rawExifLen; /* EXIF data length */
+ char *rawExif; /* EXIF buffer pointer */
+
+} JPEGENCODERSTATE;
diff --git a/contrib/python/Pillow/py3/libImaging/Jpeg2K.h b/contrib/python/Pillow/py3/libImaging/Jpeg2K.h
new file mode 100644
index 00000000000..e8d92f7b6bc
--- /dev/null
+++ b/contrib/python/Pillow/py3/libImaging/Jpeg2K.h
@@ -0,0 +1,113 @@
+/*
+ * The Python Imaging Library
+ * $Id$
+ *
+ * declarations for the OpenJPEG codec interface.
+ *
+ * Copyright (c) 2014 by Coriolis Systems Limited
+ * Copyright (c) 2014 by Alastair Houghton
+ */
+
+#include <openjpeg.h>
+
+/* 1MB for now */
+#define BUFFER_SIZE OPJ_J2K_STREAM_CHUNK_SIZE
+
+/* -------------------------------------------------------------------- */
+/* Decoder */
+/* -------------------------------------------------------------------- */
+
+typedef struct {
+ /* CONFIGURATION */
+
+ /* File descriptor, if available; otherwise, -1 */
+ int fd;
+
+ /* File pointer, when opened */
+ FILE *pfile;
+
+ /* Length of data, if available; otherwise, -1 */
+ off_t length;
+
+ /* Specify the desired format */
+ OPJ_CODEC_FORMAT format;
+
+ /* Set to divide image resolution by 2**reduce. */
+ int reduce;
+
+ /* Set to limit the number of quality layers to decode (0 = all layers) */
+ int layers;
+
+ /* PRIVATE CONTEXT (set by decoder) */
+ const char *error_msg;
+
+} JPEG2KDECODESTATE;
+
+/* -------------------------------------------------------------------- */
+/* Encoder */
+/* -------------------------------------------------------------------- */
+
+typedef struct {
+ /* CONFIGURATION */
+
+ /* File descriptor, if available; otherwise, -1 */
+ int fd;
+
+ /* File pointer, when opened */
+ FILE *pfile;
+
+ /* Specify the desired format */
+ OPJ_CODEC_FORMAT format;
+
+ /* Image offset */
+ int offset_x, offset_y;
+
+ /* Tile information */
+ int tile_offset_x, tile_offset_y;
+ int tile_size_x, tile_size_y;
+
+ /* Quality layers (a sequence of numbers giving *either* rates or dB) */
+ int quality_is_in_db;
+ PyObject *quality_layers;
+
+ /* Number of resolutions (DWT decompositions + 1 */
+ int num_resolutions;
+
+ /* Code block size */
+ int cblk_width, cblk_height;
+
+ /* Precinct size */
+ int precinct_width, precinct_height;
+
+ /* Compression style */
+ int irreversible;
+
+ /* Set multiple component transformation */
+ char mct;
+
+ /* Signed */
+ int sgnd;
+
+ /* Progression order (LRCP/RLCP/RPCL/PCRL/CPRL) */
+ OPJ_PROG_ORDER progression;
+
+ /* Cinema mode */
+ OPJ_CINEMA_MODE cinema_mode;
+
+ /* PRIVATE CONTEXT (set by decoder) */
+ const char *error_msg;
+
+ /* Custom comment */
+ char *comment;
+
+ /* Include PLT marker segment */
+ int plt;
+
+} JPEG2KENCODESTATE;
+
+/*
+ * Local Variables:
+ * c-basic-offset: 4
+ * End:
+ *
+ */
diff --git a/contrib/python/Pillow/py3/libImaging/Jpeg2KDecode.c b/contrib/python/Pillow/py3/libImaging/Jpeg2KDecode.c
new file mode 100644
index 00000000000..cff30e2d0bf
--- /dev/null
+++ b/contrib/python/Pillow/py3/libImaging/Jpeg2KDecode.c
@@ -0,0 +1,999 @@
+/*
+ * The Python Imaging Library.
+ * $Id$
+ *
+ * decoder for JPEG2000 image data.
+ *
+ * history:
+ * 2014-03-12 ajh Created
+ *
+ * Copyright (c) 2014 Coriolis Systems Limited
+ * Copyright (c) 2014 Alastair Houghton
+ *
+ * See the README file for details on usage and redistribution.
+ */
+
+#include "Imaging.h"
+
+#ifdef HAVE_OPENJPEG
+
+#include <stdlib.h>
+#include "Jpeg2K.h"
+
+typedef struct {
+ OPJ_UINT32 tile_index;
+ OPJ_UINT32 data_size;
+ OPJ_INT32 x0, y0, x1, y1;
+ OPJ_UINT32 nb_comps;
+} JPEG2KTILEINFO;
+
+/* -------------------------------------------------------------------- */
+/* Error handler */
+/* -------------------------------------------------------------------- */
+
+static void
+j2k_error(const char *msg, void *client_data) {
+ JPEG2KDECODESTATE *state = (JPEG2KDECODESTATE *)client_data;
+ free((void *)state->error_msg);
+ state->error_msg = strdup(msg);
+}
+
+/* -------------------------------------------------------------------- */
+/* Buffer input stream */
+/* -------------------------------------------------------------------- */
+
+static OPJ_SIZE_T
+j2k_read(void *p_buffer, OPJ_SIZE_T p_nb_bytes, void *p_user_data) {
+ ImagingCodecState state = (ImagingCodecState)p_user_data;
+
+ size_t len = _imaging_read_pyFd(state->fd, p_buffer, p_nb_bytes);
+
+ return len ? len : (OPJ_SIZE_T)-1;
+}
+
+static OPJ_OFF_T
+j2k_skip(OPJ_OFF_T p_nb_bytes, void *p_user_data) {
+ off_t pos;
+ ImagingCodecState state = (ImagingCodecState)p_user_data;
+
+ _imaging_seek_pyFd(state->fd, p_nb_bytes, SEEK_CUR);
+ pos = _imaging_tell_pyFd(state->fd);
+
+ return pos ? pos : (OPJ_OFF_T)-1;
+}
+
+/* -------------------------------------------------------------------- */
+/* Unpackers */
+/* -------------------------------------------------------------------- */
+
+typedef void (*j2k_unpacker_t)(
+ opj_image_t *in, const JPEG2KTILEINFO *tileInfo, const UINT8 *data, Imaging im);
+
+struct j2k_decode_unpacker {
+ const char *mode;
+ OPJ_COLOR_SPACE color_space;
+ unsigned components;
+ /* bool indicating if unpacker supports subsampling */
+ int subsampling;
+ j2k_unpacker_t unpacker;
+};
+
+static inline unsigned
+j2ku_shift(unsigned x, int n) {
+ if (n < 0) {
+ return x >> -n;
+ } else {
+ return x << n;
+ }
+}
+
+static void
+j2ku_gray_l(
+ opj_image_t *in,
+ const JPEG2KTILEINFO *tileinfo,
+ const UINT8 *tiledata,
+ Imaging im) {
+ unsigned x0 = tileinfo->x0 - in->x0, y0 = tileinfo->y0 - in->y0;
+ unsigned w = tileinfo->x1 - tileinfo->x0;
+ unsigned h = tileinfo->y1 - tileinfo->y0;
+
+ int shift = 8 - in->comps[0].prec;
+ int offset = in->comps[0].sgnd ? 1 << (in->comps[0].prec - 1) : 0;
+ int csiz = (in->comps[0].prec + 7) >> 3;
+
+ unsigned x, y;
+
+ if (csiz == 3) {
+ csiz = 4;
+ }
+
+ if (shift < 0) {
+ offset += 1 << (-shift - 1);
+ }
+
+ /* csiz*h*w + offset = tileinfo.datasize */
+ switch (csiz) {
+ case 1:
+ for (y = 0; y < h; ++y) {
+ const UINT8 *data = &tiledata[y * w];
+ UINT8 *row = (UINT8 *)im->image[y0 + y] + x0;
+ for (x = 0; x < w; ++x) {
+ *row++ = j2ku_shift(offset + *data++, shift);
+ }
+ }
+ break;
+ case 2:
+ for (y = 0; y < h; ++y) {
+ const UINT16 *data = (const UINT16 *)&tiledata[2 * y * w];
+ UINT8 *row = (UINT8 *)im->image[y0 + y] + x0;
+ for (x = 0; x < w; ++x) {
+ *row++ = j2ku_shift(offset + *data++, shift);
+ }
+ }
+ break;
+ case 4:
+ for (y = 0; y < h; ++y) {
+ const UINT32 *data = (const UINT32 *)&tiledata[4 * y * w];
+ UINT8 *row = (UINT8 *)im->image[y0 + y] + x0;
+ for (x = 0; x < w; ++x) {
+ *row++ = j2ku_shift(offset + *data++, shift);
+ }
+ }
+ break;
+ }
+}
+
+static void
+j2ku_gray_i(
+ opj_image_t *in,
+ const JPEG2KTILEINFO *tileinfo,
+ const UINT8 *tiledata,
+ Imaging im) {
+ unsigned x0 = tileinfo->x0 - in->x0, y0 = tileinfo->y0 - in->y0;
+ unsigned w = tileinfo->x1 - tileinfo->x0;
+ unsigned h = tileinfo->y1 - tileinfo->y0;
+
+ int shift = 16 - in->comps[0].prec;
+ int offset = in->comps[0].sgnd ? 1 << (in->comps[0].prec - 1) : 0;
+ int csiz = (in->comps[0].prec + 7) >> 3;
+
+ unsigned x, y;
+
+ if (csiz == 3) {
+ csiz = 4;
+ }
+
+ if (shift < 0) {
+ offset += 1 << (-shift - 1);
+ }
+
+ switch (csiz) {
+ case 1:
+ for (y = 0; y < h; ++y) {
+ const UINT8 *data = &tiledata[y * w];
+ UINT16 *row = (UINT16 *)im->image[y0 + y] + x0;
+ for (x = 0; x < w; ++x) {
+ *row++ = j2ku_shift(offset + *data++, shift);
+ }
+ }
+ break;
+ case 2:
+ for (y = 0; y < h; ++y) {
+ const UINT16 *data = (const UINT16 *)&tiledata[2 * y * w];
+ UINT16 *row = (UINT16 *)im->image[y0 + y] + x0;
+ for (x = 0; x < w; ++x) {
+ UINT16 pixel = j2ku_shift(offset + *data++, shift);
+ #ifdef WORDS_BIGENDIAN
+ pixel = (pixel >> 8) | (pixel << 8);
+ #endif
+ *row++ = pixel;
+ }
+ }
+ break;
+ case 4:
+ for (y = 0; y < h; ++y) {
+ const UINT32 *data = (const UINT32 *)&tiledata[4 * y * w];
+ UINT16 *row = (UINT16 *)im->image[y0 + y] + x0;
+ for (x = 0; x < w; ++x) {
+ *row++ = j2ku_shift(offset + *data++, shift);
+ }
+ }
+ break;
+ }
+}
+
+static void
+j2ku_gray_rgb(
+ opj_image_t *in,
+ const JPEG2KTILEINFO *tileinfo,
+ const UINT8 *tiledata,
+ Imaging im) {
+ unsigned x0 = tileinfo->x0 - in->x0, y0 = tileinfo->y0 - in->y0;
+ unsigned w = tileinfo->x1 - tileinfo->x0;
+ unsigned h = tileinfo->y1 - tileinfo->y0;
+
+ int shift = 8 - in->comps[0].prec;
+ int offset = in->comps[0].sgnd ? 1 << (in->comps[0].prec - 1) : 0;
+ int csiz = (in->comps[0].prec + 7) >> 3;
+
+ unsigned x, y;
+
+ if (shift < 0) {
+ offset += 1 << (-shift - 1);
+ }
+
+ if (csiz == 3) {
+ csiz = 4;
+ }
+
+ switch (csiz) {
+ case 1:
+ for (y = 0; y < h; ++y) {
+ const UINT8 *data = &tiledata[y * w];
+ UINT8 *row = (UINT8 *)im->image[y0 + y] + x0;
+ for (x = 0; x < w; ++x) {
+ UINT8 byte = j2ku_shift(offset + *data++, shift);
+ row[0] = row[1] = row[2] = byte;
+ row[3] = 0xff;
+ row += 4;
+ }
+ }
+ break;
+ case 2:
+ for (y = 0; y < h; ++y) {
+ const UINT16 *data = (UINT16 *)&tiledata[2 * y * w];
+ UINT8 *row = (UINT8 *)im->image[y0 + y] + x0;
+ for (x = 0; x < w; ++x) {
+ UINT8 byte = j2ku_shift(offset + *data++, shift);
+ row[0] = row[1] = row[2] = byte;
+ row[3] = 0xff;
+ row += 4;
+ }
+ }
+ break;
+ case 4:
+ for (y = 0; y < h; ++y) {
+ const UINT32 *data = (UINT32 *)&tiledata[4 * y * w];
+ UINT8 *row = (UINT8 *)im->image[y0 + y] + x0;
+ for (x = 0; x < w; ++x) {
+ UINT8 byte = j2ku_shift(offset + *data++, shift);
+ row[0] = row[1] = row[2] = byte;
+ row[3] = 0xff;
+ row += 4;
+ }
+ }
+ break;
+ }
+}
+
+static void
+j2ku_graya_la(
+ opj_image_t *in,
+ const JPEG2KTILEINFO *tileinfo,
+ const UINT8 *tiledata,
+ Imaging im) {
+ unsigned x0 = tileinfo->x0 - in->x0, y0 = tileinfo->y0 - in->y0;
+ unsigned w = tileinfo->x1 - tileinfo->x0;
+ unsigned h = tileinfo->y1 - tileinfo->y0;
+
+ int shift = 8 - in->comps[0].prec;
+ int offset = in->comps[0].sgnd ? 1 << (in->comps[0].prec - 1) : 0;
+ int csiz = (in->comps[0].prec + 7) >> 3;
+ int ashift = 8 - in->comps[1].prec;
+ int aoffset = in->comps[1].sgnd ? 1 << (in->comps[1].prec - 1) : 0;
+ int acsiz = (in->comps[1].prec + 7) >> 3;
+ const UINT8 *atiledata;
+
+ unsigned x, y;
+
+ if (csiz == 3) {
+ csiz = 4;
+ }
+ if (acsiz == 3) {
+ acsiz = 4;
+ }
+
+ if (shift < 0) {
+ offset += 1 << (-shift - 1);
+ }
+ if (ashift < 0) {
+ aoffset += 1 << (-ashift - 1);
+ }
+
+ atiledata = tiledata + csiz * w * h;
+
+ for (y = 0; y < h; ++y) {
+ const UINT8 *data = &tiledata[csiz * y * w];
+ const UINT8 *adata = &atiledata[acsiz * y * w];
+ UINT8 *row = (UINT8 *)im->image[y0 + y] + x0 * 4;
+ for (x = 0; x < w; ++x) {
+ UINT32 word = 0, aword = 0, byte;
+
+ switch (csiz) {
+ case 1:
+ word = *data++;
+ break;
+ case 2:
+ word = *(const UINT16 *)data;
+ data += 2;
+ break;
+ case 4:
+ word = *(const UINT32 *)data;
+ data += 4;
+ break;
+ }
+
+ switch (acsiz) {
+ case 1:
+ aword = *adata++;
+ break;
+ case 2:
+ aword = *(const UINT16 *)adata;
+ adata += 2;
+ break;
+ case 4:
+ aword = *(const UINT32 *)adata;
+ adata += 4;
+ break;
+ }
+
+ byte = j2ku_shift(offset + word, shift);
+ row[0] = row[1] = row[2] = byte;
+ row[3] = j2ku_shift(aoffset + aword, ashift);
+ row += 4;
+ }
+ }
+}
+
+static void
+j2ku_srgb_rgb(
+ opj_image_t *in,
+ const JPEG2KTILEINFO *tileinfo,
+ const UINT8 *tiledata,
+ Imaging im) {
+ unsigned x0 = tileinfo->x0 - in->x0, y0 = tileinfo->y0 - in->y0;
+ unsigned w = tileinfo->x1 - tileinfo->x0;
+ unsigned h = tileinfo->y1 - tileinfo->y0;
+
+ int shifts[3], offsets[3], csiz[3];
+ unsigned dx[3], dy[3];
+ const UINT8 *cdata[3];
+ const UINT8 *cptr = tiledata;
+ unsigned n, x, y;
+
+ for (n = 0; n < 3; ++n) {
+ cdata[n] = cptr;
+ shifts[n] = 8 - in->comps[n].prec;
+ offsets[n] = in->comps[n].sgnd ? 1 << (in->comps[n].prec - 1) : 0;
+ csiz[n] = (in->comps[n].prec + 7) >> 3;
+ dx[n] = (in->comps[n].dx);
+ dy[n] = (in->comps[n].dy);
+
+ if (csiz[n] == 3) {
+ csiz[n] = 4;
+ }
+
+ if (shifts[n] < 0) {
+ offsets[n] += 1 << (-shifts[n] - 1);
+ }
+
+ cptr += csiz[n] * (w / dx[n]) * (h / dy[n]);
+ }
+
+ for (y = 0; y < h; ++y) {
+ const UINT8 *data[3];
+ UINT8 *row = (UINT8 *)im->image[y0 + y] + x0 * 4;
+ for (n = 0; n < 3; ++n) {
+ data[n] = &cdata[n][csiz[n] * (y / dy[n]) * (w / dx[n])];
+ }
+
+ for (x = 0; x < w; ++x) {
+ for (n = 0; n < 3; ++n) {
+ UINT32 word = 0;
+
+ switch (csiz[n]) {
+ case 1:
+ word = data[n][x / dx[n]];
+ break;
+ case 2:
+ word = ((const UINT16 *)data[n])[x / dx[n]];
+ break;
+ case 4:
+ word = ((const UINT32 *)data[n])[x / dx[n]];
+ break;
+ }
+
+ row[n] = j2ku_shift(offsets[n] + word, shifts[n]);
+ }
+ row[3] = 0xff;
+ row += 4;
+ }
+ }
+}
+
+static void
+j2ku_sycc_rgb(
+ opj_image_t *in,
+ const JPEG2KTILEINFO *tileinfo,
+ const UINT8 *tiledata,
+ Imaging im) {
+ unsigned x0 = tileinfo->x0 - in->x0, y0 = tileinfo->y0 - in->y0;
+ unsigned w = tileinfo->x1 - tileinfo->x0;
+ unsigned h = tileinfo->y1 - tileinfo->y0;
+
+ int shifts[3], offsets[3], csiz[3];
+ unsigned dx[3], dy[3];
+ const UINT8 *cdata[3];
+ const UINT8 *cptr = tiledata;
+ unsigned n, x, y;
+
+ for (n = 0; n < 3; ++n) {
+ cdata[n] = cptr;
+ shifts[n] = 8 - in->comps[n].prec;
+ offsets[n] = in->comps[n].sgnd ? 1 << (in->comps[n].prec - 1) : 0;
+ csiz[n] = (in->comps[n].prec + 7) >> 3;
+ dx[n] = (in->comps[n].dx);
+ dy[n] = (in->comps[n].dy);
+
+ if (csiz[n] == 3) {
+ csiz[n] = 4;
+ }
+
+ if (shifts[n] < 0) {
+ offsets[n] += 1 << (-shifts[n] - 1);
+ }
+
+ cptr += csiz[n] * (w / dx[n]) * (h / dy[n]);
+ }
+
+ for (y = 0; y < h; ++y) {
+ const UINT8 *data[3];
+ UINT8 *row = (UINT8 *)im->image[y0 + y] + x0 * 4;
+ UINT8 *row_start = row;
+ for (n = 0; n < 3; ++n) {
+ data[n] = &cdata[n][csiz[n] * (y / dy[n]) * (w / dx[n])];
+ }
+
+ for (x = 0; x < w; ++x) {
+ for (n = 0; n < 3; ++n) {
+ UINT32 word = 0;
+
+ switch (csiz[n]) {
+ case 1:
+ word = data[n][x / dx[n]];
+ break;
+ case 2:
+ word = ((const UINT16 *)data[n])[x / dx[n]];
+ break;
+ case 4:
+ word = ((const UINT32 *)data[n])[x / dx[n]];
+ break;
+ }
+
+ row[n] = j2ku_shift(offsets[n] + word, shifts[n]);
+ }
+ row[3] = 0xff;
+ row += 4;
+ }
+
+ ImagingConvertYCbCr2RGB(row_start, row_start, w);
+ }
+}
+
+static void
+j2ku_srgba_rgba(
+ opj_image_t *in,
+ const JPEG2KTILEINFO *tileinfo,
+ const UINT8 *tiledata,
+ Imaging im) {
+ unsigned x0 = tileinfo->x0 - in->x0, y0 = tileinfo->y0 - in->y0;
+ unsigned w = tileinfo->x1 - tileinfo->x0;
+ unsigned h = tileinfo->y1 - tileinfo->y0;
+
+ int shifts[4], offsets[4], csiz[4];
+ unsigned dx[4], dy[4];
+ const UINT8 *cdata[4];
+ const UINT8 *cptr = tiledata;
+ unsigned n, x, y;
+
+ for (n = 0; n < 4; ++n) {
+ cdata[n] = cptr;
+ shifts[n] = 8 - in->comps[n].prec;
+ offsets[n] = in->comps[n].sgnd ? 1 << (in->comps[n].prec - 1) : 0;
+ csiz[n] = (in->comps[n].prec + 7) >> 3;
+ dx[n] = (in->comps[n].dx);
+ dy[n] = (in->comps[n].dy);
+
+ if (csiz[n] == 3) {
+ csiz[n] = 4;
+ }
+
+ if (shifts[n] < 0) {
+ offsets[n] += 1 << (-shifts[n] - 1);
+ }
+
+ cptr += csiz[n] * (w / dx[n]) * (h / dy[n]);
+ }
+
+ for (y = 0; y < h; ++y) {
+ const UINT8 *data[4];
+ UINT8 *row = (UINT8 *)im->image[y0 + y] + x0 * 4;
+ for (n = 0; n < 4; ++n) {
+ data[n] = &cdata[n][csiz[n] * (y / dy[n]) * (w / dx[n])];
+ }
+
+ for (x = 0; x < w; ++x) {
+ for (n = 0; n < 4; ++n) {
+ UINT32 word = 0;
+
+ switch (csiz[n]) {
+ case 1:
+ word = data[n][x / dx[n]];
+ break;
+ case 2:
+ word = ((const UINT16 *)data[n])[x / dx[n]];
+ break;
+ case 4:
+ word = ((const UINT32 *)data[n])[x / dx[n]];
+ break;
+ }
+
+ row[n] = j2ku_shift(offsets[n] + word, shifts[n]);
+ }
+ row += 4;
+ }
+ }
+}
+
+static void
+j2ku_sycca_rgba(
+ opj_image_t *in,
+ const JPEG2KTILEINFO *tileinfo,
+ const UINT8 *tiledata,
+ Imaging im) {
+ unsigned x0 = tileinfo->x0 - in->x0, y0 = tileinfo->y0 - in->y0;
+ unsigned w = tileinfo->x1 - tileinfo->x0;
+ unsigned h = tileinfo->y1 - tileinfo->y0;
+
+ int shifts[4], offsets[4], csiz[4];
+ unsigned dx[4], dy[4];
+ const UINT8 *cdata[4];
+ const UINT8 *cptr = tiledata;
+ unsigned n, x, y;
+
+ for (n = 0; n < 4; ++n) {
+ cdata[n] = cptr;
+ shifts[n] = 8 - in->comps[n].prec;
+ offsets[n] = in->comps[n].sgnd ? 1 << (in->comps[n].prec - 1) : 0;
+ csiz[n] = (in->comps[n].prec + 7) >> 3;
+ dx[n] = (in->comps[n].dx);
+ dy[n] = (in->comps[n].dy);
+
+ if (csiz[n] == 3) {
+ csiz[n] = 4;
+ }
+
+ if (shifts[n] < 0) {
+ offsets[n] += 1 << (-shifts[n] - 1);
+ }
+
+ cptr += csiz[n] * (w / dx[n]) * (h / dy[n]);
+ }
+
+ for (y = 0; y < h; ++y) {
+ const UINT8 *data[4];
+ UINT8 *row = (UINT8 *)im->image[y0 + y] + x0 * 4;
+ UINT8 *row_start = row;
+ for (n = 0; n < 4; ++n) {
+ data[n] = &cdata[n][csiz[n] * (y / dy[n]) * (w / dx[n])];
+ }
+
+ for (x = 0; x < w; ++x) {
+ for (n = 0; n < 4; ++n) {
+ UINT32 word = 0;
+
+ switch (csiz[n]) {
+ case 1:
+ word = data[n][x / dx[n]];
+ break;
+ case 2:
+ word = ((const UINT16 *)data[n])[x / dx[n]];
+ break;
+ case 4:
+ word = ((const UINT32 *)data[n])[x / dx[n]];
+ break;
+ }
+
+ row[n] = j2ku_shift(offsets[n] + word, shifts[n]);
+ }
+ row += 4;
+ }
+
+ ImagingConvertYCbCr2RGB(row_start, row_start, w);
+ }
+}
+
+static const struct j2k_decode_unpacker j2k_unpackers[] = {
+ {"L", OPJ_CLRSPC_GRAY, 1, 0, j2ku_gray_l},
+ {"I;16", OPJ_CLRSPC_GRAY, 1, 0, j2ku_gray_i},
+ {"I;16B", OPJ_CLRSPC_GRAY, 1, 0, j2ku_gray_i},
+ {"LA", OPJ_CLRSPC_GRAY, 2, 0, j2ku_graya_la},
+ {"RGB", OPJ_CLRSPC_GRAY, 1, 0, j2ku_gray_rgb},
+ {"RGB", OPJ_CLRSPC_GRAY, 2, 0, j2ku_gray_rgb},
+ {"RGB", OPJ_CLRSPC_SRGB, 3, 1, j2ku_srgb_rgb},
+ {"RGB", OPJ_CLRSPC_SYCC, 3, 1, j2ku_sycc_rgb},
+ {"RGB", OPJ_CLRSPC_SRGB, 4, 1, j2ku_srgb_rgb},
+ {"RGB", OPJ_CLRSPC_SYCC, 4, 1, j2ku_sycc_rgb},
+ {"RGBA", OPJ_CLRSPC_GRAY, 1, 0, j2ku_gray_rgb},
+ {"RGBA", OPJ_CLRSPC_GRAY, 2, 0, j2ku_graya_la},
+ {"RGBA", OPJ_CLRSPC_SRGB, 3, 1, j2ku_srgb_rgb},
+ {"RGBA", OPJ_CLRSPC_SYCC, 3, 1, j2ku_sycc_rgb},
+ {"RGBA", OPJ_CLRSPC_SRGB, 4, 1, j2ku_srgba_rgba},
+ {"RGBA", OPJ_CLRSPC_SYCC, 4, 1, j2ku_sycca_rgba},
+};
+
+/* -------------------------------------------------------------------- */
+/* Decoder */
+/* -------------------------------------------------------------------- */
+
+enum {
+ J2K_STATE_START = 0,
+ J2K_STATE_DECODING = 1,
+ J2K_STATE_DONE = 2,
+ J2K_STATE_FAILED = 3,
+};
+
+static int
+j2k_decode_entry(Imaging im, ImagingCodecState state) {
+ JPEG2KDECODESTATE *context = (JPEG2KDECODESTATE *)state->context;
+ opj_stream_t *stream = NULL;
+ opj_image_t *image = NULL;
+ opj_codec_t *codec = NULL;
+ opj_dparameters_t params;
+ OPJ_COLOR_SPACE color_space;
+ j2k_unpacker_t unpack = NULL;
+ size_t buffer_size = 0, tile_bytes = 0;
+ unsigned n, tile_height, tile_width;
+ int subsampling;
+ int total_component_width = 0;
+
+ stream = opj_stream_create(BUFFER_SIZE, OPJ_TRUE);
+
+ if (!stream) {
+ state->errcode = IMAGING_CODEC_BROKEN;
+ state->state = J2K_STATE_FAILED;
+ goto quick_exit;
+ }
+
+ opj_stream_set_read_function(stream, j2k_read);
+ opj_stream_set_skip_function(stream, j2k_skip);
+
+ /* OpenJPEG 2.0 doesn't have OPJ_VERSION_MAJOR */
+#ifndef OPJ_VERSION_MAJOR
+ opj_stream_set_user_data(stream, state);
+#else
+ opj_stream_set_user_data(stream, state, NULL);
+
+ /* Hack: if we don't know the length, the largest file we can
+ possibly support is 4GB. We can't go larger than this, because
+ OpenJPEG truncates this value for the final box in the file, and
+ the box lengths in OpenJPEG are currently 32 bit. */
+ if (context->length < 0) {
+ opj_stream_set_user_data_length(stream, 0xffffffff);
+ } else {
+ opj_stream_set_user_data_length(stream, context->length);
+ }
+#endif
+
+ /* Setup decompression context */
+ context->error_msg = NULL;
+
+ opj_set_default_decoder_parameters(&params);
+ params.cp_reduce = context->reduce;
+ params.cp_layer = context->layers;
+
+ codec = opj_create_decompress(context->format);
+
+ if (!codec) {
+ state->errcode = IMAGING_CODEC_BROKEN;
+ state->state = J2K_STATE_FAILED;
+ goto quick_exit;
+ }
+
+ opj_set_error_handler(codec, j2k_error, context);
+ opj_setup_decoder(codec, &params);
+
+ if (!opj_read_header(stream, codec, &image)) {
+ state->errcode = IMAGING_CODEC_BROKEN;
+ state->state = J2K_STATE_FAILED;
+ goto quick_exit;
+ }
+
+ /* Check that this image is something we can handle */
+ if (image->numcomps < 1 || image->numcomps > 4 ||
+ image->color_space == OPJ_CLRSPC_UNKNOWN) {
+ state->errcode = IMAGING_CODEC_BROKEN;
+ state->state = J2K_STATE_FAILED;
+ goto quick_exit;
+ }
+
+ /*
+ * Find first component with subsampling.
+ *
+ * This is a heuristic to determine the colorspace if unspecified.
+ */
+ subsampling = -1;
+ for (n = 0; n < image->numcomps; ++n) {
+ if (image->comps[n].dx != 1 || image->comps[n].dy != 1) {
+ subsampling = n;
+ break;
+ }
+ }
+
+ /*
+ Colorspace Number of components PIL mode
+ ------------------------------------------------------
+ sRGB 3 RGB
+ sRGB 4 RGBA
+ gray 1 L or I
+ gray 2 LA
+ YCC 3 YCbCr
+
+
+ If colorspace is unspecified, we assume:
+
+ Number of components Subsampling Colorspace
+ -------------------------------------------------------
+ 1 Any gray
+ 2 Any gray (+ alpha)
+ 3 -1, 0 sRGB
+ 3 1, 2 YCbCr
+ 4 -1, 0, 3 sRGB (+ alpha)
+ 4 1, 2 YCbCr (+ alpha)
+
+ */
+
+ /* Find the correct unpacker */
+ color_space = image->color_space;
+
+ if (color_space == OPJ_CLRSPC_UNSPECIFIED) {
+ switch (image->numcomps) {
+ case 1:
+ case 2:
+ color_space = OPJ_CLRSPC_GRAY;
+ break;
+ case 3:
+ case 4:
+ switch (subsampling) {
+ case -1:
+ case 0:
+ case 3:
+ color_space = OPJ_CLRSPC_SRGB;
+ break;
+ case 1:
+ case 2:
+ color_space = OPJ_CLRSPC_SYCC;
+ break;
+ }
+ break;
+ }
+ }
+
+ for (n = 0; n < sizeof(j2k_unpackers) / sizeof(j2k_unpackers[0]); ++n) {
+ if (color_space == j2k_unpackers[n].color_space &&
+ image->numcomps == j2k_unpackers[n].components &&
+ (j2k_unpackers[n].subsampling || (subsampling == -1)) &&
+ strcmp(im->mode, j2k_unpackers[n].mode) == 0) {
+ unpack = j2k_unpackers[n].unpacker;
+ break;
+ }
+ }
+
+ if (!unpack) {
+ state->errcode = IMAGING_CODEC_BROKEN;
+ state->state = J2K_STATE_FAILED;
+ goto quick_exit;
+ }
+
+ /* Decode the image tile-by-tile; this means we only need use as much
+ memory as is required for one tile's worth of components. */
+ for (;;) {
+ JPEG2KTILEINFO tile_info;
+ OPJ_BOOL should_continue;
+ unsigned correction = (1 << params.cp_reduce) - 1;
+
+ if (!opj_read_tile_header(
+ codec,
+ stream,
+ &tile_info.tile_index,
+ &tile_info.data_size,
+ &tile_info.x0,
+ &tile_info.y0,
+ &tile_info.x1,
+ &tile_info.y1,
+ &tile_info.nb_comps,
+ &should_continue)) {
+ state->errcode = IMAGING_CODEC_BROKEN;
+ state->state = J2K_STATE_FAILED;
+ goto quick_exit;
+ }
+
+ if (!should_continue) {
+ break;
+ }
+
+ /* Adjust the tile co-ordinates based on the reduction (OpenJPEG
+ doesn't do this for us) */
+ tile_info.x0 = (tile_info.x0 + correction) >> context->reduce;
+ tile_info.y0 = (tile_info.y0 + correction) >> context->reduce;
+ tile_info.x1 = (tile_info.x1 + correction) >> context->reduce;
+ tile_info.y1 = (tile_info.y1 + correction) >> context->reduce;
+
+ /* Check the tile bounds; if the tile is outside the image area,
+ or if it has a negative width or height (i.e. the coordinates are
+ swapped), bail. */
+ if (tile_info.x0 >= tile_info.x1 || tile_info.y0 >= tile_info.y1 ||
+ tile_info.x0 < 0 || tile_info.y0 < 0 ||
+ (OPJ_UINT32)tile_info.x0 < image->x0 ||
+ (OPJ_UINT32)tile_info.y0 < image->y0 ||
+ (OPJ_INT32)(tile_info.x1 - image->x0) > im->xsize ||
+ (OPJ_INT32)(tile_info.y1 - image->y0) > im->ysize) {
+ state->errcode = IMAGING_CODEC_BROKEN;
+ state->state = J2K_STATE_FAILED;
+ goto quick_exit;
+ }
+
+ if (tile_info.nb_comps != image->numcomps) {
+ state->errcode = IMAGING_CODEC_BROKEN;
+ state->state = J2K_STATE_FAILED;
+ goto quick_exit;
+ }
+
+ /* Sometimes the tile_info.datasize we get back from openjpeg
+ is less than sum(comp_bytes)*w*h, and we overflow in the
+ shuffle stage */
+
+ tile_width = tile_info.x1 - tile_info.x0;
+ tile_height = tile_info.y1 - tile_info.y0;
+
+ /* Total component width = sum (component_width) e.g, it's
+ legal for an la file to have a 1 byte width for l, and 4 for
+ a, and then a malicious file could have a smaller tile_bytes
+ */
+
+ for (n=0; n < tile_info.nb_comps; n++) {
+ // see csize /acsize calcs
+ int csize = (image->comps[n].prec + 7) >> 3;
+ csize = (csize == 3) ? 4 : csize;
+ total_component_width += csize;
+ }
+ if ((tile_width > UINT_MAX / total_component_width) ||
+ (tile_height > UINT_MAX / total_component_width) ||
+ (tile_width > UINT_MAX / (tile_height * total_component_width)) ||
+ (tile_height > UINT_MAX / (tile_width * total_component_width))) {
+ state->errcode = IMAGING_CODEC_BROKEN;
+ state->state = J2K_STATE_FAILED;
+ goto quick_exit;
+ }
+
+ tile_bytes = tile_width * tile_height * total_component_width;
+
+ if (tile_bytes > tile_info.data_size) {
+ tile_info.data_size = tile_bytes;
+ }
+
+ if (buffer_size < tile_info.data_size) {
+ /* malloc check ok, overflow and tile size sanity check above */
+ UINT8 *new = realloc(state->buffer, tile_info.data_size);
+ if (!new) {
+ state->errcode = IMAGING_CODEC_MEMORY;
+ state->state = J2K_STATE_FAILED;
+ goto quick_exit;
+ }
+ /* Undefined behavior, sometimes decode_tile_data doesn't
+ fill the buffer and we do things with it later, leading
+ to valgrind errors. */
+ memset(new, 0, tile_info.data_size);
+ state->buffer = new;
+ buffer_size = tile_info.data_size;
+ }
+
+ if (!opj_decode_tile_data(
+ codec,
+ tile_info.tile_index,
+ (OPJ_BYTE *)state->buffer,
+ tile_info.data_size,
+ stream)) {
+ state->errcode = IMAGING_CODEC_BROKEN;
+ state->state = J2K_STATE_FAILED;
+ goto quick_exit;
+ }
+
+ unpack(image, &tile_info, state->buffer, im);
+ }
+
+ if (!opj_end_decompress(codec, stream)) {
+ state->errcode = IMAGING_CODEC_BROKEN;
+ state->state = J2K_STATE_FAILED;
+ goto quick_exit;
+ }
+
+ state->state = J2K_STATE_DONE;
+ state->errcode = IMAGING_CODEC_END;
+
+ if (context->pfile) {
+ if (fclose(context->pfile)) {
+ context->pfile = NULL;
+ }
+ }
+
+quick_exit:
+ if (codec) {
+ opj_destroy_codec(codec);
+ }
+ if (image) {
+ opj_image_destroy(image);
+ }
+ if (stream) {
+ opj_stream_destroy(stream);
+ }
+
+ return -1;
+}
+
+int
+ImagingJpeg2KDecode(Imaging im, ImagingCodecState state, UINT8 *buf, Py_ssize_t bytes) {
+ if (bytes) {
+ state->errcode = IMAGING_CODEC_BROKEN;
+ state->state = J2K_STATE_FAILED;
+ return -1;
+ }
+
+ if (state->state == J2K_STATE_DONE || state->state == J2K_STATE_FAILED) {
+ return -1;
+ }
+
+ if (state->state == J2K_STATE_START) {
+ state->state = J2K_STATE_DECODING;
+
+ return j2k_decode_entry(im, state);
+ }
+
+ if (state->state == J2K_STATE_DECODING) {
+ state->errcode = IMAGING_CODEC_BROKEN;
+ state->state = J2K_STATE_FAILED;
+ return -1;
+ }
+ return -1;
+}
+
+/* -------------------------------------------------------------------- */
+/* Cleanup */
+/* -------------------------------------------------------------------- */
+
+int
+ImagingJpeg2KDecodeCleanup(ImagingCodecState state) {
+ JPEG2KDECODESTATE *context = (JPEG2KDECODESTATE *)state->context;
+
+ if (context->error_msg) {
+ free((void *)context->error_msg);
+ }
+
+ context->error_msg = NULL;
+
+ return -1;
+}
+
+const char *
+ImagingJpeg2KVersion(void) {
+ return opj_version();
+}
+
+#endif /* HAVE_OPENJPEG */
+
+/*
+ * Local Variables:
+ * c-basic-offset: 4
+ * End:
+ *
+ */
diff --git a/contrib/python/Pillow/py3/libImaging/Jpeg2KEncode.c b/contrib/python/Pillow/py3/libImaging/Jpeg2KEncode.c
new file mode 100644
index 00000000000..3295373fd64
--- /dev/null
+++ b/contrib/python/Pillow/py3/libImaging/Jpeg2KEncode.c
@@ -0,0 +1,659 @@
+/*
+ * The Python Imaging Library.
+ * $Id$
+ *
+ * decoder for JPEG2000 image data.
+ *
+ * history:
+ * 2014-03-12 ajh Created
+ *
+ * Copyright (c) 2014 Coriolis Systems Limited
+ * Copyright (c) 2014 Alastair Houghton
+ *
+ * See the README file for details on usage and redistribution.
+ */
+
+#include "Imaging.h"
+
+#ifdef HAVE_OPENJPEG
+
+#include "Jpeg2K.h"
+
+#define CINEMA_24_CS_LENGTH 1302083
+#define CINEMA_48_CS_LENGTH 651041
+#define COMP_24_CS_MAX_LENGTH 1041666
+#define COMP_48_CS_MAX_LENGTH 520833
+
+/* -------------------------------------------------------------------- */
+/* Error handler */
+/* -------------------------------------------------------------------- */
+
+static void
+j2k_error(const char *msg, void *client_data) {
+ JPEG2KENCODESTATE *state = (JPEG2KENCODESTATE *)client_data;
+ free((void *)state->error_msg);
+ state->error_msg = strdup(msg);
+}
+
+static void
+j2k_warn(const char *msg, void *client_data) {
+ // Null handler
+}
+
+/* -------------------------------------------------------------------- */
+/* Buffer output stream */
+/* -------------------------------------------------------------------- */
+
+static OPJ_SIZE_T
+j2k_write(void *p_buffer, OPJ_SIZE_T p_nb_bytes, void *p_user_data) {
+ ImagingCodecState state = (ImagingCodecState)p_user_data;
+ unsigned int result;
+
+ result = _imaging_write_pyFd(state->fd, p_buffer, p_nb_bytes);
+
+ return result ? result : (OPJ_SIZE_T)-1;
+}
+
+static OPJ_OFF_T
+j2k_skip(OPJ_OFF_T p_nb_bytes, void *p_user_data) {
+ ImagingCodecState state = (ImagingCodecState)p_user_data;
+ char *buffer;
+ int result;
+
+ /* Explicitly write zeros */
+ buffer = calloc(p_nb_bytes, 1);
+ if (!buffer) {
+ return (OPJ_OFF_T)-1;
+ }
+
+ result = _imaging_write_pyFd(state->fd, buffer, p_nb_bytes);
+
+ free(buffer);
+
+ return result ? result : p_nb_bytes;
+}
+
+static OPJ_BOOL
+j2k_seek(OPJ_OFF_T p_nb_bytes, void *p_user_data) {
+ ImagingCodecState state = (ImagingCodecState)p_user_data;
+ off_t pos = 0;
+
+ _imaging_seek_pyFd(state->fd, p_nb_bytes, SEEK_SET);
+ pos = _imaging_tell_pyFd(state->fd);
+
+ return pos == p_nb_bytes;
+}
+
+/* -------------------------------------------------------------------- */
+/* Encoder */
+/* -------------------------------------------------------------------- */
+
+typedef void (*j2k_pack_tile_t)(
+ Imaging im, UINT8 *buf, unsigned x0, unsigned y0, unsigned w, unsigned h);
+
+static void
+j2k_pack_l(Imaging im, UINT8 *buf, unsigned x0, unsigned y0, unsigned w, unsigned h) {
+ UINT8 *ptr = buf;
+ unsigned x, y;
+ for (y = 0; y < h; ++y) {
+ UINT8 *data = (UINT8 *)(im->image[y + y0] + x0);
+ for (x = 0; x < w; ++x) {
+ *ptr++ = *data++;
+ }
+ }
+}
+
+static void
+j2k_pack_i16(Imaging im, UINT8 *buf, unsigned x0, unsigned y0, unsigned w, unsigned h) {
+ UINT8 *ptr = buf;
+ unsigned x, y;
+ for (y = 0; y < h; ++y) {
+ UINT8 *data = (UINT8 *)(im->image[y + y0] + x0);
+ for (x = 0; x < w; ++x) {
+#ifdef WORDS_BIGENDIAN
+ ptr[0] = data[1];
+ ptr[1] = data[0];
+#else
+ ptr[0] = data[0];
+ ptr[1] = data[1];
+#endif
+ ptr += 2;
+ data += 2;
+ }
+ }
+}
+
+static void
+j2k_pack_la(Imaging im, UINT8 *buf, unsigned x0, unsigned y0, unsigned w, unsigned h) {
+ UINT8 *ptr = buf;
+ UINT8 *ptra = buf + w * h;
+ unsigned x, y;
+ for (y = 0; y < h; ++y) {
+ UINT8 *data = (UINT8 *)(im->image[y + y0] + 4 * x0);
+ for (x = 0; x < w; ++x) {
+ *ptr++ = data[0];
+ *ptra++ = data[3];
+ data += 4;
+ }
+ }
+}
+
+static void
+j2k_pack_rgb(Imaging im, UINT8 *buf, unsigned x0, unsigned y0, unsigned w, unsigned h) {
+ UINT8 *pr = buf;
+ UINT8 *pg = pr + w * h;
+ UINT8 *pb = pg + w * h;
+ unsigned x, y;
+ for (y = 0; y < h; ++y) {
+ UINT8 *data = (UINT8 *)(im->image[y + y0] + 4 * x0);
+ for (x = 0; x < w; ++x) {
+ *pr++ = data[0];
+ *pg++ = data[1];
+ *pb++ = data[2];
+ data += 4;
+ }
+ }
+}
+
+static void
+j2k_pack_rgba(
+ Imaging im, UINT8 *buf, unsigned x0, unsigned y0, unsigned w, unsigned h) {
+ UINT8 *pr = buf;
+ UINT8 *pg = pr + w * h;
+ UINT8 *pb = pg + w * h;
+ UINT8 *pa = pb + w * h;
+ unsigned x, y;
+ for (y = 0; y < h; ++y) {
+ UINT8 *data = (UINT8 *)(im->image[y + y0] + 4 * x0);
+ for (x = 0; x < w; ++x) {
+ *pr++ = *data++;
+ *pg++ = *data++;
+ *pb++ = *data++;
+ *pa++ = *data++;
+ }
+ }
+}
+
+enum {
+ J2K_STATE_START = 0,
+ J2K_STATE_ENCODING = 1,
+ J2K_STATE_DONE = 2,
+ J2K_STATE_FAILED = 3,
+};
+
+static void
+j2k_set_cinema_params(Imaging im, int components, opj_cparameters_t *params) {
+ float rate;
+ int n;
+
+ /* These settings have been copied from opj_compress in the OpenJPEG
+ sources. */
+
+ params->tile_size_on = OPJ_FALSE;
+ params->cp_tdx = params->cp_tdy = 1;
+ params->tp_flag = 'C';
+ params->tp_on = 1;
+ params->cp_tx0 = params->cp_ty0 = 0;
+ params->image_offset_x0 = params->image_offset_y0 = 0;
+ params->cblockw_init = 32;
+ params->cblockh_init = 32;
+ params->csty |= 0x01;
+ params->prog_order = OPJ_CPRL;
+ params->roi_compno = -1;
+ params->subsampling_dx = params->subsampling_dy = 1;
+ params->irreversible = 1;
+
+ if (params->cp_cinema == OPJ_CINEMA4K_24) {
+ float max_rate =
+ ((float)(components * im->xsize * im->ysize * 8) /
+ (CINEMA_24_CS_LENGTH * 8));
+
+ params->POC[0].tile = 1;
+ params->POC[0].resno0 = 0;
+ params->POC[0].compno0 = 0;
+ params->POC[0].layno1 = 1;
+ params->POC[0].resno1 = params->numresolution - 1;
+ params->POC[0].compno1 = 3;
+ params->POC[0].prg1 = OPJ_CPRL;
+ params->POC[1].tile = 1;
+ params->POC[1].resno0 = 0;
+ params->POC[1].compno0 = 0;
+ params->POC[1].layno1 = 1;
+ params->POC[1].resno1 = params->numresolution - 1;
+ params->POC[1].compno1 = 3;
+ params->POC[1].prg1 = OPJ_CPRL;
+ params->numpocs = 2;
+
+ for (n = 0; n < params->tcp_numlayers; ++n) {
+ rate = 0;
+ if (params->tcp_rates[0] == 0) {
+ params->tcp_rates[n] = max_rate;
+ } else {
+ rate =
+ ((float)(components * im->xsize * im->ysize * 8) /
+ (params->tcp_rates[n] * 8));
+ if (rate > CINEMA_24_CS_LENGTH) {
+ params->tcp_rates[n] = max_rate;
+ }
+ }
+ }
+
+ params->max_comp_size = COMP_24_CS_MAX_LENGTH;
+ } else {
+ float max_rate =
+ ((float)(components * im->xsize * im->ysize * 8) /
+ (CINEMA_48_CS_LENGTH * 8));
+
+ for (n = 0; n < params->tcp_numlayers; ++n) {
+ rate = 0;
+ if (params->tcp_rates[0] == 0) {
+ params->tcp_rates[n] = max_rate;
+ } else {
+ rate =
+ ((float)(components * im->xsize * im->ysize * 8) /
+ (params->tcp_rates[n] * 8));
+ if (rate > CINEMA_48_CS_LENGTH) {
+ params->tcp_rates[n] = max_rate;
+ }
+ }
+ }
+
+ params->max_comp_size = COMP_48_CS_MAX_LENGTH;
+ }
+}
+
+static int
+j2k_encode_entry(Imaging im, ImagingCodecState state) {
+ JPEG2KENCODESTATE *context = (JPEG2KENCODESTATE *)state->context;
+ opj_stream_t *stream = NULL;
+ opj_image_t *image = NULL;
+ opj_codec_t *codec = NULL;
+ opj_cparameters_t params;
+ unsigned components;
+ OPJ_COLOR_SPACE color_space;
+ opj_image_cmptparm_t image_params[4];
+ unsigned xsiz, ysiz;
+ unsigned tile_width, tile_height;
+ unsigned tiles_x, tiles_y;
+ unsigned x, y, tile_ndx;
+ unsigned n;
+ j2k_pack_tile_t pack;
+ int ret = -1;
+
+ unsigned prec = 8;
+ unsigned _overflow_scale_factor;
+
+ stream = opj_stream_create(BUFFER_SIZE, OPJ_FALSE);
+
+ if (!stream) {
+ state->errcode = IMAGING_CODEC_BROKEN;
+ state->state = J2K_STATE_FAILED;
+ goto quick_exit;
+ }
+
+ opj_stream_set_write_function(stream, j2k_write);
+ opj_stream_set_skip_function(stream, j2k_skip);
+ opj_stream_set_seek_function(stream, j2k_seek);
+
+ /* OpenJPEG 2.0 doesn't have OPJ_VERSION_MAJOR */
+#ifndef OPJ_VERSION_MAJOR
+ opj_stream_set_user_data(stream, state);
+#else
+ opj_stream_set_user_data(stream, state, NULL);
+#endif
+
+ /* Setup an opj_image */
+ if (strcmp(im->mode, "L") == 0) {
+ components = 1;
+ color_space = OPJ_CLRSPC_GRAY;
+ pack = j2k_pack_l;
+ } else if (strcmp(im->mode, "I;16") == 0 || strcmp(im->mode, "I;16B") == 0) {
+ components = 1;
+ color_space = OPJ_CLRSPC_GRAY;
+ pack = j2k_pack_i16;
+ prec = 16;
+ } else if (strcmp(im->mode, "LA") == 0) {
+ components = 2;
+ color_space = OPJ_CLRSPC_GRAY;
+ pack = j2k_pack_la;
+ } else if (strcmp(im->mode, "RGB") == 0) {
+ components = 3;
+ color_space = OPJ_CLRSPC_SRGB;
+ pack = j2k_pack_rgb;
+ } else if (strcmp(im->mode, "YCbCr") == 0) {
+ components = 3;
+ color_space = OPJ_CLRSPC_SYCC;
+ pack = j2k_pack_rgb;
+ } else if (strcmp(im->mode, "RGBA") == 0) {
+ components = 4;
+ color_space = OPJ_CLRSPC_SRGB;
+ pack = j2k_pack_rgba;
+ } else {
+ state->errcode = IMAGING_CODEC_BROKEN;
+ state->state = J2K_STATE_FAILED;
+ goto quick_exit;
+ }
+
+ for (n = 0; n < components; ++n) {
+ image_params[n].dx = image_params[n].dy = 1;
+ image_params[n].w = im->xsize;
+ image_params[n].h = im->ysize;
+ image_params[n].x0 = image_params[n].y0 = 0;
+ image_params[n].prec = prec;
+ image_params[n].sgnd = context->sgnd == 0 ? 0 : 1;
+ }
+
+ image = opj_image_create(components, image_params, color_space);
+ if (!image) {
+ state->errcode = IMAGING_CODEC_BROKEN;
+ state->state = J2K_STATE_FAILED;
+ goto quick_exit;
+ }
+
+ /* Setup compression context */
+ context->error_msg = NULL;
+
+ opj_set_default_encoder_parameters(&params);
+
+ params.image_offset_x0 = context->offset_x;
+ params.image_offset_y0 = context->offset_y;
+
+ if (context->tile_size_x && context->tile_size_y) {
+ params.tile_size_on = OPJ_TRUE;
+ params.cp_tx0 = context->tile_offset_x;
+ params.cp_ty0 = context->tile_offset_y;
+ params.cp_tdx = context->tile_size_x;
+ params.cp_tdy = context->tile_size_y;
+
+ tile_width = params.cp_tdx;
+ tile_height = params.cp_tdy;
+ } else {
+ params.cp_tx0 = 0;
+ params.cp_ty0 = 0;
+ params.cp_tdx = 1;
+ params.cp_tdy = 1;
+
+ tile_width = im->xsize;
+ tile_height = im->ysize;
+ }
+
+ if (context->quality_layers && PySequence_Check(context->quality_layers)) {
+ Py_ssize_t len = PySequence_Length(context->quality_layers);
+ Py_ssize_t n;
+ float *pq;
+
+ if (len > 0) {
+ if ((size_t)len >
+ sizeof(params.tcp_rates) / sizeof(params.tcp_rates[0])) {
+ len = sizeof(params.tcp_rates) / sizeof(params.tcp_rates[0]);
+ }
+
+ params.tcp_numlayers = (int)len;
+
+ if (context->quality_is_in_db) {
+ params.cp_disto_alloc = params.cp_fixed_alloc = 0;
+ params.cp_fixed_quality = 1;
+ pq = params.tcp_distoratio;
+ } else {
+ params.cp_disto_alloc = 1;
+ params.cp_fixed_alloc = params.cp_fixed_quality = 0;
+ pq = params.tcp_rates;
+ }
+
+ for (n = 0; n < len; ++n) {
+ PyObject *obj = PySequence_ITEM(context->quality_layers, n);
+ pq[n] = PyFloat_AsDouble(obj);
+ }
+ }
+ } else {
+ params.tcp_numlayers = 1;
+ params.tcp_rates[0] = 0;
+ params.cp_disto_alloc = 1;
+ }
+
+ if (context->num_resolutions) {
+ params.numresolution = context->num_resolutions;
+ }
+
+ if (context->cblk_width >= 4 && context->cblk_width <= 1024 &&
+ context->cblk_height >= 4 && context->cblk_height <= 1024 &&
+ context->cblk_width * context->cblk_height <= 4096) {
+ params.cblockw_init = context->cblk_width;
+ params.cblockh_init = context->cblk_height;
+ }
+
+ if (context->precinct_width >= 4 && context->precinct_height >= 4 &&
+ context->precinct_width >= context->cblk_width &&
+ context->precinct_height > context->cblk_height) {
+ params.prcw_init[0] = context->precinct_width;
+ params.prch_init[0] = context->precinct_height;
+ params.res_spec = 1;
+ params.csty |= 0x01;
+ }
+
+ params.irreversible = context->irreversible;
+ if (components == 3) {
+ params.tcp_mct = context->mct;
+ }
+
+ if (context->comment) {
+ params.cp_comment = context->comment;
+ }
+
+ params.prog_order = context->progression;
+
+ params.cp_cinema = context->cinema_mode;
+
+ switch (params.cp_cinema) {
+ case OPJ_OFF:
+ params.cp_rsiz = OPJ_STD_RSIZ;
+ break;
+ case OPJ_CINEMA2K_24:
+ case OPJ_CINEMA2K_48:
+ params.cp_rsiz = OPJ_CINEMA2K;
+ if (params.numresolution > 6) {
+ params.numresolution = 6;
+ }
+ break;
+ case OPJ_CINEMA4K_24:
+ params.cp_rsiz = OPJ_CINEMA4K;
+ if (params.numresolution > 7) {
+ params.numresolution = 7;
+ }
+ break;
+ }
+
+ if (!context->num_resolutions) {
+ while (tile_width < (1U << (params.numresolution - 1U)) || tile_height < (1U << (params.numresolution - 1U))) {
+ params.numresolution -= 1;
+ }
+ }
+
+ if (context->cinema_mode != OPJ_OFF) {
+ j2k_set_cinema_params(im, components, &params);
+ }
+
+ /* Set up the reference grid in the image */
+ image->x0 = params.image_offset_x0;
+ image->y0 = params.image_offset_y0;
+ image->x1 = xsiz = im->xsize + params.image_offset_x0;
+ image->y1 = ysiz = im->ysize + params.image_offset_y0;
+
+ /* Create the compressor */
+ codec = opj_create_compress(context->format);
+
+ if (!codec) {
+ state->errcode = IMAGING_CODEC_BROKEN;
+ state->state = J2K_STATE_FAILED;
+ goto quick_exit;
+ }
+
+ if (strcmp(im->mode, "RGBA") == 0) {
+ image->comps[3].alpha = 1;
+ } else if (strcmp(im->mode, "LA") == 0) {
+ image->comps[1].alpha = 1;
+ }
+
+ opj_set_error_handler(codec, j2k_error, context);
+ opj_set_info_handler(codec, j2k_warn, context);
+ opj_set_warning_handler(codec, j2k_warn, context);
+ opj_setup_encoder(codec, &params, image);
+
+ /* Enabling PLT markers only supported in OpenJPEG 2.4.0 and up */
+#if ((OPJ_VERSION_MAJOR == 2 && OPJ_VERSION_MINOR >= 4) || OPJ_VERSION_MAJOR > 2)
+ if (context->plt) {
+ const char *plt_option[2] = {"PLT=YES", NULL};
+ opj_encoder_set_extra_options(codec, plt_option);
+ }
+#endif
+
+ /* Start encoding */
+ if (!opj_start_compress(codec, image, stream)) {
+ state->errcode = IMAGING_CODEC_BROKEN;
+ state->state = J2K_STATE_FAILED;
+ goto quick_exit;
+ }
+
+ /* Write each tile */
+ tiles_x = (im->xsize + (params.image_offset_x0 - params.cp_tx0) + tile_width - 1) /
+ tile_width;
+ tiles_y = (im->ysize + (params.image_offset_y0 - params.cp_ty0) + tile_height - 1) /
+ tile_height;
+
+ /* check for integer overflow for the malloc line, checking any expression
+ that may multiply either tile_width or tile_height */
+ _overflow_scale_factor = components * prec;
+ if ((tile_width > UINT_MAX / _overflow_scale_factor) ||
+ (tile_height > UINT_MAX / _overflow_scale_factor) ||
+ (tile_width > UINT_MAX / (tile_height * _overflow_scale_factor)) ||
+ (tile_height > UINT_MAX / (tile_width * _overflow_scale_factor))) {
+ state->errcode = IMAGING_CODEC_BROKEN;
+ state->state = J2K_STATE_FAILED;
+ goto quick_exit;
+ }
+ /* malloc check ok, checked for overflow above */
+ state->buffer = malloc(tile_width * tile_height * components * prec / 8);
+ if (!state->buffer) {
+ state->errcode = IMAGING_CODEC_BROKEN;
+ state->state = J2K_STATE_FAILED;
+ goto quick_exit;
+ }
+
+ tile_ndx = 0;
+ for (y = 0; y < tiles_y; ++y) {
+ int ty0 = params.cp_ty0 + y * tile_height;
+ unsigned ty1 = ty0 + tile_height;
+ unsigned pixy, pixh;
+
+ if (ty0 < params.image_offset_y0) {
+ ty0 = params.image_offset_y0;
+ }
+ if (ty1 > ysiz) {
+ ty1 = ysiz;
+ }
+
+ pixy = ty0 - params.image_offset_y0;
+ pixh = ty1 - ty0;
+
+ for (x = 0; x < tiles_x; ++x) {
+ int tx0 = params.cp_tx0 + x * tile_width;
+ unsigned tx1 = tx0 + tile_width;
+ unsigned pixx, pixw;
+ unsigned data_size;
+
+ if (tx0 < params.image_offset_x0) {
+ tx0 = params.image_offset_x0;
+ }
+ if (tx1 > xsiz) {
+ tx1 = xsiz;
+ }
+
+ pixx = tx0 - params.image_offset_x0;
+ pixw = tx1 - tx0;
+
+ pack(im, state->buffer, pixx, pixy, pixw, pixh);
+
+ data_size = pixw * pixh * components * prec / 8;
+
+ if (!opj_write_tile(codec, tile_ndx++, state->buffer, data_size, stream)) {
+ state->errcode = IMAGING_CODEC_BROKEN;
+ state->state = J2K_STATE_FAILED;
+ goto quick_exit;
+ }
+ }
+ }
+
+ if (!opj_end_compress(codec, stream)) {
+ state->errcode = IMAGING_CODEC_BROKEN;
+ state->state = J2K_STATE_FAILED;
+ goto quick_exit;
+ }
+
+ state->errcode = IMAGING_CODEC_END;
+ state->state = J2K_STATE_DONE;
+ ret = -1;
+
+quick_exit:
+ if (codec) {
+ opj_destroy_codec(codec);
+ }
+ if (image) {
+ opj_image_destroy(image);
+ }
+ if (stream) {
+ opj_stream_destroy(stream);
+ }
+
+ return ret;
+}
+
+int
+ImagingJpeg2KEncode(Imaging im, ImagingCodecState state, UINT8 *buf, int bytes) {
+ if (state->state == J2K_STATE_FAILED) {
+ return -1;
+ }
+
+ if (state->state == J2K_STATE_START) {
+ state->state = J2K_STATE_ENCODING;
+
+ return j2k_encode_entry(im, state);
+ }
+
+ return -1;
+}
+
+/* -------------------------------------------------------------------- */
+/* Cleanup */
+/* -------------------------------------------------------------------- */
+
+int
+ImagingJpeg2KEncodeCleanup(ImagingCodecState state) {
+ JPEG2KENCODESTATE *context = (JPEG2KENCODESTATE *)state->context;
+
+ if (context->quality_layers) {
+ Py_XDECREF(context->quality_layers);
+ context->quality_layers = NULL;
+ }
+
+ if (context->error_msg) {
+ free((void *)context->error_msg);
+ }
+
+ if (context->comment) {
+ free((void *)context->comment);
+ }
+
+ context->error_msg = NULL;
+ context->comment = NULL;
+
+ return -1;
+}
+
+#endif /* HAVE_OPENJPEG */
+
+/*
+ * Local Variables:
+ * c-basic-offset: 4
+ * End:
+ *
+ */
diff --git a/contrib/python/Pillow/py3/libImaging/JpegDecode.c b/contrib/python/Pillow/py3/libImaging/JpegDecode.c
new file mode 100644
index 00000000000..55d10a81aec
--- /dev/null
+++ b/contrib/python/Pillow/py3/libImaging/JpegDecode.c
@@ -0,0 +1,304 @@
+/*
+ * The Python Imaging Library.
+ * $Id$
+ *
+ * decoder for JPEG image data.
+ *
+ * history:
+ * 1996-05-02 fl Created
+ * 1996-05-05 fl Handle small JPEG files correctly
+ * 1996-05-28 fl Added "draft mode" support
+ * 1997-01-25 fl Added colour conversion override
+ * 1998-01-31 fl Adapted to libjpeg 6a
+ * 1998-07-12 fl Extended YCbCr support
+ * 1998-12-29 fl Added new state to handle suspension in multipass modes
+ * 2000-10-12 fl Suppress warnings
+ * 2000-12-04 fl Suppress errors beyond end of image data
+ *
+ * Copyright (c) 1998-2000 Secret Labs AB
+ * Copyright (c) 1996-2000 Fredrik Lundh
+ *
+ * See the README file for details on usage and redistribution.
+ */
+
+#include "Imaging.h"
+
+#ifdef HAVE_LIBJPEG
+
+#undef HAVE_PROTOTYPES
+#undef HAVE_STDLIB_H
+#undef HAVE_STDDEF_H
+#undef UINT8
+#undef UINT16
+#undef UINT32
+#undef INT16
+#undef INT32
+
+#include "Jpeg.h"
+
+#define STRINGIFY(x) #x
+#define TOSTRING(x) STRINGIFY(x)
+
+// There is no way to compare versions on compile time,
+// so we have to do that in runtime.
+#ifdef LIBJPEG_TURBO_VERSION
+char *libjpeg_turbo_version = TOSTRING(LIBJPEG_TURBO_VERSION);
+#else
+char *libjpeg_turbo_version = NULL;
+#endif
+
+int
+ImagingJpegUseJCSExtensions() {
+ int use_jcs_extensions = 0;
+#ifdef JCS_EXTENSIONS
+#if defined(LIBJPEG_TURBO_VERSION_NUMBER)
+#if LIBJPEG_TURBO_VERSION_NUMBER >= 1002010
+ use_jcs_extensions = 1;
+#endif
+#else
+ if (libjpeg_turbo_version) {
+ use_jcs_extensions = strcmp(libjpeg_turbo_version, "1.2.1") >= 0;
+ }
+#endif
+#endif
+ return use_jcs_extensions;
+}
+
+/* -------------------------------------------------------------------- */
+/* Suspending input handler */
+/* -------------------------------------------------------------------- */
+
+METHODDEF(void)
+stub(j_decompress_ptr cinfo) { /* empty */ }
+
+METHODDEF(boolean)
+fill_input_buffer(j_decompress_ptr cinfo) {
+ /* Suspension */
+ return FALSE;
+}
+
+METHODDEF(void)
+skip_input_data(j_decompress_ptr cinfo, long num_bytes) {
+ JPEGSOURCE *source = (JPEGSOURCE *)cinfo->src;
+
+ if (num_bytes > (long)source->pub.bytes_in_buffer) {
+ /* We need to skip more data than we have in the buffer.
+ This will force the JPEG library to suspend decoding. */
+ source->skip = num_bytes - source->pub.bytes_in_buffer;
+ source->pub.next_input_byte += source->pub.bytes_in_buffer;
+ source->pub.bytes_in_buffer = 0;
+ } else {
+ /* Skip portion of the buffer */
+ source->pub.bytes_in_buffer -= num_bytes;
+ source->pub.next_input_byte += num_bytes;
+ source->skip = 0;
+ }
+}
+
+GLOBAL(void)
+jpeg_buffer_src(j_decompress_ptr cinfo, JPEGSOURCE *source) {
+ cinfo->src = (void *)source;
+
+ /* Prepare for suspending reader */
+ source->pub.init_source = stub;
+ source->pub.fill_input_buffer = fill_input_buffer;
+ source->pub.skip_input_data = skip_input_data;
+ source->pub.resync_to_restart = jpeg_resync_to_restart;
+ source->pub.term_source = stub;
+ source->pub.bytes_in_buffer = 0; /* forces fill_input_buffer on first read */
+
+ source->skip = 0;
+}
+
+/* -------------------------------------------------------------------- */
+/* Error handler */
+/* -------------------------------------------------------------------- */
+
+METHODDEF(void)
+error(j_common_ptr cinfo) {
+ JPEGERROR *error;
+ error = (JPEGERROR *)cinfo->err;
+ longjmp(error->setjmp_buffer, 1);
+}
+
+METHODDEF(void)
+output(j_common_ptr cinfo) { /* nothing */ }
+
+/* -------------------------------------------------------------------- */
+/* Decoder */
+/* -------------------------------------------------------------------- */
+
+int
+ImagingJpegDecode(Imaging im, ImagingCodecState state, UINT8 *buf, Py_ssize_t bytes) {
+ JPEGSTATE *context = (JPEGSTATE *)state->context;
+ int ok;
+
+ if (setjmp(context->error.setjmp_buffer)) {
+ /* JPEG error handler */
+ jpeg_destroy_decompress(&context->cinfo);
+ state->errcode = IMAGING_CODEC_BROKEN;
+ return -1;
+ }
+
+ if (!state->state) {
+ /* Setup decompression context */
+ context->cinfo.err = jpeg_std_error(&context->error.pub);
+ context->error.pub.error_exit = error;
+ context->error.pub.output_message = output;
+ jpeg_create_decompress(&context->cinfo);
+ jpeg_buffer_src(&context->cinfo, &context->source);
+
+ /* Ready to decode */
+ state->state = 1;
+ }
+
+ /* Load the source buffer */
+ context->source.pub.next_input_byte = buf;
+ context->source.pub.bytes_in_buffer = bytes;
+
+ if (context->source.skip > 0) {
+ skip_input_data(&context->cinfo, context->source.skip);
+ if (context->source.skip > 0) {
+ return context->source.pub.next_input_byte - buf;
+ }
+ }
+
+ switch (state->state) {
+ case 1:
+
+ /* Read JPEG header, until we find an image body. */
+ do {
+ /* Note that we cannot return unless we have decoded
+ as much data as possible. */
+ ok = jpeg_read_header(&context->cinfo, FALSE);
+
+ } while (ok == JPEG_HEADER_TABLES_ONLY);
+
+ if (ok == JPEG_SUSPENDED) {
+ break;
+ }
+
+ /* Decoder settings */
+
+ /* jpegmode indicates whats in the file; if not set, we'll
+ trust the decoder */
+ if (strcmp(context->jpegmode, "L") == 0) {
+ context->cinfo.jpeg_color_space = JCS_GRAYSCALE;
+ } else if (strcmp(context->jpegmode, "RGB") == 0) {
+ context->cinfo.jpeg_color_space = JCS_RGB;
+ } else if (strcmp(context->jpegmode, "CMYK") == 0) {
+ context->cinfo.jpeg_color_space = JCS_CMYK;
+ } else if (strcmp(context->jpegmode, "YCbCr") == 0) {
+ context->cinfo.jpeg_color_space = JCS_YCbCr;
+ } else if (strcmp(context->jpegmode, "YCbCrK") == 0) {
+ context->cinfo.jpeg_color_space = JCS_YCCK;
+ }
+
+ /* rawmode indicates what we want from the decoder. if not
+ set, conversions are disabled */
+ if (strcmp(context->rawmode, "L") == 0) {
+ context->cinfo.out_color_space = JCS_GRAYSCALE;
+ } else if (strcmp(context->rawmode, "RGB") == 0) {
+ context->cinfo.out_color_space = JCS_RGB;
+ }
+#ifdef JCS_EXTENSIONS
+ else if (strcmp(context->rawmode, "RGBX") == 0) {
+ context->cinfo.out_color_space = JCS_EXT_RGBX;
+ }
+#endif
+ else if (
+ strcmp(context->rawmode, "CMYK") == 0 ||
+ strcmp(context->rawmode, "CMYK;I") == 0) {
+ context->cinfo.out_color_space = JCS_CMYK;
+ } else if (strcmp(context->rawmode, "YCbCr") == 0) {
+ context->cinfo.out_color_space = JCS_YCbCr;
+ } else if (strcmp(context->rawmode, "YCbCrK") == 0) {
+ context->cinfo.out_color_space = JCS_YCCK;
+ } else {
+ /* Disable decoder conversions */
+ context->cinfo.jpeg_color_space = JCS_UNKNOWN;
+ context->cinfo.out_color_space = JCS_UNKNOWN;
+ }
+
+ if (context->scale > 1) {
+ context->cinfo.scale_num = 1;
+ context->cinfo.scale_denom = context->scale;
+ }
+ if (context->draft) {
+ context->cinfo.do_fancy_upsampling = FALSE;
+ context->cinfo.dct_method = JDCT_FASTEST;
+ }
+
+ state->state++;
+ /* fall through */
+
+ case 2:
+
+ /* Set things up for decompression (this processes the entire
+ file if necessary to return data line by line) */
+ if (!jpeg_start_decompress(&context->cinfo)) {
+ break;
+ }
+
+ state->state++;
+ /* fall through */
+
+ case 3:
+
+ /* Decompress a single line of data */
+ ok = 1;
+ while (state->y < state->ysize) {
+ ok = jpeg_read_scanlines(&context->cinfo, &state->buffer, 1);
+ if (ok != 1) {
+ break;
+ }
+ state->shuffle(
+ (UINT8 *)im->image[state->y + state->yoff] +
+ state->xoff * im->pixelsize,
+ state->buffer,
+ state->xsize);
+ state->y++;
+ }
+ if (ok != 1) {
+ break;
+ }
+ state->state++;
+ /* fall through */
+
+ case 4:
+
+ /* Finish decompression */
+ if (!jpeg_finish_decompress(&context->cinfo)) {
+ /* FIXME: add strictness mode test */
+ if (state->y < state->ysize) {
+ break;
+ }
+ }
+
+ /* Clean up */
+ jpeg_destroy_decompress(&context->cinfo);
+ /* if (jerr.pub.num_warnings) return BROKEN; */
+ return -1;
+ }
+
+ /* Return number of bytes consumed */
+ return context->source.pub.next_input_byte - buf;
+}
+
+/* -------------------------------------------------------------------- */
+/* Cleanup */
+/* -------------------------------------------------------------------- */
+
+int
+ImagingJpegDecodeCleanup(ImagingCodecState state) {
+ /* called to free the decompression engine when the decode terminates
+ due to a corrupt or truncated image
+ */
+ JPEGSTATE *context = (JPEGSTATE *)state->context;
+
+ /* Clean up */
+ jpeg_destroy_decompress(&context->cinfo);
+ return -1;
+}
+
+#endif
diff --git a/contrib/python/Pillow/py3/libImaging/JpegEncode.c b/contrib/python/Pillow/py3/libImaging/JpegEncode.c
new file mode 100644
index 00000000000..2a24eff39ca
--- /dev/null
+++ b/contrib/python/Pillow/py3/libImaging/JpegEncode.c
@@ -0,0 +1,354 @@
+/*
+ * The Python Imaging Library.
+ * $Id$
+ *
+ * coder for JPEG data
+ *
+ * history:
+ * 1996-05-06 fl created
+ * 1996-07-16 fl don't drop last block of encoded data
+ * 1996-12-30 fl added quality and progressive settings
+ * 1997-01-08 fl added streamtype settings
+ * 1998-01-31 fl adapted to libjpeg 6a
+ * 1998-07-12 fl added YCbCr support
+ * 2001-04-16 fl added DPI write support
+ *
+ * Copyright (c) 1997-2001 by Secret Labs AB
+ * Copyright (c) 1995-1997 by Fredrik Lundh
+ *
+ * See the README file for details on usage and redistribution.
+ */
+
+#include "Imaging.h"
+
+#ifdef HAVE_LIBJPEG
+
+#undef HAVE_PROTOTYPES
+#undef HAVE_STDLIB_H
+#undef HAVE_STDDEF_H
+#undef UINT8
+#undef UINT16
+#undef UINT32
+#undef INT16
+#undef INT32
+
+#include "Jpeg.h"
+
+/* -------------------------------------------------------------------- */
+/* Suspending output handler */
+/* -------------------------------------------------------------------- */
+
+METHODDEF(void)
+stub(j_compress_ptr cinfo) { /* empty */ }
+
+METHODDEF(boolean)
+empty_output_buffer(j_compress_ptr cinfo) {
+ /* Suspension */
+ return FALSE;
+}
+
+GLOBAL(void)
+jpeg_buffer_dest(j_compress_ptr cinfo, JPEGDESTINATION *destination) {
+ cinfo->dest = (void *)destination;
+
+ destination->pub.init_destination = stub;
+ destination->pub.empty_output_buffer = empty_output_buffer;
+ destination->pub.term_destination = stub;
+}
+
+/* -------------------------------------------------------------------- */
+/* Error handler */
+/* -------------------------------------------------------------------- */
+
+METHODDEF(void)
+error(j_common_ptr cinfo) {
+ JPEGERROR *error;
+ error = (JPEGERROR *)cinfo->err;
+ (*cinfo->err->output_message)(cinfo);
+ longjmp(error->setjmp_buffer, 1);
+}
+
+/* -------------------------------------------------------------------- */
+/* Encoder */
+/* -------------------------------------------------------------------- */
+
+int
+ImagingJpegEncode(Imaging im, ImagingCodecState state, UINT8 *buf, int bytes) {
+ JPEGENCODERSTATE *context = (JPEGENCODERSTATE *)state->context;
+ int ok;
+
+ if (setjmp(context->error.setjmp_buffer)) {
+ /* JPEG error handler */
+ jpeg_destroy_compress(&context->cinfo);
+ state->errcode = IMAGING_CODEC_BROKEN;
+ return -1;
+ }
+
+ if (!state->state) {
+ /* Setup compression context (very similar to the decoder) */
+ context->cinfo.err = jpeg_std_error(&context->error.pub);
+ context->error.pub.error_exit = error;
+ jpeg_create_compress(&context->cinfo);
+ jpeg_buffer_dest(&context->cinfo, &context->destination);
+
+ context->extra_offset = 0;
+
+ /* Ready to encode */
+ state->state = 1;
+ }
+
+ /* Load the destination buffer */
+ context->destination.pub.next_output_byte = buf;
+ context->destination.pub.free_in_buffer = bytes;
+
+ switch (state->state) {
+ case 1:
+
+ context->cinfo.image_width = state->xsize;
+ context->cinfo.image_height = state->ysize;
+
+ switch (state->bits) {
+ case 8:
+ context->cinfo.input_components = 1;
+ context->cinfo.in_color_space = JCS_GRAYSCALE;
+ break;
+ case 24:
+ context->cinfo.input_components = 3;
+ if (strcmp(im->mode, "YCbCr") == 0) {
+ context->cinfo.in_color_space = JCS_YCbCr;
+ } else {
+ context->cinfo.in_color_space = JCS_RGB;
+ }
+ break;
+ case 32:
+ context->cinfo.input_components = 4;
+ context->cinfo.in_color_space = JCS_CMYK;
+#ifdef JCS_EXTENSIONS
+ if (strcmp(context->rawmode, "RGBX") == 0) {
+ context->cinfo.in_color_space = JCS_EXT_RGBX;
+ }
+#endif
+ break;
+ default:
+ state->errcode = IMAGING_CODEC_CONFIG;
+ return -1;
+ }
+
+ /* Compressor configuration */
+ jpeg_set_defaults(&context->cinfo);
+
+ /* Use custom quantization tables */
+ if (context->qtables) {
+ int i;
+ int quality = 100;
+ int last_q = 0;
+ if (context->quality != -1) {
+ quality = context->quality;
+ }
+ for (i = 0; i < context->qtablesLen; i++) {
+ jpeg_add_quant_table(
+ &context->cinfo,
+ i,
+ &context->qtables[i * DCTSIZE2],
+ quality,
+ FALSE);
+ context->cinfo.comp_info[i].quant_tbl_no = i;
+ last_q = i;
+ }
+ if (context->qtablesLen == 1) {
+ // jpeg_set_defaults created two qtables internally, but we only
+ // wanted one.
+ jpeg_add_quant_table(
+ &context->cinfo, 1, &context->qtables[0], quality, FALSE);
+ }
+ for (i = last_q; i < context->cinfo.num_components; i++) {
+ context->cinfo.comp_info[i].quant_tbl_no = last_q;
+ }
+ } else if (context->quality != -1) {
+ jpeg_set_quality(&context->cinfo, context->quality, TRUE);
+ }
+
+ /* Set subsampling options */
+ switch (context->subsampling) {
+ case 0: /* 1x1 1x1 1x1 (4:4:4) : None */
+ {
+ context->cinfo.comp_info[0].h_samp_factor = 1;
+ context->cinfo.comp_info[0].v_samp_factor = 1;
+ context->cinfo.comp_info[1].h_samp_factor = 1;
+ context->cinfo.comp_info[1].v_samp_factor = 1;
+ context->cinfo.comp_info[2].h_samp_factor = 1;
+ context->cinfo.comp_info[2].v_samp_factor = 1;
+ break;
+ }
+ case 1: /* 2x1, 1x1, 1x1 (4:2:2) : Medium */
+ {
+ context->cinfo.comp_info[0].h_samp_factor = 2;
+ context->cinfo.comp_info[0].v_samp_factor = 1;
+ context->cinfo.comp_info[1].h_samp_factor = 1;
+ context->cinfo.comp_info[1].v_samp_factor = 1;
+ context->cinfo.comp_info[2].h_samp_factor = 1;
+ context->cinfo.comp_info[2].v_samp_factor = 1;
+ break;
+ }
+ case 2: /* 2x2, 1x1, 1x1 (4:2:0) : High */
+ {
+ context->cinfo.comp_info[0].h_samp_factor = 2;
+ context->cinfo.comp_info[0].v_samp_factor = 2;
+ context->cinfo.comp_info[1].h_samp_factor = 1;
+ context->cinfo.comp_info[1].v_samp_factor = 1;
+ context->cinfo.comp_info[2].h_samp_factor = 1;
+ context->cinfo.comp_info[2].v_samp_factor = 1;
+ break;
+ }
+ default: {
+ /* Use the lib's default */
+ break;
+ }
+ }
+ if (context->progressive) {
+ jpeg_simple_progression(&context->cinfo);
+ }
+ context->cinfo.smoothing_factor = context->smooth;
+ context->cinfo.optimize_coding = (boolean)context->optimize;
+ if (context->xdpi > 0 && context->ydpi > 0) {
+ context->cinfo.write_JFIF_header = TRUE;
+ context->cinfo.density_unit = 1; /* dots per inch */
+ context->cinfo.X_density = context->xdpi;
+ context->cinfo.Y_density = context->ydpi;
+ }
+ switch (context->streamtype) {
+ case 1:
+ /* tables only -- not yet implemented */
+ state->errcode = IMAGING_CODEC_CONFIG;
+ return -1;
+ case 2:
+ /* image only */
+ jpeg_suppress_tables(&context->cinfo, TRUE);
+ jpeg_start_compress(&context->cinfo, FALSE);
+ /* suppress extra section */
+ context->extra_offset = context->extra_size;
+ break;
+ default:
+ /* interchange stream */
+ jpeg_start_compress(&context->cinfo, TRUE);
+ break;
+ }
+ state->state++;
+ /* fall through */
+
+ case 2:
+ // check for exif len + 'APP1' header bytes
+ if (context->rawExifLen + 5 > context->destination.pub.free_in_buffer) {
+ break;
+ }
+ // add exif header
+ if (context->rawExifLen > 0) {
+ jpeg_write_marker(
+ &context->cinfo,
+ JPEG_APP0 + 1,
+ (unsigned char *)context->rawExif,
+ context->rawExifLen);
+ }
+
+ state->state++;
+ /* fall through */
+ case 3:
+
+ if (context->extra) {
+ /* copy extra buffer to output buffer */
+ unsigned int n = context->extra_size - context->extra_offset;
+ if (n > context->destination.pub.free_in_buffer) {
+ n = context->destination.pub.free_in_buffer;
+ }
+ memcpy(
+ context->destination.pub.next_output_byte,
+ context->extra + context->extra_offset,
+ n);
+ context->destination.pub.next_output_byte += n;
+ context->destination.pub.free_in_buffer -= n;
+ context->extra_offset += n;
+ if (context->extra_offset >= context->extra_size) {
+ state->state++;
+ } else {
+ break;
+ }
+ } else {
+ state->state++;
+ }
+
+ case 4:
+
+ if (context->comment) {
+ jpeg_write_marker(&context->cinfo, JPEG_COM, (unsigned char *)context->comment, context->comment_size);
+ }
+ state->state++;
+
+ case 5:
+ if (1024 > context->destination.pub.free_in_buffer) {
+ break;
+ }
+
+ ok = 1;
+ while (state->y < state->ysize) {
+ state->shuffle(
+ state->buffer,
+ (UINT8 *)im->image[state->y + state->yoff] +
+ state->xoff * im->pixelsize,
+ state->xsize);
+ ok = jpeg_write_scanlines(&context->cinfo, &state->buffer, 1);
+ if (ok != 1) {
+ break;
+ }
+ state->y++;
+ }
+
+ if (ok != 1) {
+ break;
+ }
+ state->state++;
+ /* fall through */
+
+ case 6:
+
+ /* Finish compression */
+ if (context->destination.pub.free_in_buffer < 100) {
+ break;
+ }
+ jpeg_finish_compress(&context->cinfo);
+
+ /* Clean up */
+ if (context->comment) {
+ free(context->comment);
+ context->comment = NULL;
+ }
+ if (context->extra) {
+ free(context->extra);
+ context->extra = NULL;
+ }
+ if (context->rawExif) {
+ free(context->rawExif);
+ context->rawExif = NULL;
+ }
+ if (context->qtables) {
+ free(context->qtables);
+ context->qtables = NULL;
+ }
+
+ jpeg_destroy_compress(&context->cinfo);
+ /* if (jerr.pub.num_warnings) return BROKEN; */
+ state->errcode = IMAGING_CODEC_END;
+ break;
+ }
+
+ /* Return number of bytes in output buffer */
+ return context->destination.pub.next_output_byte - buf;
+}
+
+const char *
+ImagingJpegVersion(void) {
+ static char version[20];
+ sprintf(version, "%d.%d", JPEG_LIB_VERSION / 10, JPEG_LIB_VERSION % 10);
+ return version;
+}
+
+#endif
diff --git a/contrib/python/Pillow/py3/libImaging/Matrix.c b/contrib/python/Pillow/py3/libImaging/Matrix.c
new file mode 100644
index 00000000000..182eb62a7e6
--- /dev/null
+++ b/contrib/python/Pillow/py3/libImaging/Matrix.c
@@ -0,0 +1,78 @@
+/*
+ * The Python Imaging Library
+ * $Id$
+ *
+ * colour and luminance matrix transforms
+ *
+ * history:
+ * 1996-05-18 fl: created (brute force implementation)
+ *
+ * Copyright (c) Fredrik Lundh 1996.
+ * Copyright (c) Secret Labs AB 1997.
+ *
+ * See the README file for information on usage and redistribution.
+ */
+
+#include "Imaging.h"
+
+#define CLIPF(v) ((v <= 0.0) ? 0 : (v >= 255.0F) ? 255 : (UINT8)v)
+
+Imaging
+ImagingConvertMatrix(Imaging im, const char *mode, float m[]) {
+ Imaging imOut;
+ int x, y;
+ ImagingSectionCookie cookie;
+
+ /* Assume there's enough data in the buffer */
+ if (!im) {
+ return (Imaging)ImagingError_ModeError();
+ }
+
+ if (strcmp(mode, "L") == 0 && im->bands == 3) {
+ imOut = ImagingNewDirty("L", im->xsize, im->ysize);
+ if (!imOut) {
+ return NULL;
+ }
+
+ ImagingSectionEnter(&cookie);
+ for (y = 0; y < im->ysize; y++) {
+ UINT8 *in = (UINT8 *)im->image[y];
+ UINT8 *out = (UINT8 *)imOut->image[y];
+
+ for (x = 0; x < im->xsize; x++) {
+ float v = m[0] * in[0] + m[1] * in[1] + m[2] * in[2] + m[3] + 0.5;
+ out[x] = CLIPF(v);
+ in += 4;
+ }
+ }
+ ImagingSectionLeave(&cookie);
+
+ } else if (strlen(mode) == 3 && im->bands == 3) {
+ imOut = ImagingNewDirty(mode, im->xsize, im->ysize);
+ if (!imOut) {
+ return NULL;
+ }
+
+ for (y = 0; y < im->ysize; y++) {
+ UINT8 *in = (UINT8 *)im->image[y];
+ UINT8 *out = (UINT8 *)imOut->image[y];
+
+ ImagingSectionEnter(&cookie);
+ for (x = 0; x < im->xsize; x++) {
+ float v0 = m[0] * in[0] + m[1] * in[1] + m[2] * in[2] + m[3] + 0.5;
+ float v1 = m[4] * in[0] + m[5] * in[1] + m[6] * in[2] + m[7] + 0.5;
+ float v2 = m[8] * in[0] + m[9] * in[1] + m[10] * in[2] + m[11] + 0.5;
+ out[0] = CLIPF(v0);
+ out[1] = CLIPF(v1);
+ out[2] = CLIPF(v2);
+ in += 4;
+ out += 4;
+ }
+ ImagingSectionLeave(&cookie);
+ }
+ } else {
+ return (Imaging)ImagingError_ModeError();
+ }
+
+ return imOut;
+}
diff --git a/contrib/python/Pillow/py3/libImaging/ModeFilter.c b/contrib/python/Pillow/py3/libImaging/ModeFilter.c
new file mode 100644
index 00000000000..757cbc3fb86
--- /dev/null
+++ b/contrib/python/Pillow/py3/libImaging/ModeFilter.c
@@ -0,0 +1,81 @@
+/*
+ * The Python Imaging Library
+ * $Id$
+ *
+ * mode filter
+ *
+ * history:
+ * 2002-06-08 fl Created (based on code from IFUNC95)
+ * 2004-10-05 fl Rewritten; use a simpler brute-force algorithm
+ *
+ * Copyright (c) Secret Labs AB 2002-2004. All rights reserved.
+ *
+ * See the README file for information on usage and redistribution.
+ */
+
+#include "Imaging.h"
+
+Imaging
+ImagingModeFilter(Imaging im, int size) {
+ Imaging imOut;
+ int x, y, i;
+ int xx, yy;
+ int maxcount;
+ UINT8 maxpixel;
+ int histogram[256];
+
+ if (!im || im->bands != 1 || im->type != IMAGING_TYPE_UINT8) {
+ return (Imaging)ImagingError_ModeError();
+ }
+
+ imOut = ImagingNewDirty(im->mode, im->xsize, im->ysize);
+ if (!imOut) {
+ return NULL;
+ }
+
+ size = size / 2;
+
+ for (y = 0; y < imOut->ysize; y++) {
+ UINT8 *out = &IMAGING_PIXEL_L(imOut, 0, y);
+ for (x = 0; x < imOut->xsize; x++) {
+ /* calculate histogram over current area */
+
+ /* FIXME: brute force! to improve, update the histogram
+ incrementally. may also add a "frequent list", like in
+ the old implementation, but I'm not sure that's worth
+ the added complexity... */
+
+ memset(histogram, 0, sizeof(histogram));
+ for (yy = y - size; yy <= y + size; yy++) {
+ if (yy >= 0 && yy < imOut->ysize) {
+ UINT8 *in = &IMAGING_PIXEL_L(im, 0, yy);
+ for (xx = x - size; xx <= x + size; xx++) {
+ if (xx >= 0 && xx < imOut->xsize) {
+ histogram[in[xx]]++;
+ }
+ }
+ }
+ }
+
+ /* find most frequent pixel value in this region */
+ maxpixel = 0;
+ maxcount = histogram[maxpixel];
+ for (i = 1; i < 256; i++) {
+ if (histogram[i] > maxcount) {
+ maxcount = histogram[i];
+ maxpixel = (UINT8)i;
+ }
+ }
+
+ if (maxcount > 2) {
+ out[x] = maxpixel;
+ } else {
+ out[x] = IMAGING_PIXEL_L(im, x, y);
+ }
+ }
+ }
+
+ ImagingCopyPalette(imOut, im);
+
+ return imOut;
+}
diff --git a/contrib/python/Pillow/py3/libImaging/Negative.c b/contrib/python/Pillow/py3/libImaging/Negative.c
new file mode 100644
index 00000000000..70b96c39772
--- /dev/null
+++ b/contrib/python/Pillow/py3/libImaging/Negative.c
@@ -0,0 +1,42 @@
+/*
+ * The Python Imaging Library
+ * $Id$
+ *
+ * negate image
+ *
+ * to do:
+ * FIXME: Maybe this should be implemented using ImagingPoint()
+ *
+ * history:
+ * 95-11-27 fl: Created
+ *
+ * Copyright (c) Fredrik Lundh 1995.
+ * Copyright (c) Secret Labs AB 1997.
+ *
+ * See the README file for information on usage and redistribution.
+ */
+
+#include "Imaging.h"
+
+Imaging
+ImagingNegative(Imaging im) {
+ Imaging imOut;
+ int x, y;
+
+ if (!im) {
+ return (Imaging)ImagingError_ModeError();
+ }
+
+ imOut = ImagingNewDirty(im->mode, im->xsize, im->ysize);
+ if (!imOut) {
+ return NULL;
+ }
+
+ for (y = 0; y < im->ysize; y++) {
+ for (x = 0; x < im->linesize; x++) {
+ imOut->image[y][x] = ~im->image[y][x];
+ }
+ }
+
+ return imOut;
+}
diff --git a/contrib/python/Pillow/py3/libImaging/Offset.c b/contrib/python/Pillow/py3/libImaging/Offset.c
new file mode 100644
index 00000000000..91ee91083cc
--- /dev/null
+++ b/contrib/python/Pillow/py3/libImaging/Offset.c
@@ -0,0 +1,64 @@
+/*
+ * The Python Imaging Library
+ * $Id$
+ *
+ * offset an image in x and y directions
+ *
+ * history:
+ * 96-07-22 fl: Created
+ * 98-11-01 [email protected]: Fixed negative-array index bug
+ *
+ * Copyright (c) Fredrik Lundh 1996.
+ * Copyright (c) Secret Labs AB 1997.
+ *
+ * See the README file for information on usage and redistribution.
+ */
+
+#include "Imaging.h"
+
+Imaging
+ImagingOffset(Imaging im, int xoffset, int yoffset) {
+ int x, y;
+ Imaging imOut;
+
+ if (!im) {
+ return (Imaging)ImagingError_ModeError();
+ }
+
+ imOut = ImagingNewDirty(im->mode, im->xsize, im->ysize);
+ if (!imOut) {
+ return NULL;
+ }
+
+ ImagingCopyPalette(imOut, im);
+
+ /* make offsets positive to avoid negative coordinates */
+ xoffset %= im->xsize;
+ xoffset = im->xsize - xoffset;
+ if (xoffset < 0) {
+ xoffset += im->xsize;
+ }
+
+ yoffset %= im->ysize;
+ yoffset = im->ysize - yoffset;
+ if (yoffset < 0) {
+ yoffset += im->ysize;
+ }
+
+#define OFFSET(image) \
+ for (y = 0; y < im->ysize; y++) { \
+ for (x = 0; x < im->xsize; x++) { \
+ int yi = (y + yoffset) % im->ysize; \
+ int xi = (x + xoffset) % im->xsize; \
+ imOut->image[y][x] = im->image[yi][xi]; \
+ } \
+ }
+
+ if (im->image8) {
+ OFFSET(image8)
+ } else {
+ OFFSET(image32)
+ }
+
+ return imOut;
+}
diff --git a/contrib/python/Pillow/py3/libImaging/Pack.c b/contrib/python/Pillow/py3/libImaging/Pack.c
new file mode 100644
index 00000000000..14c8f1461aa
--- /dev/null
+++ b/contrib/python/Pillow/py3/libImaging/Pack.c
@@ -0,0 +1,693 @@
+/*
+ * The Python Imaging Library.
+ * $Id$
+ *
+ * code to pack raw data
+ *
+ * history:
+ * 1996-04-30 fl Created
+ * 1996-05-12 fl Published a few RGB packers
+ * 1996-11-01 fl More RGB packers (Tk booster stuff)
+ * 1996-12-30 fl Added P;1, P;2 and P;4 packers
+ * 1997-06-02 fl Added F (F;32NF) packer
+ * 1997-08-28 fl Added 1 as L packer
+ * 1998-02-08 fl Added I packer
+ * 1998-03-09 fl Added mode field, RGBA/RGBX as RGB packers
+ * 1998-07-01 fl Added YCbCr support
+ * 1998-07-12 fl Added I 16 packer
+ * 1999-02-03 fl Added BGR packers
+ * 2003-09-26 fl Added LA/PA packers
+ * 2006-06-22 fl Added CMYK;I packer
+ *
+ * Copyright (c) 1997-2006 by Secret Labs AB.
+ * Copyright (c) 1996-1997 by Fredrik Lundh.
+ *
+ * See the README file for information on usage and redistribution.
+ */
+
+#include "Imaging.h"
+
+#define R 0
+#define G 1
+#define B 2
+#define X 3
+#define A 3
+
+#define C 0
+#define M 1
+#define Y 2
+#define K 3
+
+/* byte swapping macros */
+
+#define C16N (out[0] = tmp[0], out[1] = tmp[1]);
+#define C16S (out[1] = tmp[0], out[0] = tmp[1]);
+#define C32N (out[0] = tmp[0], out[1] = tmp[1], out[2] = tmp[2], out[3] = tmp[3]);
+#define C32S (out[3] = tmp[0], out[2] = tmp[1], out[1] = tmp[2], out[0] = tmp[3]);
+#define C64N \
+ (out[0] = tmp[0], \
+ out[1] = tmp[1], \
+ out[2] = tmp[2], \
+ out[3] = tmp[3], \
+ out[4] = tmp[4], \
+ out[5] = tmp[5], \
+ out[6] = tmp[6], \
+ out[7] = tmp[7]);
+#define C64S \
+ (out[7] = tmp[0], \
+ out[6] = tmp[1], \
+ out[5] = tmp[2], \
+ out[4] = tmp[3], \
+ out[3] = tmp[4], \
+ out[2] = tmp[5], \
+ out[1] = tmp[6], \
+ out[0] = tmp[7]);
+
+#ifdef WORDS_BIGENDIAN
+#define C16B C16N
+#define C16L C16S
+#define C32B C32N
+#define C32L C32S
+#define C64B C64N
+#define C64L C64S
+#else
+#define C16B C16S
+#define C16L C16N
+#define C32B C32S
+#define C32L C32N
+#define C64B C64S
+#define C64L C64N
+#endif
+
+static void
+pack1(UINT8 *out, const UINT8 *in, int pixels) {
+ int i, m, b;
+ /* bilevel (black is 0) */
+ b = 0;
+ m = 128;
+ for (i = 0; i < pixels; i++) {
+ if (in[i] != 0) {
+ b |= m;
+ }
+ m >>= 1;
+ if (m == 0) {
+ *out++ = b;
+ b = 0;
+ m = 128;
+ }
+ }
+ if (m != 128) {
+ *out++ = b;
+ }
+}
+
+static void
+pack1I(UINT8 *out, const UINT8 *in, int pixels) {
+ int i, m, b;
+ /* bilevel (black is 1) */
+ b = 0;
+ m = 128;
+ for (i = 0; i < pixels; i++) {
+ if (in[i] == 0) {
+ b |= m;
+ }
+ m >>= 1;
+ if (m == 0) {
+ *out++ = b;
+ b = 0;
+ m = 128;
+ }
+ }
+ if (m != 128) {
+ *out++ = b;
+ }
+}
+
+static void
+pack1R(UINT8 *out, const UINT8 *in, int pixels) {
+ int i, m, b;
+ /* bilevel, lsb first (black is 0) */
+ b = 0;
+ m = 1;
+ for (i = 0; i < pixels; i++) {
+ if (in[i] != 0) {
+ b |= m;
+ }
+ m <<= 1;
+ if (m == 256) {
+ *out++ = b;
+ b = 0;
+ m = 1;
+ }
+ }
+ if (m != 1) {
+ *out++ = b;
+ }
+}
+
+static void
+pack1IR(UINT8 *out, const UINT8 *in, int pixels) {
+ int i, m, b;
+ /* bilevel, lsb first (black is 1) */
+ b = 0;
+ m = 1;
+ for (i = 0; i < pixels; i++) {
+ if (in[i] == 0) {
+ b |= m;
+ }
+ m <<= 1;
+ if (m == 256) {
+ *out++ = b;
+ b = 0;
+ m = 1;
+ }
+ }
+ if (m != 1) {
+ *out++ = b;
+ }
+}
+
+static void
+pack1L(UINT8 *out, const UINT8 *in, int pixels) {
+ int i;
+ /* bilevel, stored as bytes */
+ for (i = 0; i < pixels; i++) {
+ out[i] = (in[i] != 0) ? 255 : 0;
+ }
+}
+
+static void
+packP4(UINT8 *out, const UINT8 *in, int pixels) {
+ while (pixels >= 2) {
+ *out++ = (in[0] << 4) | (in[1] & 15);
+ in += 2;
+ pixels -= 2;
+ }
+
+ if (pixels) {
+ out[0] = (in[0] << 4);
+ }
+}
+
+static void
+packP2(UINT8 *out, const UINT8 *in, int pixels) {
+ while (pixels >= 4) {
+ *out++ = (in[0] << 6) | ((in[1] & 3) << 4) | ((in[2] & 3) << 2) | (in[3] & 3);
+ in += 4;
+ pixels -= 4;
+ }
+
+ switch (pixels) {
+ case 3:
+ out[0] = (in[0] << 6) | ((in[1] & 3) << 4) | ((in[2] & 3) << 2);
+ break;
+ case 2:
+ out[0] = (in[0] << 6) | ((in[1] & 3) << 4);
+ break;
+ case 1:
+ out[0] = (in[0] << 6);
+ }
+}
+
+static void
+packL16(UINT8 *out, const UINT8 *in, int pixels) {
+ int i;
+ /* L -> L;16, e.g: \xff77 -> \x00\xff\x00\x77 */
+ for (i = 0; i < pixels; i++) {
+ out[0] = 0;
+ out[1] = in[i];
+ out += 2;
+ }
+}
+
+static void
+packL16B(UINT8 *out, const UINT8 *in, int pixels) {
+ int i;
+ /* L -> L;16B, e.g: \xff77 -> \xff\x00\x77\x00 */
+ for (i = 0; i < pixels; i++) {
+ out[0] = in[i];
+ out[1] = 0;
+ out += 2;
+ }
+}
+
+static void
+packLA(UINT8 *out, const UINT8 *in, int pixels) {
+ int i;
+ /* LA, pixel interleaved */
+ for (i = 0; i < pixels; i++) {
+ out[0] = in[R];
+ out[1] = in[A];
+ out += 2;
+ in += 4;
+ }
+}
+
+static void
+packLAL(UINT8 *out, const UINT8 *in, int pixels) {
+ int i;
+ /* LA, line interleaved */
+ for (i = 0; i < pixels; i++) {
+ out[i] = in[R];
+ out[i + pixels] = in[A];
+ in += 4;
+ }
+}
+
+void
+ImagingPackRGB(UINT8 *out, const UINT8 *in, int pixels) {
+ int i = 0;
+ /* RGB triplets */
+#ifdef __sparc
+ /* SPARC CPUs cannot read integers from nonaligned addresses. */
+ for (; i < pixels; i++) {
+ out[0] = in[R];
+ out[1] = in[G];
+ out[2] = in[B];
+ out += 3;
+ in += 4;
+ }
+#else
+ for (; i < pixels - 1; i++) {
+ memcpy(out, in + i * 4, 4);
+ out += 3;
+ }
+ for (; i < pixels; i++) {
+ out[0] = in[i * 4 + R];
+ out[1] = in[i * 4 + G];
+ out[2] = in[i * 4 + B];
+ out += 3;
+ }
+#endif
+}
+
+void
+ImagingPackXRGB(UINT8 *out, const UINT8 *in, int pixels) {
+ int i;
+ /* XRGB, triplets with left padding */
+ for (i = 0; i < pixels; i++) {
+ out[0] = 0;
+ out[1] = in[R];
+ out[2] = in[G];
+ out[3] = in[B];
+ out += 4;
+ in += 4;
+ }
+}
+
+void
+ImagingPackBGR(UINT8 *out, const UINT8 *in, int pixels) {
+ int i;
+ /* RGB, reversed bytes */
+ for (i = 0; i < pixels; i++) {
+ out[0] = in[B];
+ out[1] = in[G];
+ out[2] = in[R];
+ out += 3;
+ in += 4;
+ }
+}
+
+void
+ImagingPackBGRX(UINT8 *out, const UINT8 *in, int pixels) {
+ int i;
+ /* BGRX, reversed bytes with right padding */
+ for (i = 0; i < pixels; i++) {
+ out[0] = in[B];
+ out[1] = in[G];
+ out[2] = in[R];
+ out[3] = 0;
+ out += 4;
+ in += 4;
+ }
+}
+
+void
+ImagingPackXBGR(UINT8 *out, const UINT8 *in, int pixels) {
+ int i;
+ /* XBGR, reversed bytes with left padding */
+ for (i = 0; i < pixels; i++) {
+ out[0] = 0;
+ out[1] = in[B];
+ out[2] = in[G];
+ out[3] = in[R];
+ out += 4;
+ in += 4;
+ }
+}
+
+void
+ImagingPackBGRA(UINT8 *out, const UINT8 *in, int pixels) {
+ int i;
+ /* BGRX, reversed bytes with right padding */
+ for (i = 0; i < pixels; i++) {
+ out[0] = in[B];
+ out[1] = in[G];
+ out[2] = in[R];
+ out[3] = in[A];
+ out += 4;
+ in += 4;
+ }
+}
+
+void
+ImagingPackABGR(UINT8 *out, const UINT8 *in, int pixels) {
+ int i;
+ /* XBGR, reversed bytes with left padding */
+ for (i = 0; i < pixels; i++) {
+ out[0] = in[A];
+ out[1] = in[B];
+ out[2] = in[G];
+ out[3] = in[R];
+ out += 4;
+ in += 4;
+ }
+}
+
+void
+ImagingPackBGRa(UINT8 *out, const UINT8 *in, int pixels) {
+ int i;
+ /* BGRa, reversed bytes with premultiplied alpha */
+ for (i = 0; i < pixels; i++) {
+ int alpha = out[3] = in[A];
+ int tmp;
+ out[0] = MULDIV255(in[B], alpha, tmp);
+ out[1] = MULDIV255(in[G], alpha, tmp);
+ out[2] = MULDIV255(in[R], alpha, tmp);
+ out += 4;
+ in += 4;
+ }
+}
+
+static void
+packRGBL(UINT8 *out, const UINT8 *in, int pixels) {
+ int i;
+ /* RGB, line interleaved */
+ for (i = 0; i < pixels; i++) {
+ out[i] = in[R];
+ out[i + pixels] = in[G];
+ out[i + pixels + pixels] = in[B];
+ in += 4;
+ }
+}
+
+static void
+packRGBXL(UINT8 *out, const UINT8 *in, int pixels) {
+ int i;
+ /* RGBX, line interleaved */
+ for (i = 0; i < pixels; i++) {
+ out[i] = in[R];
+ out[i + pixels] = in[G];
+ out[i + pixels + pixels] = in[B];
+ out[i + pixels + pixels + pixels] = in[X];
+ in += 4;
+ }
+}
+
+static void
+packI16B(UINT8 *out, const UINT8 *in_, int pixels) {
+ int i;
+ UINT16 tmp_;
+ UINT8 *tmp = (UINT8 *)&tmp_;
+ for (i = 0; i < pixels; i++) {
+ INT32 in;
+ memcpy(&in, in_, sizeof(in));
+ if (in <= 0) {
+ tmp_ = 0;
+ } else if (in > 65535) {
+ tmp_ = 65535;
+ } else {
+ tmp_ = in;
+ }
+ C16B;
+ out += 2;
+ in_ += sizeof(in);
+ }
+}
+
+static void
+packI16N_I16B(UINT8 *out, const UINT8 *in, int pixels) {
+ int i;
+ UINT8 *tmp = (UINT8 *)in;
+ for (i = 0; i < pixels; i++) {
+ C16B;
+ out += 2;
+ tmp += 2;
+ }
+}
+static void
+packI16N_I16(UINT8 *out, const UINT8 *in, int pixels) {
+ int i;
+ UINT8 *tmp = (UINT8 *)in;
+ for (i = 0; i < pixels; i++) {
+ C16L;
+ out += 2;
+ tmp += 2;
+ }
+}
+
+static void
+packI32S(UINT8 *out, const UINT8 *in, int pixels) {
+ int i;
+ UINT8 *tmp = (UINT8 *)in;
+ for (i = 0; i < pixels; i++) {
+ C32L;
+ out += 4;
+ tmp += 4;
+ }
+}
+
+void
+ImagingPackLAB(UINT8 *out, const UINT8 *in, int pixels) {
+ int i;
+ /* LAB triplets */
+ for (i = 0; i < pixels; i++) {
+ out[0] = in[0];
+ out[1] = in[1] ^ 128; /* signed in outside world */
+ out[2] = in[2] ^ 128;
+ out += 3;
+ in += 4;
+ }
+}
+
+static void
+copy1(UINT8 *out, const UINT8 *in, int pixels) {
+ /* L, P */
+ memcpy(out, in, pixels);
+}
+
+static void
+copy2(UINT8 *out, const UINT8 *in, int pixels) {
+ /* I;16, etc */
+ memcpy(out, in, pixels * 2);
+}
+
+static void
+copy3(UINT8 *out, const UINT8 *in, int pixels) {
+ /* BGR;24, etc */
+ memcpy(out, in, pixels * 3);
+}
+
+static void
+copy4(UINT8 *out, const UINT8 *in, int pixels) {
+ /* RGBA, CMYK quadruples */
+ memcpy(out, in, 4 * pixels);
+}
+
+static void
+copy4I(UINT8 *out, const UINT8 *in, int pixels) {
+ /* RGBA, CMYK quadruples, inverted */
+ int i;
+ for (i = 0; i < pixels * 4; i++) {
+ out[i] = ~in[i];
+ }
+}
+
+static void
+band0(UINT8 *out, const UINT8 *in, int pixels) {
+ int i;
+ for (i = 0; i < pixels; i++, in += 4) {
+ out[i] = in[0];
+ }
+}
+
+static void
+band1(UINT8 *out, const UINT8 *in, int pixels) {
+ int i;
+ for (i = 0; i < pixels; i++, in += 4) {
+ out[i] = in[1];
+ }
+}
+
+static void
+band2(UINT8 *out, const UINT8 *in, int pixels) {
+ int i;
+ for (i = 0; i < pixels; i++, in += 4) {
+ out[i] = in[2];
+ }
+}
+
+static void
+band3(UINT8 *out, const UINT8 *in, int pixels) {
+ int i;
+ for (i = 0; i < pixels; i++, in += 4) {
+ out[i] = in[3];
+ }
+}
+
+static struct {
+ const char *mode;
+ const char *rawmode;
+ int bits;
+ ImagingShuffler pack;
+} packers[] = {
+
+ /* bilevel */
+ {"1", "1", 1, pack1},
+ {"1", "1;I", 1, pack1I},
+ {"1", "1;R", 1, pack1R},
+ {"1", "1;IR", 1, pack1IR},
+ {"1", "L", 8, pack1L},
+
+ /* greyscale */
+ {"L", "L", 8, copy1},
+ {"L", "L;16", 16, packL16},
+ {"L", "L;16B", 16, packL16B},
+
+ /* greyscale w. alpha */
+ {"LA", "LA", 16, packLA},
+ {"LA", "LA;L", 16, packLAL},
+
+ /* greyscale w. alpha premultiplied */
+ {"La", "La", 16, packLA},
+
+ /* palette */
+ {"P", "P;1", 1, pack1},
+ {"P", "P;2", 2, packP2},
+ {"P", "P;4", 4, packP4},
+ {"P", "P", 8, copy1},
+
+ /* palette w. alpha */
+ {"PA", "PA", 16, packLA},
+ {"PA", "PA;L", 16, packLAL},
+
+ /* true colour */
+ {"RGB", "RGB", 24, ImagingPackRGB},
+ {"RGB", "RGBX", 32, copy4},
+ {"RGB", "RGBA", 32, copy4},
+ {"RGB", "XRGB", 32, ImagingPackXRGB},
+ {"RGB", "BGR", 24, ImagingPackBGR},
+ {"RGB", "BGRX", 32, ImagingPackBGRX},
+ {"RGB", "XBGR", 32, ImagingPackXBGR},
+ {"RGB", "RGB;L", 24, packRGBL},
+ {"RGB", "R", 8, band0},
+ {"RGB", "G", 8, band1},
+ {"RGB", "B", 8, band2},
+
+ /* true colour w. alpha */
+ {"RGBA", "RGBA", 32, copy4},
+ {"RGBA", "RGBA;L", 32, packRGBXL},
+ {"RGBA", "RGB", 24, ImagingPackRGB},
+ {"RGBA", "BGR", 24, ImagingPackBGR},
+ {"RGBA", "BGRA", 32, ImagingPackBGRA},
+ {"RGBA", "ABGR", 32, ImagingPackABGR},
+ {"RGBA", "BGRa", 32, ImagingPackBGRa},
+ {"RGBA", "R", 8, band0},
+ {"RGBA", "G", 8, band1},
+ {"RGBA", "B", 8, band2},
+ {"RGBA", "A", 8, band3},
+
+ /* true colour w. alpha premultiplied */
+ {"RGBa", "RGBa", 32, copy4},
+ {"RGBa", "BGRa", 32, ImagingPackBGRA},
+ {"RGBa", "aBGR", 32, ImagingPackABGR},
+
+ /* true colour w. padding */
+ {"RGBX", "RGBX", 32, copy4},
+ {"RGBX", "RGBX;L", 32, packRGBXL},
+ {"RGBX", "RGB", 24, ImagingPackRGB},
+ {"RGBX", "BGR", 24, ImagingPackBGR},
+ {"RGBX", "BGRX", 32, ImagingPackBGRX},
+ {"RGBX", "XBGR", 32, ImagingPackXBGR},
+ {"RGBX", "R", 8, band0},
+ {"RGBX", "G", 8, band1},
+ {"RGBX", "B", 8, band2},
+ {"RGBX", "X", 8, band3},
+
+ /* colour separation */
+ {"CMYK", "CMYK", 32, copy4},
+ {"CMYK", "CMYK;I", 32, copy4I},
+ {"CMYK", "CMYK;L", 32, packRGBXL},
+ {"CMYK", "C", 8, band0},
+ {"CMYK", "M", 8, band1},
+ {"CMYK", "Y", 8, band2},
+ {"CMYK", "K", 8, band3},
+
+ /* video (YCbCr) */
+ {"YCbCr", "YCbCr", 24, ImagingPackRGB},
+ {"YCbCr", "YCbCr;L", 24, packRGBL},
+ {"YCbCr", "YCbCrX", 32, copy4},
+ {"YCbCr", "YCbCrK", 32, copy4},
+ {"YCbCr", "Y", 8, band0},
+ {"YCbCr", "Cb", 8, band1},
+ {"YCbCr", "Cr", 8, band2},
+
+ /* LAB Color */
+ {"LAB", "LAB", 24, ImagingPackLAB},
+ {"LAB", "L", 8, band0},
+ {"LAB", "A", 8, band1},
+ {"LAB", "B", 8, band2},
+
+ /* HSV */
+ {"HSV", "HSV", 24, ImagingPackRGB},
+ {"HSV", "H", 8, band0},
+ {"HSV", "S", 8, band1},
+ {"HSV", "V", 8, band2},
+
+ /* integer */
+ {"I", "I", 32, copy4},
+ {"I", "I;16B", 16, packI16B},
+ {"I", "I;32S", 32, packI32S},
+ {"I", "I;32NS", 32, copy4},
+
+ /* floating point */
+ {"F", "F", 32, copy4},
+ {"F", "F;32F", 32, packI32S},
+ {"F", "F;32NF", 32, copy4},
+
+ /* storage modes */
+ {"I;16", "I;16", 16, copy2},
+#ifdef WORDS_BIGENDIAN
+ {"I;16", "I;16B", 16, packI16N_I16},
+#else
+ {"I;16", "I;16B", 16, packI16N_I16B},
+#endif
+ {"I;16B", "I;16B", 16, copy2},
+ {"I;16L", "I;16L", 16, copy2},
+ {"I;16N", "I;16N", 16, copy2},
+ {"I;16", "I;16N", 16, packI16N_I16}, // LibTiff native->image endian.
+ {"I;16L", "I;16N", 16, packI16N_I16},
+ {"I;16B", "I;16N", 16, packI16N_I16B},
+ {"BGR;15", "BGR;15", 16, copy2},
+ {"BGR;16", "BGR;16", 16, copy2},
+ {"BGR;24", "BGR;24", 24, copy3},
+
+ {NULL} /* sentinel */
+};
+
+ImagingShuffler
+ImagingFindPacker(const char *mode, const char *rawmode, int *bits_out) {
+ int i;
+
+ /* find a suitable pixel packer */
+ for (i = 0; packers[i].rawmode; i++) {
+ if (strcmp(packers[i].mode, mode) == 0 &&
+ strcmp(packers[i].rawmode, rawmode) == 0) {
+ if (bits_out) {
+ *bits_out = packers[i].bits;
+ }
+ return packers[i].pack;
+ }
+ }
+ return NULL;
+}
diff --git a/contrib/python/Pillow/py3/libImaging/PackDecode.c b/contrib/python/Pillow/py3/libImaging/PackDecode.c
new file mode 100644
index 00000000000..7dd432b91c2
--- /dev/null
+++ b/contrib/python/Pillow/py3/libImaging/PackDecode.c
@@ -0,0 +1,92 @@
+/*
+ * The Python Imaging Library.
+ * $Id$
+ *
+ * decoder for PackBits image data.
+ *
+ * history:
+ * 96-04-19 fl Created
+ *
+ * Copyright (c) Fredrik Lundh 1996.
+ * Copyright (c) Secret Labs AB 1997.
+ *
+ * See the README file for information on usage and redistribution.
+ */
+
+#include "Imaging.h"
+
+int
+ImagingPackbitsDecode(
+ Imaging im, ImagingCodecState state, UINT8 *buf, Py_ssize_t bytes) {
+ UINT8 n;
+ UINT8 *ptr;
+ int i;
+
+ ptr = buf;
+
+ for (;;) {
+ if (bytes < 1) {
+ return ptr - buf;
+ }
+
+ if (ptr[0] & 0x80) {
+ if (ptr[0] == 0x80) {
+ /* Nop */
+ ptr++;
+ bytes--;
+ continue;
+ }
+
+ /* Run */
+ if (bytes < 2) {
+ return ptr - buf;
+ }
+
+ for (n = 257 - ptr[0]; n > 0; n--) {
+ if (state->x >= state->bytes) {
+ /* state->errcode = IMAGING_CODEC_OVERRUN; */
+ break;
+ }
+ state->buffer[state->x++] = ptr[1];
+ }
+
+ ptr += 2;
+ bytes -= 2;
+
+ } else {
+ /* Literal */
+ n = ptr[0] + 2;
+
+ if (bytes < n) {
+ return ptr - buf;
+ }
+
+ for (i = 1; i < n; i++) {
+ if (state->x >= state->bytes) {
+ /* state->errcode = IMAGING_CODEC_OVERRUN; */
+ break;
+ }
+ state->buffer[state->x++] = ptr[i];
+ }
+
+ ptr += n;
+ bytes -= n;
+ }
+
+ if (state->x >= state->bytes) {
+ /* Got a full line, unpack it */
+ state->shuffle(
+ (UINT8 *)im->image[state->y + state->yoff] +
+ state->xoff * im->pixelsize,
+ state->buffer,
+ state->xsize);
+
+ state->x = 0;
+
+ if (++state->y >= state->ysize) {
+ /* End of file (errcode = 0) */
+ return -1;
+ }
+ }
+ }
+}
diff --git a/contrib/python/Pillow/py3/libImaging/Palette.c b/contrib/python/Pillow/py3/libImaging/Palette.c
new file mode 100644
index 00000000000..059d7b72aca
--- /dev/null
+++ b/contrib/python/Pillow/py3/libImaging/Palette.c
@@ -0,0 +1,307 @@
+/*
+ * The Python Imaging Library
+ * $Id$
+ *
+ * imaging palette object
+ *
+ * history:
+ * 1996-05-05 fl Added to library
+ * 1996-05-27 fl Added colour mapping stuff
+ * 1997-05-12 fl Support RGBA palettes
+ * 2005-02-09 fl Removed grayscale entries from web palette
+ *
+ * Copyright (c) Secret Labs AB 1997-2005. All rights reserved.
+ * Copyright (c) Fredrik Lundh 1995-1997.
+ *
+ * See the README file for information on usage and redistribution.
+ */
+
+#include "Imaging.h"
+
+#include <math.h>
+
+ImagingPalette
+ImagingPaletteNew(const char *mode) {
+ /* Create a palette object */
+
+ int i;
+ ImagingPalette palette;
+
+ if (strcmp(mode, "RGB") && strcmp(mode, "RGBA")) {
+ return (ImagingPalette)ImagingError_ModeError();
+ }
+
+ palette = calloc(1, sizeof(struct ImagingPaletteInstance));
+ if (!palette) {
+ return (ImagingPalette)ImagingError_MemoryError();
+ }
+
+ strncpy(palette->mode, mode, IMAGING_MODE_LENGTH - 1);
+ palette->mode[IMAGING_MODE_LENGTH - 1] = 0;
+
+ palette->size = 0;
+ for (i = 0; i < 256; i++) {
+ palette->palette[i * 4 + 3] = 255; /* opaque */
+ }
+
+ return palette;
+}
+
+ImagingPalette
+ImagingPaletteNewBrowser(void) {
+ /* Create a standard "browser" palette object */
+
+ int i, r, g, b;
+ ImagingPalette palette;
+
+ palette = ImagingPaletteNew("RGB");
+ if (!palette) {
+ return NULL;
+ }
+
+ /* FIXME: Add 10-level windows palette here? */
+
+ /* Simple 6x6x6 colour cube */
+ i = 10;
+ for (b = 0; b < 256; b += 51) {
+ for (g = 0; g < 256; g += 51) {
+ for (r = 0; r < 256; r += 51) {
+ palette->palette[i * 4 + 0] = r;
+ palette->palette[i * 4 + 1] = g;
+ palette->palette[i * 4 + 2] = b;
+ i++;
+ }
+ }
+ }
+ palette->size = i;
+
+ /* FIXME: add 30-level greyscale wedge here? */
+
+ return palette;
+}
+
+ImagingPalette
+ImagingPaletteDuplicate(ImagingPalette palette) {
+ /* Duplicate palette descriptor */
+
+ ImagingPalette new_palette;
+
+ if (!palette) {
+ return NULL;
+ }
+ /* malloc check ok, small constant allocation */
+ new_palette = malloc(sizeof(struct ImagingPaletteInstance));
+ if (!new_palette) {
+ return (ImagingPalette)ImagingError_MemoryError();
+ }
+
+ memcpy(new_palette, palette, sizeof(struct ImagingPaletteInstance));
+
+ /* Don't share the cache */
+ new_palette->cache = NULL;
+
+ return new_palette;
+}
+
+void
+ImagingPaletteDelete(ImagingPalette palette) {
+ /* Destroy palette object */
+
+ if (palette) {
+ if (palette->cache) {
+ free(palette->cache);
+ }
+ free(palette);
+ }
+}
+
+/* -------------------------------------------------------------------- */
+/* Colour mapping */
+/* -------------------------------------------------------------------- */
+
+/* This code is used to map RGB triplets to palette indices, using
+ a palette index cache. */
+
+/*
+ * This implementation is loosely based on the corresponding code in
+ * the IJG JPEG library by Thomas G. Lane. Original algorithms by
+ * Paul Heckbert and Spencer W. Thomas.
+ *
+ * The IJG JPEG library is copyright (C) 1991-1995, Thomas G. Lane. */
+
+#define DIST(a, b, s) (a - b) * (a - b) * s
+
+/* Colour weights (no scaling, for now) */
+#define RSCALE 1
+#define GSCALE 1
+#define BSCALE 1
+
+/* Calculated scaled distances */
+#define RDIST(a, b) DIST(a, b, RSCALE *RSCALE)
+#define GDIST(a, b) DIST(a, b, GSCALE *GSCALE)
+#define BDIST(a, b) DIST(a, b, BSCALE *BSCALE)
+
+/* Incremental steps */
+#define RSTEP (4 * RSCALE)
+#define GSTEP (4 * GSCALE)
+#define BSTEP (4 * BSCALE)
+
+#define BOX 8
+
+#define BOXVOLUME BOX *BOX *BOX
+
+void
+ImagingPaletteCacheUpdate(ImagingPalette palette, int r, int g, int b) {
+ int i, j;
+ unsigned int dmin[256], dmax;
+ int r0, g0, b0;
+ int r1, g1, b1;
+ int rc, gc, bc;
+ unsigned int d[BOXVOLUME];
+ UINT8 c[BOXVOLUME];
+
+ /* Get box boundaries for the given (r,g,b)-triplet. Each box
+ covers eight cache slots (32 colour values, that is). */
+
+ r0 = r & 0xe0;
+ r1 = r0 + 0x1f;
+ rc = (r0 + r1) / 2;
+ g0 = g & 0xe0;
+ g1 = g0 + 0x1f;
+ gc = (g0 + g1) / 2;
+ b0 = b & 0xe0;
+ b1 = b0 + 0x1f;
+ bc = (b0 + b1) / 2;
+
+ /* Step 1 -- Select relevant palette entries (after Heckbert) */
+
+ /* For each palette entry, calculate the min and max distances to
+ * any position in the box given by the colour we're looking for. */
+
+ dmax = (unsigned int)~0;
+
+ for (i = 0; i < palette->size; i++) {
+ int r, g, b;
+ unsigned int tmin, tmax;
+
+ /* Find min and max distances to any point in the box */
+ r = palette->palette[i * 4 + 0];
+ tmin = (r < r0) ? RDIST(r, r0) : (r > r1) ? RDIST(r, r1) : 0;
+ tmax = (r <= rc) ? RDIST(r, r1) : RDIST(r, r0);
+
+ g = palette->palette[i * 4 + 1];
+ tmin += (g < g0) ? GDIST(g, g0) : (g > g1) ? GDIST(g, g1) : 0;
+ tmax += (g <= gc) ? GDIST(g, g1) : GDIST(g, g0);
+
+ b = palette->palette[i * 4 + 2];
+ tmin += (b < b0) ? BDIST(b, b0) : (b > b1) ? BDIST(b, b1) : 0;
+ tmax += (b <= bc) ? BDIST(b, b1) : BDIST(b, b0);
+
+ dmin[i] = tmin;
+ if (tmax < dmax) {
+ dmax = tmax; /* keep the smallest max distance only */
+ }
+ }
+
+ /* Step 2 -- Incrementally update cache slot (after Thomas) */
+
+ /* Find the box containing the nearest palette entry, and update
+ * all slots in that box. We only check boxes for which the min
+ * distance is less than or equal the smallest max distance */
+
+ for (i = 0; i < BOXVOLUME; i++) {
+ d[i] = (unsigned int)~0;
+ }
+
+ for (i = 0; i < palette->size; i++) {
+ if (dmin[i] <= dmax) {
+ int rd, gd, bd;
+ int ri, gi, bi;
+ int rx, gx, bx;
+
+ ri = (r0 - palette->palette[i * 4 + 0]) * RSCALE;
+ gi = (g0 - palette->palette[i * 4 + 1]) * GSCALE;
+ bi = (b0 - palette->palette[i * 4 + 2]) * BSCALE;
+
+ rd = ri * ri + gi * gi + bi * bi;
+
+ ri = ri * (2 * RSTEP) + RSTEP * RSTEP;
+ gi = gi * (2 * GSTEP) + GSTEP * GSTEP;
+ bi = bi * (2 * BSTEP) + BSTEP * BSTEP;
+
+ rx = ri;
+ for (r = j = 0; r < BOX; r++) {
+ gd = rd;
+ gx = gi;
+ for (g = 0; g < BOX; g++) {
+ bd = gd;
+ bx = bi;
+ for (b = 0; b < BOX; b++) {
+ if ((unsigned int)bd < d[j]) {
+ d[j] = bd;
+ c[j] = (UINT8)i;
+ }
+ bd += bx;
+ bx += 2 * BSTEP * BSTEP;
+ j++;
+ }
+ gd += gx;
+ gx += 2 * GSTEP * GSTEP;
+ }
+ rd += rx;
+ rx += 2 * RSTEP * RSTEP;
+ }
+ }
+ }
+
+ /* Step 3 -- Update cache */
+
+ /* The c array now contains the closest match for each
+ * cache slot in the box. Update the cache. */
+
+ j = 0;
+ for (r = r0; r < r1; r += 4) {
+ for (g = g0; g < g1; g += 4) {
+ for (b = b0; b < b1; b += 4) {
+ ImagingPaletteCache(palette, r, g, b) = c[j++];
+ }
+ }
+ }
+}
+
+int
+ImagingPaletteCachePrepare(ImagingPalette palette) {
+ /* Add a colour cache to a palette */
+
+ int i;
+ int entries = 64 * 64 * 64;
+
+ if (palette->cache == NULL) {
+ /* The cache is 512k. It might be a good idea to break it
+ up into a pointer array (e.g. an 8-bit image?) */
+
+ /* malloc check ok, small constant allocation */
+ palette->cache = (INT16 *)malloc(entries * sizeof(INT16));
+ if (!palette->cache) {
+ (void)ImagingError_MemoryError();
+ return -1;
+ }
+
+ /* Mark all entries as empty */
+ for (i = 0; i < entries; i++) {
+ palette->cache[i] = 0x100;
+ }
+ }
+
+ return 0;
+}
+
+void
+ImagingPaletteCacheDelete(ImagingPalette palette) {
+ /* Release the colour cache, if any */
+
+ if (palette && palette->cache) {
+ free(palette->cache);
+ palette->cache = NULL;
+ }
+}
diff --git a/contrib/python/Pillow/py3/libImaging/Paste.c b/contrib/python/Pillow/py3/libImaging/Paste.c
new file mode 100644
index 00000000000..6684b11efe3
--- /dev/null
+++ b/contrib/python/Pillow/py3/libImaging/Paste.c
@@ -0,0 +1,628 @@
+/*
+ * The Python Imaging Library
+ * $Id$
+ *
+ * paste image on another image
+ *
+ * history:
+ * 96-03-27 fl Created
+ * 96-07-16 fl Support "1", "L" and "RGBA" masks
+ * 96-08-16 fl Merged with opaque paste
+ * 97-01-17 fl Faster blending, added support for RGBa images
+ * 97-08-27 fl Faster masking for 32-bit images
+ * 98-02-02 fl Fixed MULDIV255 macro for gcc
+ * 99-02-02 fl Added "RGBa" mask support
+ * 99-02-06 fl Rewritten. Added support for masked fill operations.
+ * 99-12-08 fl Fixed matte fill.
+ *
+ * Copyright (c) Fredrik Lundh 1996-97.
+ * Copyright (c) Secret Labs AB 1997-99.
+ *
+ * See the README file for information on usage and redistribution.
+ */
+
+#include "Imaging.h"
+
+static inline void
+paste(
+ Imaging imOut,
+ Imaging imIn,
+ int dx,
+ int dy,
+ int sx,
+ int sy,
+ int xsize,
+ int ysize,
+ int pixelsize) {
+ /* paste opaque region */
+
+ int y;
+
+ dx *= pixelsize;
+ sx *= pixelsize;
+
+ xsize *= pixelsize;
+
+ for (y = 0; y < ysize; y++) {
+ memcpy(imOut->image[y + dy] + dx, imIn->image[y + sy] + sx, xsize);
+ }
+}
+
+static inline void
+paste_mask_1(
+ Imaging imOut,
+ Imaging imIn,
+ Imaging imMask,
+ int dx,
+ int dy,
+ int sx,
+ int sy,
+ int xsize,
+ int ysize,
+ int pixelsize) {
+ /* paste with mode "1" mask */
+
+ int x, y;
+
+ if (imOut->image8) {
+ for (y = 0; y < ysize; y++) {
+ UINT8 *out = imOut->image8[y + dy] + dx;
+ UINT8 *in = imIn->image8[y + sy] + sx;
+ UINT8 *mask = imMask->image8[y + sy] + sx;
+ for (x = 0; x < xsize; x++) {
+ if (*mask++) {
+ *out = *in;
+ }
+ out++, in++;
+ }
+ }
+
+ } else {
+ for (y = 0; y < ysize; y++) {
+ INT32 *out = imOut->image32[y + dy] + dx;
+ INT32 *in = imIn->image32[y + sy] + sx;
+ UINT8 *mask = imMask->image8[y + sy] + sx;
+ for (x = 0; x < xsize; x++) {
+ if (*mask++) {
+ *out = *in;
+ }
+ out++, in++;
+ }
+ }
+ }
+}
+
+static inline void
+paste_mask_L(
+ Imaging imOut,
+ Imaging imIn,
+ Imaging imMask,
+ int dx,
+ int dy,
+ int sx,
+ int sy,
+ int xsize,
+ int ysize,
+ int pixelsize) {
+ /* paste with mode "L" matte */
+
+ int x, y;
+ unsigned int tmp1;
+
+ if (imOut->image8) {
+ for (y = 0; y < ysize; y++) {
+ UINT8 *out = imOut->image8[y + dy] + dx;
+ UINT8 *in = imIn->image8[y + sy] + sx;
+ UINT8 *mask = imMask->image8[y + sy] + sx;
+ for (x = 0; x < xsize; x++) {
+ *out = BLEND(*mask, *out, *in, tmp1);
+ out++, in++, mask++;
+ }
+ }
+
+ } else {
+ for (y = 0; y < ysize; y++) {
+ UINT8 *out = (UINT8 *)(imOut->image32[y + dy] + dx);
+ UINT8 *in = (UINT8 *)(imIn->image32[y + sy] + sx);
+ UINT8 *mask = (UINT8 *)(imMask->image8[y + sy] + sx);
+ for (x = 0; x < xsize; x++) {
+ UINT8 a = mask[0];
+ out[0] = BLEND(a, out[0], in[0], tmp1);
+ out[1] = BLEND(a, out[1], in[1], tmp1);
+ out[2] = BLEND(a, out[2], in[2], tmp1);
+ out[3] = BLEND(a, out[3], in[3], tmp1);
+ out += 4;
+ in += 4;
+ mask++;
+ }
+ }
+ }
+}
+
+static inline void
+paste_mask_RGBA(
+ Imaging imOut,
+ Imaging imIn,
+ Imaging imMask,
+ int dx,
+ int dy,
+ int sx,
+ int sy,
+ int xsize,
+ int ysize,
+ int pixelsize) {
+ /* paste with mode "RGBA" matte */
+
+ int x, y;
+ unsigned int tmp1;
+
+ if (imOut->image8) {
+ for (y = 0; y < ysize; y++) {
+ UINT8 *out = imOut->image8[y + dy] + dx;
+ UINT8 *in = imIn->image8[y + sy] + sx;
+ UINT8 *mask = (UINT8 *)imMask->image[y + sy] + sx * 4 + 3;
+ for (x = 0; x < xsize; x++) {
+ *out = BLEND(*mask, *out, *in, tmp1);
+ out++, in++, mask += 4;
+ }
+ }
+
+ } else {
+ for (y = 0; y < ysize; y++) {
+ UINT8 *out = (UINT8 *)(imOut->image32[y + dy] + dx);
+ UINT8 *in = (UINT8 *)(imIn->image32[y + sy] + sx);
+ UINT8 *mask = (UINT8 *)(imMask->image32[y + sy] + sx);
+ for (x = 0; x < xsize; x++) {
+ UINT8 a = mask[3];
+ out[0] = BLEND(a, out[0], in[0], tmp1);
+ out[1] = BLEND(a, out[1], in[1], tmp1);
+ out[2] = BLEND(a, out[2], in[2], tmp1);
+ out[3] = BLEND(a, out[3], in[3], tmp1);
+ out += 4;
+ in += 4;
+ mask += 4;
+ }
+ }
+ }
+}
+
+static inline void
+paste_mask_RGBa(
+ Imaging imOut,
+ Imaging imIn,
+ Imaging imMask,
+ int dx,
+ int dy,
+ int sx,
+ int sy,
+ int xsize,
+ int ysize,
+ int pixelsize) {
+ /* paste with mode "RGBa" matte */
+
+ int x, y;
+ unsigned int tmp1;
+
+ if (imOut->image8) {
+ for (y = 0; y < ysize; y++) {
+ UINT8 *out = imOut->image8[y + dy] + dx;
+ UINT8 *in = imIn->image8[y + sy] + sx;
+ UINT8 *mask = (UINT8 *)imMask->image[y + sy] + sx * 4 + 3;
+ for (x = 0; x < xsize; x++) {
+ *out = PREBLEND(*mask, *out, *in, tmp1);
+ out++, in++, mask += 4;
+ }
+ }
+
+ } else {
+ for (y = 0; y < ysize; y++) {
+ UINT8 *out = (UINT8 *)(imOut->image32[y + dy] + dx);
+ UINT8 *in = (UINT8 *)(imIn->image32[y + sy] + sx);
+ UINT8 *mask = (UINT8 *)(imMask->image32[y + sy] + sx);
+ for (x = 0; x < xsize; x++) {
+ UINT8 a = mask[3];
+ out[0] = PREBLEND(a, out[0], in[0], tmp1);
+ out[1] = PREBLEND(a, out[1], in[1], tmp1);
+ out[2] = PREBLEND(a, out[2], in[2], tmp1);
+ out[3] = PREBLEND(a, out[3], in[3], tmp1);
+ out += 4;
+ in += 4;
+ mask += 4;
+ }
+ }
+ }
+}
+
+int
+ImagingPaste(
+ Imaging imOut, Imaging imIn, Imaging imMask, int dx0, int dy0, int dx1, int dy1) {
+ int xsize, ysize;
+ int pixelsize;
+ int sx0, sy0;
+ ImagingSectionCookie cookie;
+
+ if (!imOut || !imIn) {
+ (void)ImagingError_ModeError();
+ return -1;
+ }
+
+ pixelsize = imOut->pixelsize;
+
+ xsize = dx1 - dx0;
+ ysize = dy1 - dy0;
+
+ if (xsize != imIn->xsize || ysize != imIn->ysize || pixelsize != imIn->pixelsize) {
+ (void)ImagingError_Mismatch();
+ return -1;
+ }
+
+ if (imMask && (xsize != imMask->xsize || ysize != imMask->ysize)) {
+ (void)ImagingError_Mismatch();
+ return -1;
+ }
+
+ /* Determine which region to copy */
+ sx0 = sy0 = 0;
+ if (dx0 < 0) {
+ xsize += dx0, sx0 = -dx0, dx0 = 0;
+ }
+ if (dx0 + xsize > imOut->xsize) {
+ xsize = imOut->xsize - dx0;
+ }
+ if (dy0 < 0) {
+ ysize += dy0, sy0 = -dy0, dy0 = 0;
+ }
+ if (dy0 + ysize > imOut->ysize) {
+ ysize = imOut->ysize - dy0;
+ }
+
+ if (xsize <= 0 || ysize <= 0) {
+ return 0;
+ }
+
+ if (!imMask) {
+ ImagingSectionEnter(&cookie);
+ paste(imOut, imIn, dx0, dy0, sx0, sy0, xsize, ysize, pixelsize);
+ ImagingSectionLeave(&cookie);
+
+ } else if (strcmp(imMask->mode, "1") == 0) {
+ ImagingSectionEnter(&cookie);
+ paste_mask_1(imOut, imIn, imMask, dx0, dy0, sx0, sy0, xsize, ysize, pixelsize);
+ ImagingSectionLeave(&cookie);
+
+ } else if (strcmp(imMask->mode, "L") == 0) {
+ ImagingSectionEnter(&cookie);
+ paste_mask_L(imOut, imIn, imMask, dx0, dy0, sx0, sy0, xsize, ysize, pixelsize);
+ ImagingSectionLeave(&cookie);
+
+ } else if (strcmp(imMask->mode, "LA") == 0 || strcmp(imMask->mode, "RGBA") == 0) {
+ ImagingSectionEnter(&cookie);
+ paste_mask_RGBA(
+ imOut, imIn, imMask, dx0, dy0, sx0, sy0, xsize, ysize, pixelsize);
+ ImagingSectionLeave(&cookie);
+
+ } else if (strcmp(imMask->mode, "RGBa") == 0) {
+ ImagingSectionEnter(&cookie);
+ paste_mask_RGBa(
+ imOut, imIn, imMask, dx0, dy0, sx0, sy0, xsize, ysize, pixelsize);
+ ImagingSectionLeave(&cookie);
+
+ } else {
+ (void)ImagingError_ValueError("bad transparency mask");
+ return -1;
+ }
+
+ return 0;
+}
+
+static inline void
+fill(
+ Imaging imOut,
+ const void *ink_,
+ int dx,
+ int dy,
+ int xsize,
+ int ysize,
+ int pixelsize) {
+ /* fill opaque region */
+
+ int x, y;
+ UINT8 ink8 = 0;
+ INT32 ink32 = 0L;
+
+ memcpy(&ink32, ink_, pixelsize);
+ memcpy(&ink8, ink_, sizeof(ink8));
+
+ if (imOut->image8 || ink32 == 0L) {
+ dx *= pixelsize;
+ xsize *= pixelsize;
+ for (y = 0; y < ysize; y++) {
+ memset(imOut->image[y + dy] + dx, ink8, xsize);
+ }
+
+ } else {
+ for (y = 0; y < ysize; y++) {
+ INT32 *out = imOut->image32[y + dy] + dx;
+ for (x = 0; x < xsize; x++) {
+ out[x] = ink32;
+ }
+ }
+ }
+}
+
+static inline void
+fill_mask_1(
+ Imaging imOut,
+ const void *ink_,
+ Imaging imMask,
+ int dx,
+ int dy,
+ int sx,
+ int sy,
+ int xsize,
+ int ysize,
+ int pixelsize) {
+ /* fill with mode "1" mask */
+
+ int x, y;
+ UINT8 ink8 = 0;
+ INT32 ink32 = 0L;
+
+ memcpy(&ink32, ink_, pixelsize);
+ memcpy(&ink8, ink_, sizeof(ink8));
+
+ if (imOut->image8) {
+ for (y = 0; y < ysize; y++) {
+ UINT8 *out = imOut->image8[y + dy] + dx;
+ UINT8 *mask = imMask->image8[y + sy] + sx;
+ for (x = 0; x < xsize; x++) {
+ if (*mask++) {
+ *out = ink8;
+ }
+ out++;
+ }
+ }
+
+ } else {
+ for (y = 0; y < ysize; y++) {
+ INT32 *out = imOut->image32[y + dy] + dx;
+ UINT8 *mask = imMask->image8[y + sy] + sx;
+ for (x = 0; x < xsize; x++) {
+ if (*mask++) {
+ *out = ink32;
+ }
+ out++;
+ }
+ }
+ }
+}
+
+static inline void
+fill_mask_L(
+ Imaging imOut,
+ const UINT8 *ink,
+ Imaging imMask,
+ int dx,
+ int dy,
+ int sx,
+ int sy,
+ int xsize,
+ int ysize,
+ int pixelsize) {
+ /* fill with mode "L" matte */
+
+ int x, y, i;
+ unsigned int tmp1;
+
+ if (imOut->image8) {
+ for (y = 0; y < ysize; y++) {
+ UINT8 *out = imOut->image8[y + dy] + dx;
+ if (strncmp(imOut->mode, "I;16", 4) == 0) {
+ out += dx;
+ }
+ UINT8 *mask = imMask->image8[y + sy] + sx;
+ for (x = 0; x < xsize; x++) {
+ *out = BLEND(*mask, *out, ink[0], tmp1);
+ if (strncmp(imOut->mode, "I;16", 4) == 0) {
+ out++;
+ *out = BLEND(*mask, *out, ink[1], tmp1);
+ }
+ out++, mask++;
+ }
+ }
+
+ } else {
+ int alpha_channel = strcmp(imOut->mode, "RGBa") == 0 ||
+ strcmp(imOut->mode, "RGBA") == 0 ||
+ strcmp(imOut->mode, "La") == 0 ||
+ strcmp(imOut->mode, "LA") == 0 ||
+ strcmp(imOut->mode, "PA") == 0;
+ for (y = 0; y < ysize; y++) {
+ UINT8 *out = (UINT8 *)imOut->image[y + dy] + dx * pixelsize;
+ UINT8 *mask = (UINT8 *)imMask->image[y + sy] + sx;
+ for (x = 0; x < xsize; x++) {
+ for (i = 0; i < pixelsize; i++) {
+ UINT8 channel_mask = *mask;
+ if (alpha_channel && i != 3 && channel_mask != 0) {
+ channel_mask =
+ 255 - (255 - channel_mask) * (1 - (255 - out[3]) / 255);
+ }
+ out[i] = BLEND(channel_mask, out[i], ink[i], tmp1);
+ }
+ out += pixelsize;
+ mask++;
+ }
+ }
+ }
+}
+
+static inline void
+fill_mask_RGBA(
+ Imaging imOut,
+ const UINT8 *ink,
+ Imaging imMask,
+ int dx,
+ int dy,
+ int sx,
+ int sy,
+ int xsize,
+ int ysize,
+ int pixelsize) {
+ /* fill with mode "RGBA" matte */
+
+ int x, y, i;
+ unsigned int tmp1;
+
+ if (imOut->image8) {
+ sx = sx * 4 + 3;
+ for (y = 0; y < ysize; y++) {
+ UINT8 *out = imOut->image8[y + dy] + dx;
+ UINT8 *mask = (UINT8 *)imMask->image[y + sy] + sx;
+ for (x = 0; x < xsize; x++) {
+ *out = BLEND(*mask, *out, ink[0], tmp1);
+ out++, mask += 4;
+ }
+ }
+
+ } else {
+ dx *= pixelsize;
+ sx = sx * 4 + 3;
+ for (y = 0; y < ysize; y++) {
+ UINT8 *out = (UINT8 *)imOut->image[y + dy] + dx;
+ UINT8 *mask = (UINT8 *)imMask->image[y + sy] + sx;
+ for (x = 0; x < xsize; x++) {
+ for (i = 0; i < pixelsize; i++) {
+ *out = BLEND(*mask, *out, ink[i], tmp1);
+ out++;
+ }
+ mask += 4;
+ }
+ }
+ }
+}
+
+static inline void
+fill_mask_RGBa(
+ Imaging imOut,
+ const UINT8 *ink,
+ Imaging imMask,
+ int dx,
+ int dy,
+ int sx,
+ int sy,
+ int xsize,
+ int ysize,
+ int pixelsize) {
+ /* fill with mode "RGBa" matte */
+
+ int x, y, i;
+ unsigned int tmp1;
+
+ if (imOut->image8) {
+ sx = sx * 4 + 3;
+ for (y = 0; y < ysize; y++) {
+ UINT8 *out = imOut->image8[y + dy] + dx;
+ UINT8 *mask = (UINT8 *)imMask->image[y + sy] + sx;
+ for (x = 0; x < xsize; x++) {
+ *out = PREBLEND(*mask, *out, ink[0], tmp1);
+ out++, mask += 4;
+ }
+ }
+
+ } else {
+ dx *= pixelsize;
+ sx = sx * 4 + 3;
+ for (y = 0; y < ysize; y++) {
+ UINT8 *out = (UINT8 *)imOut->image[y + dy] + dx;
+ UINT8 *mask = (UINT8 *)imMask->image[y + sy] + sx;
+ for (x = 0; x < xsize; x++) {
+ for (i = 0; i < pixelsize; i++) {
+ *out = PREBLEND(*mask, *out, ink[i], tmp1);
+ out++;
+ }
+ mask += 4;
+ }
+ }
+ }
+}
+
+int
+ImagingFill2(
+ Imaging imOut,
+ const void *ink,
+ Imaging imMask,
+ int dx0,
+ int dy0,
+ int dx1,
+ int dy1) {
+ ImagingSectionCookie cookie;
+ int xsize, ysize;
+ int pixelsize;
+ int sx0, sy0;
+
+ if (!imOut || !ink) {
+ (void)ImagingError_ModeError();
+ return -1;
+ }
+
+ pixelsize = imOut->pixelsize;
+
+ xsize = dx1 - dx0;
+ ysize = dy1 - dy0;
+
+ if (imMask && (xsize != imMask->xsize || ysize != imMask->ysize)) {
+ (void)ImagingError_Mismatch();
+ return -1;
+ }
+
+ /* Determine which region to fill */
+ sx0 = sy0 = 0;
+ if (dx0 < 0) {
+ xsize += dx0, sx0 = -dx0, dx0 = 0;
+ }
+ if (dx0 + xsize > imOut->xsize) {
+ xsize = imOut->xsize - dx0;
+ }
+ if (dy0 < 0) {
+ ysize += dy0, sy0 = -dy0, dy0 = 0;
+ }
+ if (dy0 + ysize > imOut->ysize) {
+ ysize = imOut->ysize - dy0;
+ }
+
+ if (xsize <= 0 || ysize <= 0) {
+ return 0;
+ }
+
+ if (!imMask) {
+ ImagingSectionEnter(&cookie);
+ fill(imOut, ink, dx0, dy0, xsize, ysize, pixelsize);
+ ImagingSectionLeave(&cookie);
+
+ } else if (strcmp(imMask->mode, "1") == 0) {
+ ImagingSectionEnter(&cookie);
+ fill_mask_1(imOut, ink, imMask, dx0, dy0, sx0, sy0, xsize, ysize, pixelsize);
+ ImagingSectionLeave(&cookie);
+
+ } else if (strcmp(imMask->mode, "L") == 0) {
+ ImagingSectionEnter(&cookie);
+ fill_mask_L(imOut, ink, imMask, dx0, dy0, sx0, sy0, xsize, ysize, pixelsize);
+ ImagingSectionLeave(&cookie);
+
+ } else if (strcmp(imMask->mode, "RGBA") == 0) {
+ ImagingSectionEnter(&cookie);
+ fill_mask_RGBA(imOut, ink, imMask, dx0, dy0, sx0, sy0, xsize, ysize, pixelsize);
+ ImagingSectionLeave(&cookie);
+
+ } else if (strcmp(imMask->mode, "RGBa") == 0) {
+ ImagingSectionEnter(&cookie);
+ fill_mask_RGBa(imOut, ink, imMask, dx0, dy0, sx0, sy0, xsize, ysize, pixelsize);
+ ImagingSectionLeave(&cookie);
+
+ } else {
+ (void)ImagingError_ValueError("bad transparency mask");
+ return -1;
+ }
+
+ return 0;
+}
diff --git a/contrib/python/Pillow/py3/libImaging/PcdDecode.c b/contrib/python/Pillow/py3/libImaging/PcdDecode.c
new file mode 100644
index 00000000000..f13803cb688
--- /dev/null
+++ b/contrib/python/Pillow/py3/libImaging/PcdDecode.c
@@ -0,0 +1,74 @@
+/*
+ * The Python Imaging Library.
+ * $Id$
+ *
+ * decoder for uncompressed PCD image data.
+ *
+ * history:
+ * 96-05-10 fl Created
+ * 96-05-18 fl New tables
+ * 97-01-25 fl Use PhotoYCC unpacker
+ *
+ * notes:
+ * This driver supports uncompressed PCD modes only
+ * (resolutions up to 768x512).
+ *
+ * Copyright (c) Fredrik Lundh 1996-97.
+ * Copyright (c) Secret Labs AB 1997.
+ *
+ * See the README file for information on usage and redistribution.
+ */
+
+#include "Imaging.h"
+
+int
+ImagingPcdDecode(Imaging im, ImagingCodecState state, UINT8 *buf, Py_ssize_t bytes) {
+ int x;
+ int chunk;
+ UINT8 *out;
+ UINT8 *ptr;
+
+ ptr = buf;
+
+ chunk = 3 * state->xsize;
+
+ for (;;) {
+ /* We need data for two full lines before we can do anything */
+ if (bytes < chunk) {
+ return ptr - buf;
+ }
+
+ /* Unpack first line */
+ out = state->buffer;
+ for (x = 0; x < state->xsize; x++) {
+ out[0] = ptr[x];
+ out[1] = ptr[(x + 4 * state->xsize) / 2];
+ out[2] = ptr[(x + 5 * state->xsize) / 2];
+ out += 3;
+ }
+
+ state->shuffle((UINT8 *)im->image[state->y], state->buffer, state->xsize);
+
+ if (++state->y >= state->ysize) {
+ return -1; /* This can hardly happen */
+ }
+
+ /* Unpack second line */
+ out = state->buffer;
+ for (x = 0; x < state->xsize; x++) {
+ out[0] = ptr[x + state->xsize];
+ out[1] = ptr[(x + 4 * state->xsize) / 2];
+ out[2] = ptr[(x + 5 * state->xsize) / 2];
+ out += 3;
+ }
+
+ state->shuffle((UINT8 *)im->image[state->y], state->buffer, state->xsize);
+
+ if (++state->y >= state->ysize) {
+ return -1;
+ }
+
+ ptr += chunk;
+ bytes -= chunk;
+ }
+}
diff --git a/contrib/python/Pillow/py3/libImaging/PcxDecode.c b/contrib/python/Pillow/py3/libImaging/PcxDecode.c
new file mode 100644
index 00000000000..c95ffc8692c
--- /dev/null
+++ b/contrib/python/Pillow/py3/libImaging/PcxDecode.c
@@ -0,0 +1,89 @@
+/*
+ * The Python Imaging Library.
+ * $Id$
+ *
+ * decoder for PCX image data.
+ *
+ * history:
+ * 95-09-14 fl Created
+ *
+ * Copyright (c) Fredrik Lundh 1995.
+ * Copyright (c) Secret Labs AB 1997.
+ *
+ * See the README file for information on usage and redistribution.
+ */
+
+#include "Imaging.h"
+
+int
+ImagingPcxDecode(Imaging im, ImagingCodecState state, UINT8 *buf, Py_ssize_t bytes) {
+ UINT8 n;
+ UINT8 *ptr;
+
+ if ((state->xsize * state->bits + 7) / 8 > state->bytes) {
+ state->errcode = IMAGING_CODEC_OVERRUN;
+ return -1;
+ }
+
+ ptr = buf;
+
+ for (;;) {
+ if (bytes < 1) {
+ return ptr - buf;
+ }
+
+ if ((*ptr & 0xC0) == 0xC0) {
+ /* Run */
+ if (bytes < 2) {
+ return ptr - buf;
+ }
+
+ n = ptr[0] & 0x3F;
+
+ while (n > 0) {
+ if (state->x >= state->bytes) {
+ state->errcode = IMAGING_CODEC_OVERRUN;
+ break;
+ }
+ state->buffer[state->x++] = ptr[1];
+ n--;
+ }
+
+ ptr += 2;
+ bytes -= 2;
+
+ } else {
+ /* Literal */
+ state->buffer[state->x++] = ptr[0];
+ ptr++;
+ bytes--;
+ }
+
+ if (state->x >= state->bytes) {
+ if (state->bytes % state->xsize && state->bytes > state->xsize) {
+ int bands = state->bytes / state->xsize;
+ int stride = state->bytes / bands;
+ int i;
+ for (i = 1; i < bands; i++) { // note -- skipping first band
+ memmove(
+ &state->buffer[i * state->xsize],
+ &state->buffer[i * stride],
+ state->xsize);
+ }
+ }
+ /* Got a full line, unpack it */
+ state->shuffle(
+ (UINT8 *)im->image[state->y + state->yoff] +
+ state->xoff * im->pixelsize,
+ state->buffer,
+ state->xsize);
+
+ state->x = 0;
+
+ if (++state->y >= state->ysize) {
+ /* End of file (errcode = 0) */
+ return -1;
+ }
+ }
+ }
+}
diff --git a/contrib/python/Pillow/py3/libImaging/PcxEncode.c b/contrib/python/Pillow/py3/libImaging/PcxEncode.c
new file mode 100644
index 00000000000..549614bfd39
--- /dev/null
+++ b/contrib/python/Pillow/py3/libImaging/PcxEncode.c
@@ -0,0 +1,187 @@
+/*
+ * The Python Imaging Library.
+ * $Id$
+ *
+ * encoder for PCX data
+ *
+ * history:
+ * 99-02-07 fl created
+ *
+ * Copyright (c) Fredrik Lundh 1999.
+ * Copyright (c) Secret Labs AB 1999.
+ *
+ * See the README file for information on usage and redistribution.
+ */
+
+#include "Imaging.h"
+
+enum { INIT, FETCH, ENCODE };
+
+/* we're reusing "ystep" to store the last value */
+#define LAST ystep
+
+int
+ImagingPcxEncode(Imaging im, ImagingCodecState state, UINT8 *buf, int bytes) {
+ UINT8 *ptr;
+ int this;
+ int bytes_per_line = 0;
+ int padding = 0;
+ int stride = 0;
+ int bpp = 0;
+ int planes = 1;
+ int i;
+
+ ptr = buf;
+
+ if (!state->state) {
+ /* sanity check */
+ if (state->xsize <= 0 || state->ysize <= 0) {
+ state->errcode = IMAGING_CODEC_END;
+ return 0;
+ }
+ state->state = FETCH;
+ }
+
+ bpp = state->bits;
+ if (state->bits == 24) {
+ planes = 3;
+ bpp = 8;
+ }
+
+ bytes_per_line = (state->xsize * bpp + 7) / 8;
+ /* The stride here needs to be kept in sync with the version in
+ PcxImagePlugin.py. If it's not, the header and the body of the
+ image will be out of sync and bad things will happen on decode.
+ */
+ stride = bytes_per_line + (bytes_per_line % 2);
+
+ padding = stride - bytes_per_line;
+
+ for (;;) {
+ switch (state->state) {
+ case FETCH:
+
+ /* get a line of data */
+ if (state->y >= state->ysize) {
+ state->errcode = IMAGING_CODEC_END;
+ return ptr - buf;
+ }
+
+ state->shuffle(
+ state->buffer,
+ (UINT8 *)im->image[state->y + state->yoff] +
+ state->xoff * im->pixelsize,
+ state->xsize);
+
+ state->y += 1;
+
+ state->count = 1;
+ state->LAST = state->buffer[0];
+
+ state->x = 1;
+
+ state->state = ENCODE;
+ /* fall through */
+
+ case ENCODE:
+ /* compress this line */
+
+ /* when we arrive here, "count" contains the number of
+ bytes having the value of "LAST" that we've already
+ seen */
+ do {
+ /* If we're encoding an odd width file, and we've
+ got more than one plane, we need to pad each
+ color row with padding bytes at the end. Since
+ The pixels are stored RRRRRGGGGGBBBBB, so we need
+ to have the padding be RRRRRPGGGGGPBBBBBP. Hence
+ the double loop
+ */
+ while (state->x % bytes_per_line) {
+ if (state->count == 63) {
+ /* this run is full; flush it */
+ if (bytes < 2) {
+ return ptr - buf;
+ }
+ ptr[0] = 0xff;
+ ptr[1] = state->LAST;
+ ptr += 2;
+ bytes -= 2;
+
+ state->count = 0;
+ }
+
+ this = state->buffer[state->x];
+
+ if (this == state->LAST) {
+ /* extend the current run */
+ state->x += 1;
+ state->count += 1;
+
+ } else {
+ /* start a new run */
+ if (state->count == 1 && (state->LAST < 0xc0)) {
+ if (bytes < 1) {
+ return ptr - buf;
+ }
+ ptr[0] = state->LAST;
+ ptr += 1;
+ bytes -= 1;
+ } else {
+ if (state->count > 0) {
+ if (bytes < 2) {
+ return ptr - buf;
+ }
+ ptr[0] = 0xc0 | state->count;
+ ptr[1] = state->LAST;
+ ptr += 2;
+ bytes -= 2;
+ }
+ }
+
+ state->LAST = this;
+ state->count = 1;
+
+ state->x += 1;
+ }
+ }
+
+ /* end of line; flush the current run */
+ if (state->count == 1 && (state->LAST < 0xc0)) {
+ if (bytes < 1 + padding) {
+ return ptr - buf;
+ }
+ ptr[0] = state->LAST;
+ ptr += 1;
+ bytes -= 1;
+ } else {
+ if (state->count > 0) {
+ if (bytes < 2 + padding) {
+ return ptr - buf;
+ }
+ ptr[0] = 0xc0 | state->count;
+ ptr[1] = state->LAST;
+ ptr += 2;
+ bytes -= 2;
+ }
+ }
+ /* add the padding */
+ for (i = 0; i < padding; i++) {
+ ptr[0] = 0;
+ ptr += 1;
+ bytes -= 1;
+ }
+ /* reset for the next color plane. */
+ if (state->x < planes * bytes_per_line) {
+ state->count = 1;
+ state->LAST = state->buffer[state->x];
+ state->x += 1;
+ }
+ } while (state->x < planes * bytes_per_line);
+
+ /* read next line */
+ state->state = FETCH;
+ break;
+ }
+ }
+}
diff --git a/contrib/python/Pillow/py3/libImaging/Point.c b/contrib/python/Pillow/py3/libImaging/Point.c
new file mode 100644
index 00000000000..8883578cbff
--- /dev/null
+++ b/contrib/python/Pillow/py3/libImaging/Point.c
@@ -0,0 +1,270 @@
+/*
+ * The Python Imaging Library
+ * $Id$
+ *
+ * point (pixel) translation
+ *
+ * history:
+ * 1995-11-27 fl Created
+ * 1996-03-31 fl Fixed colour support
+ * 1996-08-13 fl Support 8-bit to "1" thresholding
+ * 1997-05-31 fl Added floating point transform
+ * 1998-07-02 fl Added integer point transform
+ * 1998-07-17 fl Support L to anything lookup
+ * 2004-12-18 fl Refactored; added I to L lookup
+ *
+ * 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.
+ */
+
+#include "Imaging.h"
+
+typedef struct {
+ const void *table;
+} im_point_context;
+
+static void
+im_point_8_8(Imaging imOut, Imaging imIn, im_point_context *context) {
+ int x, y;
+ /* 8-bit source, 8-bit destination */
+ UINT8 *table = (UINT8 *)context->table;
+ for (y = 0; y < imIn->ysize; y++) {
+ UINT8 *in = imIn->image8[y];
+ UINT8 *out = imOut->image8[y];
+ for (x = 0; x < imIn->xsize; x++) {
+ out[x] = table[in[x]];
+ }
+ }
+}
+
+static void
+im_point_2x8_2x8(Imaging imOut, Imaging imIn, im_point_context *context) {
+ int x, y;
+ /* 2x8-bit source, 2x8-bit destination */
+ UINT8 *table = (UINT8 *)context->table;
+ for (y = 0; y < imIn->ysize; y++) {
+ UINT8 *in = (UINT8 *)imIn->image[y];
+ UINT8 *out = (UINT8 *)imOut->image[y];
+ for (x = 0; x < imIn->xsize; x++) {
+ out[0] = table[in[0]];
+ out[3] = table[in[3] + 256];
+ in += 4;
+ out += 4;
+ }
+ }
+}
+
+static void
+im_point_3x8_3x8(Imaging imOut, Imaging imIn, im_point_context *context) {
+ int x, y;
+ /* 3x8-bit source, 3x8-bit destination */
+ UINT8 *table = (UINT8 *)context->table;
+ for (y = 0; y < imIn->ysize; y++) {
+ UINT8 *in = (UINT8 *)imIn->image[y];
+ UINT8 *out = (UINT8 *)imOut->image[y];
+ for (x = 0; x < imIn->xsize; x++) {
+ out[0] = table[in[0]];
+ out[1] = table[in[1] + 256];
+ out[2] = table[in[2] + 512];
+ in += 4;
+ out += 4;
+ }
+ }
+}
+
+static void
+im_point_4x8_4x8(Imaging imOut, Imaging imIn, im_point_context *context) {
+ int x, y;
+ /* 4x8-bit source, 4x8-bit destination */
+ UINT8 *table = (UINT8 *)context->table;
+ for (y = 0; y < imIn->ysize; y++) {
+ UINT8 *in = (UINT8 *)imIn->image[y];
+ UINT8 *out = (UINT8 *)imOut->image[y];
+ for (x = 0; x < imIn->xsize; x++) {
+ out[0] = table[in[0]];
+ out[1] = table[in[1] + 256];
+ out[2] = table[in[2] + 512];
+ out[3] = table[in[3] + 768];
+ in += 4;
+ out += 4;
+ }
+ }
+}
+
+static void
+im_point_8_32(Imaging imOut, Imaging imIn, im_point_context *context) {
+ int x, y;
+ /* 8-bit source, 32-bit destination */
+ char *table = (char *)context->table;
+ for (y = 0; y < imIn->ysize; y++) {
+ UINT8 *in = imIn->image8[y];
+ INT32 *out = imOut->image32[y];
+ for (x = 0; x < imIn->xsize; x++) {
+ memcpy(out + x, table + in[x] * sizeof(INT32), sizeof(INT32));
+ }
+ }
+}
+
+static void
+im_point_32_8(Imaging imOut, Imaging imIn, im_point_context *context) {
+ int x, y;
+ /* 32-bit source, 8-bit destination */
+ UINT8 *table = (UINT8 *)context->table;
+ for (y = 0; y < imIn->ysize; y++) {
+ INT32 *in = imIn->image32[y];
+ UINT8 *out = imOut->image8[y];
+ for (x = 0; x < imIn->xsize; x++) {
+ int v = in[x];
+ if (v < 0) {
+ v = 0;
+ } else if (v > 65535) {
+ v = 65535;
+ }
+ out[x] = table[v];
+ }
+ }
+}
+
+Imaging
+ImagingPoint(Imaging imIn, const char *mode, const void *table) {
+ /* lookup table transform */
+
+ ImagingSectionCookie cookie;
+ Imaging imOut;
+ im_point_context context;
+ void (*point)(Imaging imIn, Imaging imOut, im_point_context * context);
+
+ if (!imIn) {
+ return (Imaging)ImagingError_ModeError();
+ }
+
+ if (!mode) {
+ mode = imIn->mode;
+ }
+
+ if (imIn->type != IMAGING_TYPE_UINT8) {
+ if (imIn->type != IMAGING_TYPE_INT32 || strcmp(mode, "L") != 0) {
+ goto mode_mismatch;
+ }
+ } else if (!imIn->image8 && strcmp(imIn->mode, mode) != 0) {
+ goto mode_mismatch;
+ }
+
+ imOut = ImagingNew(mode, imIn->xsize, imIn->ysize);
+ if (!imOut) {
+ return NULL;
+ }
+
+ /* find appropriate handler */
+ if (imIn->type == IMAGING_TYPE_UINT8) {
+ if (imIn->bands == imOut->bands && imIn->type == imOut->type) {
+ switch (imIn->bands) {
+ case 1:
+ point = im_point_8_8;
+ break;
+ case 2:
+ point = im_point_2x8_2x8;
+ break;
+ case 3:
+ point = im_point_3x8_3x8;
+ break;
+ case 4:
+ point = im_point_4x8_4x8;
+ break;
+ default:
+ /* this cannot really happen */
+ point = im_point_8_8;
+ break;
+ }
+ } else {
+ point = im_point_8_32;
+ }
+ } else {
+ point = im_point_32_8;
+ }
+
+ ImagingCopyPalette(imOut, imIn);
+
+ ImagingSectionEnter(&cookie);
+
+ context.table = table;
+ point(imOut, imIn, &context);
+
+ ImagingSectionLeave(&cookie);
+
+ return imOut;
+
+mode_mismatch:
+ return (Imaging)ImagingError_ValueError(
+ "point operation not supported for this mode");
+}
+
+Imaging
+ImagingPointTransform(Imaging imIn, double scale, double offset) {
+ /* scale/offset transform */
+
+ ImagingSectionCookie cookie;
+ Imaging imOut;
+ int x, y;
+
+ if (!imIn || (strcmp(imIn->mode, "I") != 0 && strcmp(imIn->mode, "I;16") != 0 &&
+ strcmp(imIn->mode, "F") != 0)) {
+ return (Imaging)ImagingError_ModeError();
+ }
+
+ imOut = ImagingNew(imIn->mode, imIn->xsize, imIn->ysize);
+ if (!imOut) {
+ return NULL;
+ }
+
+ switch (imIn->type) {
+ case IMAGING_TYPE_INT32:
+ ImagingSectionEnter(&cookie);
+ for (y = 0; y < imIn->ysize; y++) {
+ INT32 *in = imIn->image32[y];
+ INT32 *out = imOut->image32[y];
+ /* FIXME: add clipping? */
+ for (x = 0; x < imIn->xsize; x++) {
+ out[x] = in[x] * scale + offset;
+ }
+ }
+ ImagingSectionLeave(&cookie);
+ break;
+ case IMAGING_TYPE_FLOAT32:
+ ImagingSectionEnter(&cookie);
+ for (y = 0; y < imIn->ysize; y++) {
+ FLOAT32 *in = (FLOAT32 *)imIn->image32[y];
+ FLOAT32 *out = (FLOAT32 *)imOut->image32[y];
+ for (x = 0; x < imIn->xsize; x++) {
+ out[x] = in[x] * scale + offset;
+ }
+ }
+ ImagingSectionLeave(&cookie);
+ break;
+ case IMAGING_TYPE_SPECIAL:
+ if (strcmp(imIn->mode, "I;16") == 0) {
+ ImagingSectionEnter(&cookie);
+ for (y = 0; y < imIn->ysize; y++) {
+ char *in = (char *)imIn->image[y];
+ char *out = (char *)imOut->image[y];
+ /* FIXME: add clipping? */
+ for (x = 0; x < imIn->xsize; x++) {
+ UINT16 v;
+ memcpy(&v, in + x * sizeof(v), sizeof(v));
+ v = v * scale + offset;
+ memcpy(out + x * sizeof(UINT16), &v, sizeof(v));
+ }
+ }
+ ImagingSectionLeave(&cookie);
+ break;
+ }
+ /* FALL THROUGH */
+ default:
+ ImagingDelete(imOut);
+ return (Imaging)ImagingError_ValueError("internal error");
+ }
+
+ return imOut;
+}
diff --git a/contrib/python/Pillow/py3/libImaging/Quant.c b/contrib/python/Pillow/py3/libImaging/Quant.c
new file mode 100644
index 00000000000..c84acb99889
--- /dev/null
+++ b/contrib/python/Pillow/py3/libImaging/Quant.c
@@ -0,0 +1,1859 @@
+/*
+ * The Python Imaging Library
+ * $Id$
+ *
+ * image quantizer
+ *
+ * history:
+ * 1998-09-10 tjs Contributed
+ * 1998-12-29 fl Added to PIL 1.0b1
+ * 2004-02-21 fl Fixed bogus free() on quantization error
+ * 2005-02-07 fl Limit number of colors to 256
+ *
+ * Written by Toby J Sargeant <[email protected]>.
+ *
+ * Copyright (c) 1998 by Toby J Sargeant
+ * Copyright (c) 1998-2004 by Secret Labs AB. All rights reserved.
+ *
+ * See the README file for information on usage and redistribution.
+ */
+
+#include "Imaging.h"
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <memory.h>
+#include <time.h>
+
+#include "QuantTypes.h"
+#include "QuantOctree.h"
+#include "QuantPngQuant.h"
+#include "QuantHash.h"
+#include "QuantHeap.h"
+
+/* MSVC9.0 */
+#ifndef UINT32_MAX
+#define UINT32_MAX 0xffffffff
+#endif
+
+#define NO_OUTPUT
+
+typedef struct {
+ uint32_t scale;
+} PixelHashData;
+
+typedef struct _PixelList {
+ struct _PixelList *next[3], *prev[3];
+ Pixel p;
+ unsigned int flag : 1;
+ int count;
+} PixelList;
+
+typedef struct _BoxNode {
+ struct _BoxNode *l, *r;
+ PixelList *head[3], *tail[3];
+ int axis;
+ int volume;
+ uint32_t pixelCount;
+} BoxNode;
+
+#define _SQR(x) ((x) * (x))
+#define _DISTSQR(p1, p2) \
+ _SQR((int)((p1)->c.r) - (int)((p2)->c.r)) + \
+ _SQR((int)((p1)->c.g) - (int)((p2)->c.g)) + \
+ _SQR((int)((p1)->c.b) - (int)((p2)->c.b))
+
+#define MAX_HASH_ENTRIES 65536
+
+#define PIXEL_HASH(r, g, b) \
+ (((unsigned int)(r)) * 463 ^ ((unsigned int)(g) << 8) * 10069 ^ \
+ ((unsigned int)(b) << 16) * 64997)
+
+#define PIXEL_UNSCALE(p, q, s) \
+ ((q)->c.r = (p)->c.r << (s)), ((q)->c.g = (p)->c.g << (s)), \
+ ((q)->c.b = (p)->c.b << (s))
+
+#define PIXEL_SCALE(p, q, s) \
+ ((q)->c.r = (p)->c.r >> (s)), ((q)->c.g = (p)->c.g >> (s)), \
+ ((q)->c.b = (p)->c.b >> (s))
+
+static uint32_t
+unshifted_pixel_hash(const HashTable *h, const Pixel pixel) {
+ return PIXEL_HASH(pixel.c.r, pixel.c.g, pixel.c.b);
+}
+
+static int
+unshifted_pixel_cmp(const HashTable *h, const Pixel pixel1, const Pixel pixel2) {
+ if (pixel1.c.r == pixel2.c.r) {
+ if (pixel1.c.g == pixel2.c.g) {
+ if (pixel1.c.b == pixel2.c.b) {
+ return 0;
+ } else {
+ return (int)(pixel1.c.b) - (int)(pixel2.c.b);
+ }
+ } else {
+ return (int)(pixel1.c.g) - (int)(pixel2.c.g);
+ }
+ } else {
+ return (int)(pixel1.c.r) - (int)(pixel2.c.r);
+ }
+}
+
+static uint32_t
+pixel_hash(const HashTable *h, const Pixel pixel) {
+ PixelHashData *d = (PixelHashData *)hashtable_get_user_data(h);
+ return PIXEL_HASH(
+ pixel.c.r >> d->scale, pixel.c.g >> d->scale, pixel.c.b >> d->scale);
+}
+
+static int
+pixel_cmp(const HashTable *h, const Pixel pixel1, const Pixel pixel2) {
+ PixelHashData *d = (PixelHashData *)hashtable_get_user_data(h);
+ uint32_t A, B;
+ A = PIXEL_HASH(
+ pixel1.c.r >> d->scale, pixel1.c.g >> d->scale, pixel1.c.b >> d->scale);
+ B = PIXEL_HASH(
+ pixel2.c.r >> d->scale, pixel2.c.g >> d->scale, pixel2.c.b >> d->scale);
+ return (A == B) ? 0 : ((A < B) ? -1 : 1);
+}
+
+static void
+exists_count_func(const HashTable *h, const Pixel key, uint32_t *val) {
+ *val += 1;
+}
+
+static void
+new_count_func(const HashTable *h, const Pixel key, uint32_t *val) {
+ *val = 1;
+}
+
+static void
+rehash_collide(
+ const HashTable *h, Pixel *keyp, uint32_t *valp, Pixel newkey, uint32_t newval) {
+ *valp += newval;
+}
+
+/* %% */
+
+static HashTable *
+create_pixel_hash(Pixel *pixelData, uint32_t nPixels) {
+ PixelHashData *d;
+ HashTable *hash;
+ uint32_t i;
+#ifndef NO_OUTPUT
+ uint32_t timer, timer2, timer3;
+#endif
+
+ /* malloc check ok, small constant allocation */
+ d = malloc(sizeof(PixelHashData));
+ if (!d) {
+ return NULL;
+ }
+ hash = hashtable_new(pixel_hash, pixel_cmp);
+ hashtable_set_user_data(hash, d);
+ d->scale = 0;
+#ifndef NO_OUTPUT
+ timer = timer3 = clock();
+#endif
+ for (i = 0; i < nPixels; i++) {
+ if (!hashtable_insert_or_update_computed(
+ hash, pixelData[i], new_count_func, exists_count_func)) {
+ ;
+ }
+ while (hashtable_get_count(hash) > MAX_HASH_ENTRIES) {
+ d->scale++;
+#ifndef NO_OUTPUT
+ printf("rehashing - new scale: %d\n", (int)d->scale);
+ timer2 = clock();
+#endif
+ hashtable_rehash_compute(hash, rehash_collide);
+#ifndef NO_OUTPUT
+ timer2 = clock() - timer2;
+ printf("rehash took %f sec\n", timer2 / (double)CLOCKS_PER_SEC);
+ timer += timer2;
+#endif
+ }
+ }
+#ifndef NO_OUTPUT
+ printf("inserts took %f sec\n", (clock() - timer) / (double)CLOCKS_PER_SEC);
+#endif
+#ifndef NO_OUTPUT
+ printf("total %f sec\n", (clock() - timer3) / (double)CLOCKS_PER_SEC);
+#endif
+ return hash;
+}
+
+static void
+destroy_pixel_hash(HashTable *hash) {
+ PixelHashData *d = (PixelHashData *)hashtable_get_user_data(hash);
+ if (d) {
+ free(d);
+ }
+ hashtable_free(hash);
+}
+
+/* 1. hash quantized pixels. */
+/* 2. create R,G,B lists of sorted quantized pixels. */
+/* 3. median cut. */
+/* 4. build hash table from median cut boxes. */
+/* 5. for each pixel, compute entry in hash table, and hence median cut box. */
+/* 6. compute median cut box pixel averages. */
+/* 7. map each pixel to nearest average. */
+
+static int
+compute_box_volume(BoxNode *b) {
+ unsigned char rl, rh, gl, gh, bl, bh;
+ if (b->volume >= 0) {
+ return b->volume;
+ }
+ if (!b->head[0]) {
+ b->volume = 0;
+ } else {
+ rh = b->head[0]->p.c.r;
+ rl = b->tail[0]->p.c.r;
+ gh = b->head[1]->p.c.g;
+ gl = b->tail[1]->p.c.g;
+ bh = b->head[2]->p.c.b;
+ bl = b->tail[2]->p.c.b;
+ b->volume = (rh - rl + 1) * (gh - gl + 1) * (bh - bl + 1);
+ }
+ return b->volume;
+}
+
+static void
+hash_to_list(const HashTable *h, const Pixel pixel, const uint32_t count, void *u) {
+ PixelHashData *d = (PixelHashData *)hashtable_get_user_data(h);
+ PixelList **pl = (PixelList **)u;
+ PixelList *p;
+ int i;
+ Pixel q;
+
+ PIXEL_SCALE(&pixel, &q, d->scale);
+
+ /* malloc check ok, small constant allocation */
+ p = malloc(sizeof(PixelList));
+ if (!p) {
+ return;
+ }
+
+ p->flag = 0;
+ p->p = q;
+ p->count = count;
+ for (i = 0; i < 3; i++) {
+ p->next[i] = pl[i];
+ p->prev[i] = NULL;
+ if (pl[i]) {
+ pl[i]->prev[i] = p;
+ }
+ pl[i] = p;
+ }
+}
+
+static PixelList *
+mergesort_pixels(PixelList *head, int i) {
+ PixelList *c, *t, *a, *b, *p;
+ if (!head || !head->next[i]) {
+ if (head) {
+ head->next[i] = NULL;
+ head->prev[i] = NULL;
+ }
+ return head;
+ }
+ for (c = t = head; c && t;
+ c = c->next[i], t = (t->next[i]) ? t->next[i]->next[i] : NULL)
+ ;
+ if (c) {
+ if (c->prev[i]) {
+ c->prev[i]->next[i] = NULL;
+ }
+ c->prev[i] = NULL;
+ }
+ a = mergesort_pixels(head, i);
+ b = mergesort_pixels(c, i);
+ head = NULL;
+ p = NULL;
+ while (a && b) {
+ if (a->p.a.v[i] > b->p.a.v[i]) {
+ c = a;
+ a = a->next[i];
+ } else {
+ c = b;
+ b = b->next[i];
+ }
+ c->prev[i] = p;
+ c->next[i] = NULL;
+ if (p) {
+ p->next[i] = c;
+ }
+ p = c;
+ if (!head) {
+ head = c;
+ }
+ }
+ if (a) {
+ c->next[i] = a;
+ a->prev[i] = c;
+ } else if (b) {
+ c->next[i] = b;
+ b->prev[i] = c;
+ }
+ return head;
+}
+
+#if defined(TEST_MERGESORT) || defined(TEST_SORTED)
+static int
+test_sorted(PixelList *pl[3]) {
+ int i, n, l;
+ PixelList *t;
+
+ for (i = 0; i < 3; i++) {
+ n = 0;
+ l = 256;
+ for (t = pl[i]; t; t = t->next[i]) {
+ if (l < t->p.a.v[i])
+ return 0;
+ l = t->p.a.v[i];
+ }
+ }
+ return 1;
+}
+#endif
+
+static int
+box_heap_cmp(const Heap *h, const void *A, const void *B) {
+ BoxNode *a = (BoxNode *)A;
+ BoxNode *b = (BoxNode *)B;
+ return (int)a->pixelCount - (int)b->pixelCount;
+}
+
+#define LUMINANCE(p) (77 * (p)->c.r + 150 * (p)->c.g + 29 * (p)->c.b)
+
+static int
+splitlists(
+ PixelList *h[3],
+ PixelList *t[3],
+ PixelList *nh[2][3],
+ PixelList *nt[2][3],
+ uint32_t nCount[2],
+ int axis,
+ uint32_t pixelCount) {
+ uint32_t left;
+
+ PixelList *l, *r, *c, *n;
+ int i;
+ int nRight;
+#ifndef NO_OUTPUT
+ int nLeft;
+#endif
+ int splitColourVal;
+
+#ifdef TEST_SPLIT
+ {
+ PixelList *_prevTest, *_nextTest;
+ int _i, _nextCount[3], _prevCount[3];
+ for (_i = 0; _i < 3; _i++) {
+ for (_nextCount[_i] = 0, _nextTest = h[_i];
+ _nextTest && _nextTest->next[_i];
+ _nextTest = _nextTest->next[_i], _nextCount[_i]++)
+ ;
+ for (_prevCount[_i] = 0, _prevTest = t[_i];
+ _prevTest && _prevTest->prev[_i];
+ _prevTest = _prevTest->prev[_i], _prevCount[_i]++)
+ ;
+ if (_nextTest != t[_i]) {
+ printf("next-list of axis %d does not end at tail\n", _i);
+ exit(1);
+ }
+ if (_prevTest != h[_i]) {
+ printf("prev-list of axis %d does not end at head\n", _i);
+ exit(1);
+ }
+ for (; _nextTest && _nextTest->prev[_i]; _nextTest = _nextTest->prev[_i])
+ ;
+ for (; _prevTest && _prevTest->next[_i]; _prevTest = _prevTest->next[_i])
+ ;
+ if (_nextTest != h[_i]) {
+ printf("next-list of axis %d does not loop back to head\n", _i);
+ exit(1);
+ }
+ if (_prevTest != t[_i]) {
+ printf("prev-list of axis %d does not loop back to tail\n", _i);
+ exit(1);
+ }
+ }
+ for (_i = 1; _i < 3; _i++) {
+ if (_prevCount[_i] != _prevCount[_i - 1] ||
+ _nextCount[_i] != _nextCount[_i - 1] ||
+ _prevCount[_i] != _nextCount[_i]) {
+ printf(
+ "{%d %d %d} {%d %d %d}\n",
+ _prevCount[0],
+ _prevCount[1],
+ _prevCount[2],
+ _nextCount[0],
+ _nextCount[1],
+ _nextCount[2]);
+ exit(1);
+ }
+ }
+ }
+#endif
+ nCount[0] = nCount[1] = 0;
+ nRight = 0;
+#ifndef NO_OUTPUT
+ nLeft = 0;
+#endif
+ for (left = 0, c = h[axis]; c;) {
+ left = left + c->count;
+ nCount[0] += c->count;
+ c->flag = 0;
+#ifndef NO_OUTPUT
+ nLeft++;
+#endif
+ c = c->next[axis];
+ if (left * 2 > pixelCount) {
+ break;
+ }
+ }
+ if (c) {
+ splitColourVal = c->prev[axis]->p.a.v[axis];
+ for (; c; c = c->next[axis]) {
+ if (splitColourVal != c->p.a.v[axis]) {
+ break;
+ }
+ c->flag = 0;
+#ifndef NO_OUTPUT
+ nLeft++;
+#endif
+ nCount[0] += c->count;
+ }
+ }
+ for (; c; c = c->next[axis]) {
+ c->flag = 1;
+ nRight++;
+ nCount[1] += c->count;
+ }
+ if (!nRight) {
+ for (c = t[axis], splitColourVal = t[axis]->p.a.v[axis]; c; c = c->prev[axis]) {
+ if (splitColourVal != c->p.a.v[axis]) {
+ break;
+ }
+ c->flag = 1;
+ nRight++;
+#ifndef NO_OUTPUT
+ nLeft--;
+#endif
+ nCount[0] -= c->count;
+ nCount[1] += c->count;
+ }
+ }
+#ifndef NO_OUTPUT
+ if (!nLeft) {
+ for (c = h[axis]; c; c = c->next[axis]) {
+ printf("[%d %d %d]\n", c->p.c.r, c->p.c.g, c->p.c.b);
+ }
+ printf("warning... trivial split\n");
+ }
+#endif
+
+ for (i = 0; i < 3; i++) {
+ l = r = NULL;
+ nh[0][i] = nt[0][i] = NULL;
+ nh[1][i] = nt[1][i] = NULL;
+ for (c = h[i]; c; c = n) {
+ n = c->next[i];
+ if (c->flag) { /* move pixel to right list*/
+ if (r) {
+ r->next[i] = c;
+ } else {
+ nh[1][i] = c;
+ }
+ c->prev[i] = r;
+ r = c;
+ } else { /* move pixel to left list */
+ if (l) {
+ l->next[i] = c;
+ } else {
+ nh[0][i] = c;
+ }
+ c->prev[i] = l;
+ l = c;
+ }
+ }
+ if (l) {
+ l->next[i] = NULL;
+ }
+ if (r) {
+ r->next[i] = NULL;
+ }
+ nt[0][i] = l;
+ nt[1][i] = r;
+ }
+ return 1;
+}
+
+static int
+split(BoxNode *node) {
+ unsigned char rl, rh, gl, gh, bl, bh;
+ int f[3];
+ int best, axis;
+ int i;
+ PixelList *heads[2][3];
+ PixelList *tails[2][3];
+ uint32_t newCounts[2];
+ BoxNode *left, *right;
+
+ rh = node->head[0]->p.c.r;
+ rl = node->tail[0]->p.c.r;
+ gh = node->head[1]->p.c.g;
+ gl = node->tail[1]->p.c.g;
+ bh = node->head[2]->p.c.b;
+ bl = node->tail[2]->p.c.b;
+#ifdef TEST_SPLIT
+ printf("splitting node [%d %d %d] [%d %d %d] ", rl, gl, bl, rh, gh, bh);
+#endif
+ f[0] = (rh - rl) * 77;
+ f[1] = (gh - gl) * 150;
+ f[2] = (bh - bl) * 29;
+
+ best = f[0];
+ axis = 0;
+ for (i = 1; i < 3; i++) {
+ if (best < f[i]) {
+ best = f[i];
+ axis = i;
+ }
+ }
+#ifdef TEST_SPLIT
+ printf("along axis %d\n", axis + 1);
+#endif
+
+#ifdef TEST_SPLIT
+ {
+ PixelList *_prevTest, *_nextTest;
+ int _i, _nextCount[3], _prevCount[3];
+ for (_i = 0; _i < 3; _i++) {
+ if (node->tail[_i]->next[_i]) {
+ printf("tail is not tail\n");
+ printf(
+ "node->tail[%d]->next[%d]=%p\n", _i, _i, node->tail[_i]->next[_i]);
+ }
+ if (node->head[_i]->prev[_i]) {
+ printf("head is not head\n");
+ printf(
+ "node->head[%d]->prev[%d]=%p\n", _i, _i, node->head[_i]->prev[_i]);
+ }
+ }
+
+ for (_i = 0; _i < 3; _i++) {
+ for (_nextCount[_i] = 0, _nextTest = node->head[_i];
+ _nextTest && _nextTest->next[_i];
+ _nextTest = _nextTest->next[_i], _nextCount[_i]++)
+ ;
+ for (_prevCount[_i] = 0, _prevTest = node->tail[_i];
+ _prevTest && _prevTest->prev[_i];
+ _prevTest = _prevTest->prev[_i], _prevCount[_i]++)
+ ;
+ if (_nextTest != node->tail[_i]) {
+ printf("next-list of axis %d does not end at tail\n", _i);
+ }
+ if (_prevTest != node->head[_i]) {
+ printf("prev-list of axis %d does not end at head\n", _i);
+ }
+ for (; _nextTest && _nextTest->prev[_i]; _nextTest = _nextTest->prev[_i])
+ ;
+ for (; _prevTest && _prevTest->next[_i]; _prevTest = _prevTest->next[_i])
+ ;
+ if (_nextTest != node->head[_i]) {
+ printf("next-list of axis %d does not loop back to head\n", _i);
+ }
+ if (_prevTest != node->tail[_i]) {
+ printf("prev-list of axis %d does not loop back to tail\n", _i);
+ }
+ }
+ for (_i = 1; _i < 3; _i++) {
+ if (_prevCount[_i] != _prevCount[_i - 1] ||
+ _nextCount[_i] != _nextCount[_i - 1] ||
+ _prevCount[_i] != _nextCount[_i]) {
+ printf(
+ "{%d %d %d} {%d %d %d}\n",
+ _prevCount[0],
+ _prevCount[1],
+ _prevCount[2],
+ _nextCount[0],
+ _nextCount[1],
+ _nextCount[2]);
+ }
+ }
+ }
+#endif
+ node->axis = axis;
+ if (!splitlists(
+ node->head, node->tail, heads, tails, newCounts, axis, node->pixelCount)) {
+#ifndef NO_OUTPUT
+ printf("list split failed.\n");
+#endif
+ return 0;
+ }
+#ifdef TEST_SPLIT
+ if (!test_sorted(heads[0])) {
+ printf("bug in split");
+ exit(1);
+ }
+ if (!test_sorted(heads[1])) {
+ printf("bug in split");
+ exit(1);
+ }
+#endif
+ /* malloc check ok, small constant allocation */
+ left = malloc(sizeof(BoxNode));
+ right = malloc(sizeof(BoxNode));
+ if (!left || !right) {
+ free(left);
+ free(right);
+ return 0;
+ }
+ for (i = 0; i < 3; i++) {
+ left->head[i] = heads[0][i];
+ left->tail[i] = tails[0][i];
+ right->head[i] = heads[1][i];
+ right->tail[i] = tails[1][i];
+ node->head[i] = NULL;
+ node->tail[i] = NULL;
+ }
+#ifdef TEST_SPLIT
+ if (left->head[0]) {
+ rh = left->head[0]->p.c.r;
+ rl = left->tail[0]->p.c.r;
+ gh = left->head[1]->p.c.g;
+ gl = left->tail[1]->p.c.g;
+ bh = left->head[2]->p.c.b;
+ bl = left->tail[2]->p.c.b;
+ printf(" left node [%3d %3d %3d] [%3d %3d %3d]\n", rl, gl, bl, rh, gh, bh);
+ }
+ if (right->head[0]) {
+ rh = right->head[0]->p.c.r;
+ rl = right->tail[0]->p.c.r;
+ gh = right->head[1]->p.c.g;
+ gl = right->tail[1]->p.c.g;
+ bh = right->head[2]->p.c.b;
+ bl = right->tail[2]->p.c.b;
+ printf(" right node [%3d %3d %3d] [%3d %3d %3d]\n", rl, gl, bl, rh, gh, bh);
+ }
+#endif
+ left->l = left->r = NULL;
+ right->l = right->r = NULL;
+ left->axis = right->axis = -1;
+ left->volume = right->volume = -1;
+ left->pixelCount = newCounts[0];
+ right->pixelCount = newCounts[1];
+ node->l = left;
+ node->r = right;
+ return 1;
+}
+
+static BoxNode *
+median_cut(PixelList *hl[3], uint32_t imPixelCount, int nPixels) {
+ PixelList *tl[3];
+ int i;
+ BoxNode *root;
+ Heap *h;
+ BoxNode *thisNode;
+
+ h = ImagingQuantHeapNew(box_heap_cmp);
+ /* malloc check ok, small constant allocation */
+ root = malloc(sizeof(BoxNode));
+ if (!root) {
+ ImagingQuantHeapFree(h);
+ return NULL;
+ }
+ for (i = 0; i < 3; i++) {
+ for (tl[i] = hl[i]; tl[i] && tl[i]->next[i]; tl[i] = tl[i]->next[i])
+ ;
+ root->head[i] = hl[i];
+ root->tail[i] = tl[i];
+ }
+ root->l = root->r = NULL;
+ root->axis = -1;
+ root->volume = -1;
+ root->pixelCount = imPixelCount;
+
+ ImagingQuantHeapAdd(h, (void *)root);
+ while (--nPixels) {
+ do {
+ if (!ImagingQuantHeapRemove(h, (void **)&thisNode)) {
+ goto done;
+ }
+ } while (compute_box_volume(thisNode) == 1);
+ if (!split(thisNode)) {
+#ifndef NO_OUTPUT
+ printf("Oops, split failed...\n");
+#endif
+ exit(1);
+ }
+ ImagingQuantHeapAdd(h, (void *)(thisNode->l));
+ ImagingQuantHeapAdd(h, (void *)(thisNode->r));
+ }
+done:
+ ImagingQuantHeapFree(h);
+ return root;
+}
+
+static void
+free_box_tree(BoxNode *n) {
+ PixelList *p, *pp;
+ if (n->l) {
+ free_box_tree(n->l);
+ }
+ if (n->r) {
+ free_box_tree(n->r);
+ }
+ for (p = n->head[0]; p; p = pp) {
+ pp = p->next[0];
+ free(p);
+ }
+ free(n);
+}
+
+#ifdef TEST_SPLIT_INTEGRITY
+static int
+checkContained(BoxNode *n, Pixel *pp) {
+ if (n->l && n->r) {
+ return checkContained(n->l, pp) + checkContained(n->r, pp);
+ }
+ if (n->l || n->r) {
+#ifndef NO_OUTPUT
+ printf("box tree is dead\n");
+#endif
+ return 0;
+ }
+ if (pp->c.r <= n->head[0]->p.c.r && pp->c.r >= n->tail[0]->p.c.r &&
+ pp->c.g <= n->head[1]->p.c.g && pp->c.g >= n->tail[1]->p.c.g &&
+ pp->c.b <= n->head[2]->p.c.b && pp->c.b >= n->tail[2]->p.c.b) {
+ return 1;
+ }
+ return 0;
+}
+#endif
+
+static int
+annotate_hash_table(BoxNode *n, HashTable *h, uint32_t *box) {
+ PixelList *p;
+ PixelHashData *d = (PixelHashData *)hashtable_get_user_data(h);
+ Pixel q;
+ if (n->l && n->r) {
+ return annotate_hash_table(n->l, h, box) && annotate_hash_table(n->r, h, box);
+ }
+ if (n->l || n->r) {
+#ifndef NO_OUTPUT
+ printf("box tree is dead\n");
+#endif
+ return 0;
+ }
+ for (p = n->head[0]; p; p = p->next[0]) {
+ PIXEL_UNSCALE(&(p->p), &q, d->scale);
+ if (!hashtable_insert(h, q, *box)) {
+#ifndef NO_OUTPUT
+ printf("hashtable insert failed\n");
+#endif
+ return 0;
+ }
+ }
+ if (n->head[0]) {
+ (*box)++;
+ }
+ return 1;
+}
+
+typedef struct {
+ uint32_t *distance;
+ uint32_t index;
+} DistanceWithIndex;
+
+static int
+_distance_index_cmp(const void *a, const void *b) {
+ DistanceWithIndex *A = (DistanceWithIndex *)a;
+ DistanceWithIndex *B = (DistanceWithIndex *)b;
+ if (*A->distance == *B->distance) {
+ return A->index < B->index ? -1 : +1;
+ }
+ return *A->distance < *B->distance ? -1 : +1;
+}
+
+static int
+resort_distance_tables(
+ uint32_t *avgDist, uint32_t **avgDistSortKey, Pixel *p, uint32_t nEntries) {
+ uint32_t i, j, k;
+ uint32_t **skRow;
+ uint32_t *skElt;
+
+ for (i = 0; i < nEntries; i++) {
+ avgDist[i * nEntries + i] = 0;
+ for (j = 0; j < i; j++) {
+ avgDist[j * nEntries + i] = avgDist[i * nEntries + j] =
+ _DISTSQR(p + i, p + j);
+ }
+ }
+ for (i = 0; i < nEntries; i++) {
+ skRow = avgDistSortKey + i * nEntries;
+ for (j = 1; j < nEntries; j++) {
+ skElt = skRow[j];
+ for (k = j; k && (*(skRow[k - 1]) > *(skRow[k])); k--) {
+ skRow[k] = skRow[k - 1];
+ }
+ if (k != j) {
+ skRow[k] = skElt;
+ }
+ }
+ }
+ return 1;
+}
+
+static int
+build_distance_tables(
+ uint32_t *avgDist, uint32_t **avgDistSortKey, Pixel *p, uint32_t nEntries) {
+ uint32_t i, j;
+ DistanceWithIndex *dwi;
+
+ for (i = 0; i < nEntries; i++) {
+ avgDist[i * nEntries + i] = 0;
+ avgDistSortKey[i * nEntries + i] = &(avgDist[i * nEntries + i]);
+ for (j = 0; j < i; j++) {
+ avgDist[j * nEntries + i] = avgDist[i * nEntries + j] =
+ _DISTSQR(p + i, p + j);
+ avgDistSortKey[j * nEntries + i] = &(avgDist[j * nEntries + i]);
+ avgDistSortKey[i * nEntries + j] = &(avgDist[i * nEntries + j]);
+ }
+ }
+
+ dwi = calloc(nEntries, sizeof(DistanceWithIndex));
+ if (!dwi) {
+ return 0;
+ }
+ for (i = 0; i < nEntries; i++) {
+ for (j = 0; j < nEntries; j++) {
+ dwi[j] = (DistanceWithIndex){
+ &(avgDist[i * nEntries + j]),
+ j
+ };
+ }
+ qsort(
+ dwi,
+ nEntries,
+ sizeof(DistanceWithIndex),
+ _distance_index_cmp);
+ for (j = 0; j < nEntries; j++) {
+ avgDistSortKey[i * nEntries + j] = dwi[j].distance;
+ }
+ }
+ free(dwi);
+ return 1;
+}
+
+static int
+map_image_pixels(
+ Pixel *pixelData,
+ uint32_t nPixels,
+ Pixel *paletteData,
+ uint32_t nPaletteEntries,
+ uint32_t *avgDist,
+ uint32_t **avgDistSortKey,
+ uint32_t *pixelArray) {
+ uint32_t *aD, **aDSK;
+ uint32_t idx;
+ uint32_t i, j;
+ uint32_t bestdist, bestmatch, dist;
+ uint32_t initialdist;
+ HashTable *h2;
+
+ h2 = hashtable_new(unshifted_pixel_hash, unshifted_pixel_cmp);
+ for (i = 0; i < nPixels; i++) {
+ if (!hashtable_lookup(h2, pixelData[i], &bestmatch)) {
+ bestmatch = 0;
+ initialdist = _DISTSQR(paletteData + bestmatch, pixelData + i);
+ bestdist = initialdist;
+ initialdist <<= 2;
+ aDSK = avgDistSortKey + bestmatch * nPaletteEntries;
+ aD = avgDist + bestmatch * nPaletteEntries;
+ for (j = 0; j < nPaletteEntries; j++) {
+ idx = aDSK[j] - aD;
+ if (*(aDSK[j]) <= initialdist) {
+ dist = _DISTSQR(paletteData + idx, pixelData + i);
+ if (dist < bestdist) {
+ bestdist = dist;
+ bestmatch = idx;
+ }
+ } else {
+ break;
+ }
+ }
+ hashtable_insert(h2, pixelData[i], bestmatch);
+ }
+ pixelArray[i] = bestmatch;
+ }
+ hashtable_free(h2);
+ return 1;
+}
+
+static int
+map_image_pixels_from_quantized_pixels(
+ Pixel *pixelData,
+ uint32_t nPixels,
+ Pixel *paletteData,
+ uint32_t nPaletteEntries,
+ uint32_t *avgDist,
+ uint32_t **avgDistSortKey,
+ uint32_t *pixelArray,
+ uint32_t *avg[3],
+ uint32_t *count) {
+ uint32_t *aD, **aDSK;
+ uint32_t idx;
+ uint32_t i, j;
+ uint32_t bestdist, bestmatch, dist;
+ uint32_t initialdist;
+ HashTable *h2;
+ int changes = 0;
+
+ h2 = hashtable_new(unshifted_pixel_hash, unshifted_pixel_cmp);
+ for (i = 0; i < nPixels; i++) {
+ if (!hashtable_lookup(h2, pixelData[i], &bestmatch)) {
+ bestmatch = pixelArray[i];
+ initialdist = _DISTSQR(paletteData + bestmatch, pixelData + i);
+ bestdist = initialdist;
+ initialdist <<= 2;
+ aDSK = avgDistSortKey + bestmatch * nPaletteEntries;
+ aD = avgDist + bestmatch * nPaletteEntries;
+ for (j = 0; j < nPaletteEntries; j++) {
+ idx = aDSK[j] - aD;
+ if (*(aDSK[j]) <= initialdist) {
+ dist = _DISTSQR(paletteData + idx, pixelData + i);
+ if (dist < bestdist) {
+ bestdist = dist;
+ bestmatch = idx;
+ }
+ } else {
+ break;
+ }
+ }
+ hashtable_insert(h2, pixelData[i], bestmatch);
+ }
+ if (pixelArray[i] != bestmatch) {
+ changes++;
+ avg[0][bestmatch] += pixelData[i].c.r;
+ avg[1][bestmatch] += pixelData[i].c.g;
+ avg[2][bestmatch] += pixelData[i].c.b;
+ avg[0][pixelArray[i]] -= pixelData[i].c.r;
+ avg[1][pixelArray[i]] -= pixelData[i].c.g;
+ avg[2][pixelArray[i]] -= pixelData[i].c.b;
+ count[bestmatch]++;
+ count[pixelArray[i]]--;
+ pixelArray[i] = bestmatch;
+ }
+ }
+ hashtable_free(h2);
+ return changes;
+}
+
+static int
+map_image_pixels_from_median_box(
+ Pixel *pixelData,
+ uint32_t nPixels,
+ Pixel *paletteData,
+ uint32_t nPaletteEntries,
+ HashTable *medianBoxHash,
+ uint32_t *avgDist,
+ uint32_t **avgDistSortKey,
+ uint32_t *pixelArray) {
+ uint32_t *aD, **aDSK;
+ uint32_t idx;
+ uint32_t i, j;
+ uint32_t bestdist, bestmatch, dist;
+ uint32_t initialdist;
+ HashTable *h2;
+ uint32_t pixelVal;
+
+ h2 = hashtable_new(unshifted_pixel_hash, unshifted_pixel_cmp);
+ for (i = 0; i < nPixels; i++) {
+ if (hashtable_lookup(h2, pixelData[i], &pixelVal)) {
+ pixelArray[i] = pixelVal;
+ continue;
+ }
+ if (!hashtable_lookup(medianBoxHash, pixelData[i], &pixelVal)) {
+#ifndef NO_OUTPUT
+ printf("pixel lookup failed\n");
+#endif
+ return 0;
+ }
+ initialdist = _DISTSQR(paletteData + pixelVal, pixelData + i);
+ bestdist = initialdist;
+ bestmatch = pixelVal;
+ initialdist <<= 2;
+ aDSK = avgDistSortKey + pixelVal * nPaletteEntries;
+ aD = avgDist + pixelVal * nPaletteEntries;
+ for (j = 0; j < nPaletteEntries; j++) {
+ idx = aDSK[j] - aD;
+ if (*(aDSK[j]) <= initialdist) {
+ dist = _DISTSQR(paletteData + idx, pixelData + i);
+ if (dist < bestdist) {
+ bestdist = dist;
+ bestmatch = idx;
+ }
+ } else {
+ break;
+ }
+ }
+ pixelArray[i] = bestmatch;
+ hashtable_insert(h2, pixelData[i], bestmatch);
+ }
+ hashtable_free(h2);
+ return 1;
+}
+
+static int
+compute_palette_from_median_cut(
+ Pixel *pixelData,
+ uint32_t nPixels,
+ HashTable *medianBoxHash,
+ Pixel **palette,
+ uint32_t nPaletteEntries) {
+ uint32_t i;
+ uint32_t paletteEntry;
+ Pixel *p;
+ uint32_t *avg[3];
+ uint32_t *count;
+
+ *palette = NULL;
+ /* malloc check ok, using calloc */
+ if (!(count = calloc(nPaletteEntries, sizeof(uint32_t)))) {
+ return 0;
+ }
+ for (i = 0; i < 3; i++) {
+ avg[i] = NULL;
+ }
+ for (i = 0; i < 3; i++) {
+ /* malloc check ok, using calloc */
+ if (!(avg[i] = calloc(nPaletteEntries, sizeof(uint32_t)))) {
+ for (i = 0; i < 3; i++) {
+ if (avg[i]) {
+ free(avg[i]);
+ }
+ }
+ free(count);
+ return 0;
+ }
+ }
+ for (i = 0; i < nPixels; i++) {
+#ifdef TEST_SPLIT_INTEGRITY
+ if (!(i % 100)) {
+ printf("%05d\r", i);
+ fflush(stdout);
+ }
+ if (checkContained(root, pixelData + i) > 1) {
+ printf("pixel in two boxes\n");
+ for (i = 0; i < 3; i++) {
+ free(avg[i]);
+ }
+ free(count);
+ return 0;
+ }
+#endif
+ if (!hashtable_lookup(medianBoxHash, pixelData[i], &paletteEntry)) {
+#ifndef NO_OUTPUT
+ printf("pixel lookup failed\n");
+#endif
+ for (i = 0; i < 3; i++) {
+ free(avg[i]);
+ }
+ free(count);
+ return 0;
+ }
+ if (paletteEntry >= nPaletteEntries) {
+#ifndef NO_OUTPUT
+ printf(
+ "panic - paletteEntry>=nPaletteEntries (%d>=%d)\n",
+ (int)paletteEntry,
+ (int)nPaletteEntries);
+#endif
+ for (i = 0; i < 3; i++) {
+ free(avg[i]);
+ }
+ free(count);
+ return 0;
+ }
+ avg[0][paletteEntry] += pixelData[i].c.r;
+ avg[1][paletteEntry] += pixelData[i].c.g;
+ avg[2][paletteEntry] += pixelData[i].c.b;
+ count[paletteEntry]++;
+ }
+ /* malloc check ok, using calloc */
+ p = calloc(nPaletteEntries, sizeof(Pixel));
+ if (!p) {
+ for (i = 0; i < 3; i++) {
+ free(avg[i]);
+ }
+ free(count);
+ return 0;
+ }
+ for (i = 0; i < nPaletteEntries; i++) {
+ p[i].c.r = (int)(.5 + (double)avg[0][i] / (double)count[i]);
+ p[i].c.g = (int)(.5 + (double)avg[1][i] / (double)count[i]);
+ p[i].c.b = (int)(.5 + (double)avg[2][i] / (double)count[i]);
+ }
+ *palette = p;
+ for (i = 0; i < 3; i++) {
+ free(avg[i]);
+ }
+ free(count);
+ return 1;
+}
+
+static int
+recompute_palette_from_averages(
+ Pixel *palette, uint32_t nPaletteEntries, uint32_t *avg[3], uint32_t *count) {
+ uint32_t i;
+
+ for (i = 0; i < nPaletteEntries; i++) {
+ palette[i].c.r = (int)(.5 + (double)avg[0][i] / (double)count[i]);
+ palette[i].c.g = (int)(.5 + (double)avg[1][i] / (double)count[i]);
+ palette[i].c.b = (int)(.5 + (double)avg[2][i] / (double)count[i]);
+ }
+ return 1;
+}
+
+static int
+compute_palette_from_quantized_pixels(
+ Pixel *pixelData,
+ uint32_t nPixels,
+ Pixel *palette,
+ uint32_t nPaletteEntries,
+ uint32_t *avg[3],
+ uint32_t *count,
+ uint32_t *qp) {
+ uint32_t i;
+
+ memset(count, 0, sizeof(uint32_t) * nPaletteEntries);
+ for (i = 0; i < 3; i++) {
+ memset(avg[i], 0, sizeof(uint32_t) * nPaletteEntries);
+ }
+ for (i = 0; i < nPixels; i++) {
+ if (qp[i] >= nPaletteEntries) {
+#ifndef NO_OUTPUT
+ printf("scream\n");
+#endif
+ return 0;
+ }
+ avg[0][qp[i]] += pixelData[i].c.r;
+ avg[1][qp[i]] += pixelData[i].c.g;
+ avg[2][qp[i]] += pixelData[i].c.b;
+ count[qp[i]]++;
+ }
+ for (i = 0; i < nPaletteEntries; i++) {
+ palette[i].c.r = (int)(.5 + (double)avg[0][i] / (double)count[i]);
+ palette[i].c.g = (int)(.5 + (double)avg[1][i] / (double)count[i]);
+ palette[i].c.b = (int)(.5 + (double)avg[2][i] / (double)count[i]);
+ }
+ return 1;
+}
+
+static int
+k_means(
+ Pixel *pixelData,
+ uint32_t nPixels,
+ Pixel *paletteData,
+ uint32_t nPaletteEntries,
+ uint32_t *qp,
+ int threshold) {
+ uint32_t *avg[3];
+ uint32_t *count;
+ uint32_t i;
+ uint32_t *avgDist;
+ uint32_t **avgDistSortKey;
+ int changes;
+ int built = 0;
+
+ if (nPaletteEntries > UINT32_MAX / (sizeof(uint32_t))) {
+ return 0;
+ }
+ /* malloc check ok, using calloc */
+ if (!(count = calloc(nPaletteEntries, sizeof(uint32_t)))) {
+ return 0;
+ }
+ for (i = 0; i < 3; i++) {
+ avg[i] = NULL;
+ }
+ for (i = 0; i < 3; i++) {
+ /* malloc check ok, using calloc */
+ if (!(avg[i] = calloc(nPaletteEntries, sizeof(uint32_t)))) {
+ goto error_1;
+ }
+ }
+
+ /* this is enough of a check, since the multiplication n*size is done above */
+ if (nPaletteEntries > UINT32_MAX / nPaletteEntries) {
+ goto error_1;
+ }
+ /* malloc check ok, using calloc, checking n*n above */
+ avgDist = calloc(nPaletteEntries * nPaletteEntries, sizeof(uint32_t));
+ if (!avgDist) {
+ goto error_1;
+ }
+
+ /* malloc check ok, using calloc, checking n*n above */
+ avgDistSortKey = calloc(nPaletteEntries * nPaletteEntries, sizeof(uint32_t *));
+ if (!avgDistSortKey) {
+ goto error_2;
+ }
+
+#ifndef NO_OUTPUT
+ printf("[");
+ fflush(stdout);
+#endif
+ while (1) {
+ if (!built) {
+ compute_palette_from_quantized_pixels(
+ pixelData, nPixels, paletteData, nPaletteEntries, avg, count, qp);
+ if (!build_distance_tables(
+ avgDist, avgDistSortKey, paletteData, nPaletteEntries)) {
+ goto error_3;
+ }
+ built = 1;
+ } else {
+ recompute_palette_from_averages(paletteData, nPaletteEntries, avg, count);
+ resort_distance_tables(
+ avgDist, avgDistSortKey, paletteData, nPaletteEntries);
+ }
+ changes = map_image_pixels_from_quantized_pixels(
+ pixelData,
+ nPixels,
+ paletteData,
+ nPaletteEntries,
+ avgDist,
+ avgDistSortKey,
+ qp,
+ avg,
+ count);
+ if (changes < 0) {
+ goto error_3;
+ }
+#ifndef NO_OUTPUT
+ printf(".(%d)", changes);
+ fflush(stdout);
+#endif
+ if (changes <= threshold) {
+ break;
+ }
+ }
+#ifndef NO_OUTPUT
+ printf("]\n");
+#endif
+ if (avgDistSortKey) {
+ free(avgDistSortKey);
+ }
+ if (avgDist) {
+ free(avgDist);
+ }
+ for (i = 0; i < 3; i++) {
+ if (avg[i]) {
+ free(avg[i]);
+ }
+ }
+ if (count) {
+ free(count);
+ }
+ return 1;
+
+error_3:
+ if (avgDistSortKey) {
+ free(avgDistSortKey);
+ }
+error_2:
+ if (avgDist) {
+ free(avgDist);
+ }
+error_1:
+ for (i = 0; i < 3; i++) {
+ if (avg[i]) {
+ free(avg[i]);
+ }
+ }
+ if (count) {
+ free(count);
+ }
+ return 0;
+}
+
+static int
+quantize(
+ Pixel *pixelData,
+ uint32_t nPixels,
+ uint32_t nQuantPixels,
+ Pixel **palette,
+ uint32_t *paletteLength,
+ uint32_t **quantizedPixels,
+ int kmeans) {
+ PixelList *hl[3];
+ HashTable *h;
+ BoxNode *root;
+ uint32_t i;
+ uint32_t *qp;
+ uint32_t nPaletteEntries;
+
+ uint32_t *avgDist;
+ uint32_t **avgDistSortKey;
+ Pixel *p;
+
+#ifndef NO_OUTPUT
+ uint32_t timer, timer2;
+#endif
+
+#ifndef NO_OUTPUT
+ timer2 = clock();
+ printf("create hash table...");
+ fflush(stdout);
+ timer = clock();
+#endif
+ h = create_pixel_hash(pixelData, nPixels);
+#ifndef NO_OUTPUT
+ printf("done (%f)\n", (clock() - timer) / (double)CLOCKS_PER_SEC);
+#endif
+ if (!h) {
+ goto error_0;
+ }
+
+#ifndef NO_OUTPUT
+ printf("create lists from hash table...");
+ fflush(stdout);
+ timer = clock();
+#endif
+ hl[0] = hl[1] = hl[2] = NULL;
+ hashtable_foreach(h, hash_to_list, hl);
+#ifndef NO_OUTPUT
+ printf("done (%f)\n", (clock() - timer) / (double)CLOCKS_PER_SEC);
+#endif
+
+ if (!hl[0]) {
+ goto error_1;
+ }
+
+#ifndef NO_OUTPUT
+ printf("mergesort lists...");
+ fflush(stdout);
+ timer = clock();
+#endif
+ for (i = 0; i < 3; i++) {
+ hl[i] = mergesort_pixels(hl[i], i);
+ }
+#ifdef TEST_MERGESORT
+ if (!test_sorted(hl)) {
+ printf("bug in mergesort\n");
+ goto error_1;
+ }
+#endif
+#ifndef NO_OUTPUT
+ printf("done (%f)\n", (clock() - timer) / (double)CLOCKS_PER_SEC);
+#endif
+
+#ifndef NO_OUTPUT
+ printf("median cut...");
+ fflush(stdout);
+ timer = clock();
+#endif
+ root = median_cut(hl, nPixels, nQuantPixels);
+#ifndef NO_OUTPUT
+ printf("done (%f)\n", (clock() - timer) / (double)CLOCKS_PER_SEC);
+#endif
+ if (!root) {
+ goto error_1;
+ }
+ nPaletteEntries = 0;
+#ifndef NO_OUTPUT
+ printf("median cut tree to hash table...");
+ fflush(stdout);
+ timer = clock();
+#endif
+ annotate_hash_table(root, h, &nPaletteEntries);
+#ifndef NO_OUTPUT
+ printf("done (%f)\n", (clock() - timer) / (double)CLOCKS_PER_SEC);
+#endif
+#ifndef NO_OUTPUT
+ printf("compute palette...\n");
+ fflush(stdout);
+ timer = clock();
+#endif
+ if (!compute_palette_from_median_cut(pixelData, nPixels, h, &p, nPaletteEntries)) {
+ goto error_3;
+ }
+#ifndef NO_OUTPUT
+ printf("done (%f)\n", (clock() - timer) / (double)CLOCKS_PER_SEC);
+#endif
+
+ free_box_tree(root);
+ root = NULL;
+
+ /* malloc check ok, using calloc for overflow */
+ qp = calloc(nPixels, sizeof(uint32_t));
+ if (!qp) {
+ goto error_4;
+ }
+
+ if (nPaletteEntries > UINT32_MAX / nPaletteEntries) {
+ goto error_5;
+ }
+ /* malloc check ok, using calloc for overflow, check of n*n above */
+ avgDist = calloc(nPaletteEntries * nPaletteEntries, sizeof(uint32_t));
+ if (!avgDist) {
+ goto error_5;
+ }
+
+ /* malloc check ok, using calloc for overflow, check of n*n above */
+ avgDistSortKey = calloc(nPaletteEntries * nPaletteEntries, sizeof(uint32_t *));
+ if (!avgDistSortKey) {
+ goto error_6;
+ }
+
+ if (!build_distance_tables(avgDist, avgDistSortKey, p, nPaletteEntries)) {
+ goto error_7;
+ }
+
+ if (!map_image_pixels_from_median_box(
+ pixelData, nPixels, p, nPaletteEntries, h, avgDist, avgDistSortKey, qp)) {
+ goto error_7;
+ }
+
+#ifdef TEST_NEAREST_NEIGHBOUR
+#include <math.h>
+ {
+ uint32_t bestmatch, bestdist, dist;
+ HashTable *h2;
+ printf("nearest neighbour search (full search)...");
+ fflush(stdout);
+ timer = clock();
+ h2 = hashtable_new(unshifted_pixel_hash, unshifted_pixel_cmp);
+ for (i = 0; i < nPixels; i++) {
+ if (hashtable_lookup(h2, pixelData[i], &paletteEntry)) {
+ bestmatch = paletteEntry;
+ } else {
+ bestmatch = 0;
+ bestdist = _SQR(pixelData[i].c.r - p[0].c.r) +
+ _SQR(pixelData[i].c.g - p[0].c.g) +
+ _SQR(pixelData[i].c.b - p[0].c.b);
+ for (j = 1; j < nPaletteEntries; j++) {
+ dist = _SQR(pixelData[i].c.r - p[j].c.r) +
+ _SQR(pixelData[i].c.g - p[j].c.g) +
+ _SQR(pixelData[i].c.b - p[j].c.b);
+ if (dist == bestdist && j == qp[i]) {
+ bestmatch = j;
+ }
+ if (dist < bestdist) {
+ bestdist = dist;
+ bestmatch = j;
+ }
+ }
+ hashtable_insert(h2, pixelData[i], bestmatch);
+ }
+ if (qp[i] != bestmatch) {
+ printf ("discrepancy in matching algorithms pixel %d [%d %d] %f %f\n",
+ i,qp[i],bestmatch,
+ sqrt((double)(_SQR(pixelData[i].c.r-p[qp[i]].c.r)+
+ _SQR(pixelData[i].c.g-p[qp[i]].c.g)+
+ _SQR(pixelData[i].c.b-p[qp[i]].c.b))),
+ sqrt((double)(_SQR(pixelData[i].c.r-p[bestmatch].c.r)+
+ _SQR(pixelData[i].c.g-p[bestmatch].c.g)+
+ _SQR(pixelData[i].c.b-p[bestmatch].c.b)))
+ );
+ }
+ }
+ hashtable_free(h2);
+ }
+#endif
+#ifndef NO_OUTPUT
+ printf("k means...\n");
+ fflush(stdout);
+ timer = clock();
+#endif
+ if (kmeans) {
+ k_means(pixelData, nPixels, p, nPaletteEntries, qp, kmeans - 1);
+ }
+#ifndef NO_OUTPUT
+ printf("done (%f)\n", (clock() - timer) / (double)CLOCKS_PER_SEC);
+#endif
+
+ *quantizedPixels = qp;
+ *palette = p;
+ *paletteLength = nPaletteEntries;
+
+#ifndef NO_OUTPUT
+ printf("cleanup...");
+ fflush(stdout);
+ timer = clock();
+#endif
+ if (avgDist) {
+ free(avgDist);
+ }
+ if (avgDistSortKey) {
+ free(avgDistSortKey);
+ }
+ destroy_pixel_hash(h);
+#ifndef NO_OUTPUT
+ printf("done (%f)\n", (clock() - timer) / (double)CLOCKS_PER_SEC);
+ printf("-----\ntotal time %f\n", (clock() - timer2) / (double)CLOCKS_PER_SEC);
+#endif
+ return 1;
+
+error_7:
+ if (avgDistSortKey) {
+ free(avgDistSortKey);
+ }
+error_6:
+ if (avgDist) {
+ free(avgDist);
+ }
+error_5:
+ if (qp) {
+ free(qp);
+ }
+error_4:
+ if (p) {
+ free(p);
+ }
+error_3:
+ if (root) {
+ free_box_tree(root);
+ }
+error_1:
+ destroy_pixel_hash(h);
+error_0:
+ *quantizedPixels = NULL;
+ *paletteLength = 0;
+ *palette = NULL;
+ return 0;
+}
+
+typedef struct {
+ Pixel new;
+ uint32_t furthestV;
+ uint32_t furthestDistance;
+ int secondPixel;
+} DistanceData;
+
+static void
+compute_distances(const HashTable *h, const Pixel pixel, uint32_t *dist, void *u) {
+ DistanceData *data = (DistanceData *)u;
+ uint32_t oldDist = *dist;
+ uint32_t newDist;
+ newDist = _DISTSQR(&(data->new), &pixel);
+ if (data->secondPixel || newDist < oldDist) {
+ *dist = newDist;
+ oldDist = newDist;
+ }
+ if (oldDist > data->furthestDistance) {
+ data->furthestDistance = oldDist;
+ data->furthestV = pixel.v;
+ }
+}
+
+static int
+quantize2(
+ Pixel *pixelData,
+ uint32_t nPixels,
+ uint32_t nQuantPixels,
+ Pixel **palette,
+ uint32_t *paletteLength,
+ uint32_t **quantizedPixels,
+ int kmeans) {
+ HashTable *h;
+ uint32_t i;
+ uint32_t mean[3];
+ Pixel *p;
+ DistanceData data;
+
+ uint32_t *qp;
+ uint32_t *avgDist;
+ uint32_t **avgDistSortKey;
+
+ /* malloc check ok, using calloc */
+ p = calloc(nQuantPixels, sizeof(Pixel));
+ if (!p) {
+ return 0;
+ }
+ mean[0] = mean[1] = mean[2] = 0;
+ h = hashtable_new(unshifted_pixel_hash, unshifted_pixel_cmp);
+ for (i = 0; i < nPixels; i++) {
+ hashtable_insert(h, pixelData[i], 0xffffffff);
+ mean[0] += pixelData[i].c.r;
+ mean[1] += pixelData[i].c.g;
+ mean[2] += pixelData[i].c.b;
+ }
+ data.new.c.r = (int)(.5 + (double)mean[0] / (double)nPixels);
+ data.new.c.g = (int)(.5 + (double)mean[1] / (double)nPixels);
+ data.new.c.b = (int)(.5 + (double)mean[2] / (double)nPixels);
+ for (i = 0; i < nQuantPixels; i++) {
+ data.furthestDistance = 0;
+ data.furthestV = pixelData[0].v;
+ data.secondPixel = (i == 1) ? 1 : 0;
+ hashtable_foreach_update(h, compute_distances, &data);
+ p[i].v = data.furthestV;
+ data.new.v = data.furthestV;
+ }
+ hashtable_free(h);
+
+ /* malloc check ok, using calloc */
+ qp = calloc(nPixels, sizeof(uint32_t));
+ if (!qp) {
+ goto error_1;
+ }
+
+ if (nQuantPixels > UINT32_MAX / nQuantPixels) {
+ goto error_2;
+ }
+
+ /* malloc check ok, using calloc for overflow, check of n*n above */
+ avgDist = calloc(nQuantPixels * nQuantPixels, sizeof(uint32_t));
+ if (!avgDist) {
+ goto error_2;
+ }
+
+ /* malloc check ok, using calloc for overflow, check of n*n above */
+ avgDistSortKey = calloc(nQuantPixels * nQuantPixels, sizeof(uint32_t *));
+ if (!avgDistSortKey) {
+ goto error_3;
+ }
+
+ if (!build_distance_tables(avgDist, avgDistSortKey, p, nQuantPixels)) {
+ goto error_4;
+ }
+
+ if (!map_image_pixels(
+ pixelData, nPixels, p, nQuantPixels, avgDist, avgDistSortKey, qp)) {
+ goto error_4;
+ }
+ if (kmeans) {
+ k_means(pixelData, nPixels, p, nQuantPixels, qp, kmeans - 1);
+ }
+
+ *paletteLength = nQuantPixels;
+ *palette = p;
+ *quantizedPixels = qp;
+ free(avgDistSortKey);
+ free(avgDist);
+ return 1;
+
+error_4:
+ free(avgDistSortKey);
+error_3:
+ free(avgDist);
+error_2:
+ free(qp);
+error_1:
+ free(p);
+ return 0;
+}
+
+Imaging
+ImagingQuantize(Imaging im, int colors, int mode, int kmeans) {
+ int i, j;
+ int x, y, v;
+ UINT8 *pp;
+ Pixel *p;
+ Pixel *palette;
+ uint32_t paletteLength;
+ int result;
+ uint32_t *newData;
+ Imaging imOut;
+ int withAlpha = 0;
+ ImagingSectionCookie cookie;
+
+ if (!im) {
+ return ImagingError_ModeError();
+ }
+ if (colors < 1 || colors > 256) {
+ /* FIXME: for colors > 256, consider returning an RGB image
+ instead (see @PIL205) */
+ return (Imaging)ImagingError_ValueError("bad number of colors");
+ }
+
+ if (strcmp(im->mode, "L") != 0 && strcmp(im->mode, "P") != 0 &&
+ strcmp(im->mode, "RGB") != 0 && strcmp(im->mode, "RGBA") != 0) {
+ return ImagingError_ModeError();
+ }
+
+ /* only octree and imagequant supports RGBA */
+ if (!strcmp(im->mode, "RGBA") && mode != 2 && mode != 3) {
+ return ImagingError_ModeError();
+ }
+
+ if (im->xsize > INT_MAX / im->ysize) {
+ return ImagingError_MemoryError();
+ }
+ /* malloc check ok, using calloc for final overflow, x*y above */
+ p = calloc(im->xsize * im->ysize, sizeof(Pixel));
+ if (!p) {
+ return ImagingError_MemoryError();
+ }
+
+ /* collect statistics */
+
+ /* FIXME: maybe we could load the hash tables directly from the
+ image data? */
+
+ if (!strcmp(im->mode, "L")) {
+ /* greyscale */
+
+ /* FIXME: converting a "L" image to "P" with 256 colors
+ should be done by a simple copy... */
+
+ for (i = y = 0; y < im->ysize; y++) {
+ for (x = 0; x < im->xsize; x++, i++) {
+ p[i].c.r = p[i].c.g = p[i].c.b = im->image8[y][x];
+ p[i].c.a = 255;
+ }
+ }
+
+ } else if (!strcmp(im->mode, "P")) {
+ /* palette */
+
+ pp = im->palette->palette;
+
+ for (i = y = 0; y < im->ysize; y++) {
+ for (x = 0; x < im->xsize; x++, i++) {
+ v = im->image8[y][x];
+ p[i].c.r = pp[v * 4 + 0];
+ p[i].c.g = pp[v * 4 + 1];
+ p[i].c.b = pp[v * 4 + 2];
+ p[i].c.a = pp[v * 4 + 3];
+ }
+ }
+
+ } else if (!strcmp(im->mode, "RGB") || !strcmp(im->mode, "RGBA")) {
+ /* true colour */
+
+ withAlpha = !strcmp(im->mode, "RGBA");
+ int transparency = 0;
+ unsigned char r = 0, g = 0, b = 0;
+ for (i = y = 0; y < im->ysize; y++) {
+ for (x = 0; x < im->xsize; x++, i++) {
+ p[i].v = im->image32[y][x];
+ if (withAlpha && p[i].c.a == 0) {
+ if (transparency == 0) {
+ transparency = 1;
+ r = p[i].c.r;
+ g = p[i].c.g;
+ b = p[i].c.b;
+ } else {
+ /* Set all subsequent transparent pixels
+ to the same colour as the first */
+ p[i].c.r = r;
+ p[i].c.g = g;
+ p[i].c.b = b;
+ }
+ }
+ }
+ }
+
+ } else {
+ free(p);
+ return (Imaging)ImagingError_ValueError("internal error");
+ }
+
+ ImagingSectionEnter(&cookie);
+
+ switch (mode) {
+ case 0:
+ /* median cut */
+ result = quantize(
+ p,
+ im->xsize * im->ysize,
+ colors,
+ &palette,
+ &paletteLength,
+ &newData,
+ kmeans);
+ break;
+ case 1:
+ /* maximum coverage */
+ result = quantize2(
+ p,
+ im->xsize * im->ysize,
+ colors,
+ &palette,
+ &paletteLength,
+ &newData,
+ kmeans);
+ break;
+ case 2:
+ result = quantize_octree(
+ p,
+ im->xsize * im->ysize,
+ colors,
+ &palette,
+ &paletteLength,
+ &newData,
+ withAlpha);
+ break;
+ case 3:
+#ifdef HAVE_LIBIMAGEQUANT
+ result = quantize_pngquant(
+ p,
+ im->xsize,
+ im->ysize,
+ colors,
+ &palette,
+ &paletteLength,
+ &newData,
+ withAlpha);
+#else
+ result = -1;
+#endif
+ break;
+ default:
+ result = 0;
+ break;
+ }
+
+ free(p);
+ ImagingSectionLeave(&cookie);
+
+ if (result > 0) {
+ imOut = ImagingNewDirty("P", im->xsize, im->ysize);
+ ImagingSectionEnter(&cookie);
+
+ for (i = y = 0; y < im->ysize; y++) {
+ for (x = 0; x < im->xsize; x++) {
+ imOut->image8[y][x] = (unsigned char)newData[i++];
+ }
+ }
+
+ free(newData);
+
+ imOut->palette->size = (int)paletteLength;
+ pp = imOut->palette->palette;
+
+ for (i = j = 0; i < (int)paletteLength; i++) {
+ *pp++ = palette[i].c.r;
+ *pp++ = palette[i].c.g;
+ *pp++ = palette[i].c.b;
+ if (withAlpha) {
+ *pp = palette[i].c.a;
+ }
+ pp++;
+ }
+
+ if (withAlpha) {
+ strcpy(imOut->palette->mode, "RGBA");
+ }
+
+ free(palette);
+ ImagingSectionLeave(&cookie);
+
+ return imOut;
+
+ } else {
+ if (result == -1) {
+ return (Imaging)ImagingError_ValueError(
+ "dependency required by this method was not "
+ "enabled at compile time");
+ }
+
+ return (Imaging)ImagingError_ValueError("quantization error");
+ }
+}
diff --git a/contrib/python/Pillow/py3/libImaging/QuantHash.c b/contrib/python/Pillow/py3/libImaging/QuantHash.c
new file mode 100644
index 00000000000..ea75d6037f9
--- /dev/null
+++ b/contrib/python/Pillow/py3/libImaging/QuantHash.c
@@ -0,0 +1,336 @@
+/*
+ * The Python Imaging Library
+ * $Id$
+ *
+ * hash tables used by the image quantizer
+ *
+ * history:
+ * 98-09-10 tjs Contributed
+ * 98-12-29 fl Added to PIL 1.0b1
+ *
+ * Written by Toby J Sargeant <[email protected]>.
+ *
+ * Copyright (c) 1998 by Toby J Sargeant
+ * Copyright (c) 1998 by Secret Labs AB
+ *
+ * See the README file for information on usage and redistribution.
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <math.h>
+
+#include "QuantHash.h"
+
+typedef struct _HashNode {
+ struct _HashNode *next;
+ HashKey_t key;
+ HashVal_t value;
+} HashNode;
+
+struct _HashTable {
+ HashNode **table;
+ uint32_t length;
+ uint32_t count;
+ HashFunc hashFunc;
+ HashCmpFunc cmpFunc;
+ void *userData;
+};
+
+#define MIN_LENGTH 11
+#define RESIZE_FACTOR 3
+
+static int
+_hashtable_insert_node(HashTable *, HashNode *, int, int, CollisionFunc);
+
+HashTable *
+hashtable_new(HashFunc hf, HashCmpFunc cf) {
+ HashTable *h;
+ h = malloc(sizeof(HashTable));
+ if (!h) {
+ return NULL;
+ }
+ h->hashFunc = hf;
+ h->cmpFunc = cf;
+ h->length = MIN_LENGTH;
+ h->count = 0;
+ h->userData = NULL;
+ h->table = malloc(sizeof(HashNode *) * h->length);
+ if (!h->table) {
+ free(h);
+ return NULL;
+ }
+ memset(h->table, 0, sizeof(HashNode *) * h->length);
+ return h;
+}
+
+static uint32_t
+_findPrime(uint32_t start, int dir) {
+ static int unit[] = {0, 1, 0, 1, 0, 0, 0, 1, 0, 1, 0, 1, 0, 1, 0, 0};
+ uint32_t t;
+ while (start > 1) {
+ if (!unit[start & 0x0f]) {
+ start += dir;
+ continue;
+ }
+ for (t = 2; t < sqrt((double)start); t++) {
+ if (!start % t) {
+ break;
+ }
+ }
+ if (t >= sqrt((double)start)) {
+ break;
+ }
+ start += dir;
+ }
+ return start;
+}
+
+static void
+_hashtable_rehash(HashTable *h, CollisionFunc cf, uint32_t newSize) {
+ HashNode **oldTable = h->table;
+ uint32_t i;
+ HashNode *n, *nn;
+ uint32_t oldSize;
+ oldSize = h->length;
+ h->table = malloc(sizeof(HashNode *) * newSize);
+ if (!h->table) {
+ h->table = oldTable;
+ return;
+ }
+ h->length = newSize;
+ h->count = 0;
+ memset(h->table, 0, sizeof(HashNode *) * h->length);
+ for (i = 0; i < oldSize; i++) {
+ for (n = oldTable[i]; n; n = nn) {
+ nn = n->next;
+ _hashtable_insert_node(h, n, 0, 0, cf);
+ }
+ }
+ free(oldTable);
+}
+
+static void
+_hashtable_resize(HashTable *h) {
+ uint32_t newSize;
+ uint32_t oldSize;
+ oldSize = h->length;
+ newSize = oldSize;
+ if (h->count * RESIZE_FACTOR < h->length) {
+ newSize = _findPrime(h->length / 2 - 1, -1);
+ } else if (h->length * RESIZE_FACTOR < h->count) {
+ newSize = _findPrime(h->length * 2 + 1, +1);
+ }
+ if (newSize < MIN_LENGTH) {
+ newSize = oldSize;
+ }
+ if (newSize != oldSize) {
+ _hashtable_rehash(h, NULL, newSize);
+ }
+}
+
+static int
+_hashtable_insert_node(
+ HashTable *h, HashNode *node, int resize, int update, CollisionFunc cf) {
+ uint32_t hash = h->hashFunc(h, node->key) % h->length;
+ HashNode **n, *nv;
+ int i;
+
+ for (n = &(h->table[hash]); *n; n = &((*n)->next)) {
+ nv = *n;
+ i = h->cmpFunc(h, nv->key, node->key);
+ if (!i) {
+ if (cf) {
+ nv->key = node->key;
+ cf(h, &(nv->key), &(nv->value), node->key, node->value);
+ free(node);
+ return 1;
+ } else {
+ nv->key = node->key;
+ nv->value = node->value;
+ free(node);
+ return 1;
+ }
+ } else if (i > 0) {
+ break;
+ }
+ }
+ if (!update) {
+ node->next = *n;
+ *n = node;
+ h->count++;
+ if (resize) {
+ _hashtable_resize(h);
+ }
+ return 1;
+ } else {
+ return 0;
+ }
+}
+
+static int
+_hashtable_insert(HashTable *h, HashKey_t key, HashVal_t val, int resize, int update) {
+ HashNode **n, *nv;
+ HashNode *t;
+ int i;
+ uint32_t hash = h->hashFunc(h, key) % h->length;
+
+ for (n = &(h->table[hash]); *n; n = &((*n)->next)) {
+ nv = *n;
+ i = h->cmpFunc(h, nv->key, key);
+ if (!i) {
+ nv->value = val;
+ return 1;
+ } else if (i > 0) {
+ break;
+ }
+ }
+ if (!update) {
+ t = malloc(sizeof(HashNode));
+ if (!t) {
+ return 0;
+ }
+ t->next = *n;
+ *n = t;
+ t->key = key;
+ t->value = val;
+ h->count++;
+ if (resize) {
+ _hashtable_resize(h);
+ }
+ return 1;
+ } else {
+ return 0;
+ }
+}
+
+int
+hashtable_insert_or_update_computed(
+ HashTable *h, HashKey_t key, ComputeFunc newFunc, ComputeFunc existsFunc) {
+ HashNode **n, *nv;
+ HashNode *t;
+ int i;
+ uint32_t hash = h->hashFunc(h, key) % h->length;
+
+ for (n = &(h->table[hash]); *n; n = &((*n)->next)) {
+ nv = *n;
+ i = h->cmpFunc(h, nv->key, key);
+ if (!i) {
+ if (existsFunc) {
+ existsFunc(h, nv->key, &(nv->value));
+ } else {
+ return 0;
+ }
+ return 1;
+ } else if (i > 0) {
+ break;
+ }
+ }
+ t = malloc(sizeof(HashNode));
+ if (!t) {
+ return 0;
+ }
+ t->key = key;
+ t->next = *n;
+ *n = t;
+ if (newFunc) {
+ newFunc(h, t->key, &(t->value));
+ } else {
+ free(t);
+ return 0;
+ }
+ h->count++;
+ _hashtable_resize(h);
+ return 1;
+}
+
+int
+hashtable_insert(HashTable *h, HashKey_t key, HashVal_t val) {
+ return _hashtable_insert(h, key, val, 1, 0);
+}
+
+void
+hashtable_foreach_update(HashTable *h, IteratorUpdateFunc i, void *u) {
+ HashNode *n;
+ uint32_t x;
+
+ if (h->table) {
+ for (x = 0; x < h->length; x++) {
+ for (n = h->table[x]; n; n = n->next) {
+ i(h, n->key, &(n->value), u);
+ }
+ }
+ }
+}
+
+void
+hashtable_foreach(HashTable *h, IteratorFunc i, void *u) {
+ HashNode *n;
+ uint32_t x;
+
+ if (h->table) {
+ for (x = 0; x < h->length; x++) {
+ for (n = h->table[x]; n; n = n->next) {
+ i(h, n->key, n->value, u);
+ }
+ }
+ }
+}
+
+void
+hashtable_free(HashTable *h) {
+ HashNode *n, *nn;
+ uint32_t i;
+
+ if (h->table) {
+ for (i = 0; i < h->length; i++) {
+ for (n = h->table[i]; n; n = nn) {
+ nn = n->next;
+ free(n);
+ }
+ }
+ free(h->table);
+ }
+ free(h);
+}
+
+void
+hashtable_rehash_compute(HashTable *h, CollisionFunc cf) {
+ _hashtable_rehash(h, cf, h->length);
+}
+
+int
+hashtable_lookup(const HashTable *h, const HashKey_t key, HashVal_t *valp) {
+ uint32_t hash = h->hashFunc(h, key) % h->length;
+ HashNode *n;
+ int i;
+
+ for (n = h->table[hash]; n; n = n->next) {
+ i = h->cmpFunc(h, n->key, key);
+ if (!i) {
+ *valp = n->value;
+ return 1;
+ } else if (i > 0) {
+ break;
+ }
+ }
+ return 0;
+}
+
+uint32_t
+hashtable_get_count(const HashTable *h) {
+ return h->count;
+}
+
+void *
+hashtable_get_user_data(const HashTable *h) {
+ return h->userData;
+}
+
+void *
+hashtable_set_user_data(HashTable *h, void *data) {
+ void *r = h->userData;
+ h->userData = data;
+ return r;
+}
diff --git a/contrib/python/Pillow/py3/libImaging/QuantHash.h b/contrib/python/Pillow/py3/libImaging/QuantHash.h
new file mode 100644
index 00000000000..fc1a9900376
--- /dev/null
+++ b/contrib/python/Pillow/py3/libImaging/QuantHash.h
@@ -0,0 +1,55 @@
+/*
+ * The Python Imaging Library
+ * $Id$
+ *
+ * image quantizer
+ *
+ * Written by Toby J Sargeant <[email protected]>.
+ *
+ * See the README file for information on usage and redistribution.
+ */
+
+#ifndef __QUANTHASH_H__
+#define __QUANTHASH_H__
+
+#include "QuantTypes.h"
+
+typedef struct _HashTable HashTable;
+typedef Pixel HashKey_t;
+typedef uint32_t HashVal_t;
+
+typedef uint32_t (*HashFunc)(const HashTable *, const HashKey_t);
+typedef int (*HashCmpFunc)(const HashTable *, const HashKey_t, const HashKey_t);
+typedef void (*IteratorFunc)(
+ const HashTable *, const HashKey_t, const HashVal_t, void *);
+typedef void (*IteratorUpdateFunc)(
+ const HashTable *, const HashKey_t, HashVal_t *, void *);
+typedef void (*ComputeFunc)(const HashTable *, const HashKey_t, HashVal_t *);
+typedef void (*CollisionFunc)(
+ const HashTable *, HashKey_t *, HashVal_t *, HashKey_t, HashVal_t);
+
+HashTable *
+hashtable_new(HashFunc hf, HashCmpFunc cf);
+void
+hashtable_free(HashTable *h);
+void
+hashtable_foreach(HashTable *h, IteratorFunc i, void *u);
+void
+hashtable_foreach_update(HashTable *h, IteratorUpdateFunc i, void *u);
+int
+hashtable_insert(HashTable *h, HashKey_t key, HashVal_t val);
+int
+hashtable_lookup(const HashTable *h, const HashKey_t key, HashVal_t *valp);
+int
+hashtable_insert_or_update_computed(
+ HashTable *h, HashKey_t key, ComputeFunc newFunc, ComputeFunc existsFunc);
+void *
+hashtable_set_user_data(HashTable *h, void *data);
+void *
+hashtable_get_user_data(const HashTable *h);
+uint32_t
+hashtable_get_count(const HashTable *h);
+void
+hashtable_rehash_compute(HashTable *h, CollisionFunc cf);
+
+#endif // __QUANTHASH_H__
diff --git a/contrib/python/Pillow/py3/libImaging/QuantHeap.c b/contrib/python/Pillow/py3/libImaging/QuantHeap.c
new file mode 100644
index 00000000000..6fb52d8902e
--- /dev/null
+++ b/contrib/python/Pillow/py3/libImaging/QuantHeap.c
@@ -0,0 +1,176 @@
+/*
+ * The Python Imaging Library
+ * $Id$
+ *
+ * heap data type used by the image quantizer
+ *
+ * history:
+ * 98-09-10 tjs Contributed
+ * 98-12-29 fl Added to PIL 1.0b1
+ *
+ * Written by Toby J Sargeant <[email protected]>.
+ *
+ * Copyright (c) 1998 by Toby J Sargeant
+ * Copyright (c) 1998 by Secret Labs AB
+ *
+ * See the README file for information on usage and redistribution.
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <math.h>
+#include <limits.h>
+
+#include "QuantHeap.h"
+
+struct _Heap {
+ void **heap;
+ unsigned int heapsize;
+ unsigned int heapcount;
+ HeapCmpFunc cf;
+};
+
+#define INITIAL_SIZE 256
+
+// #define DEBUG
+
+#ifdef DEBUG
+static int
+_heap_test(Heap *);
+#endif
+
+void
+ImagingQuantHeapFree(Heap *h) {
+ free(h->heap);
+ free(h);
+}
+
+static int
+_heap_grow(Heap *h, unsigned int newsize) {
+ void *newheap;
+ if (!newsize) {
+ newsize = h->heapsize << 1;
+ }
+ if (newsize < h->heapsize) {
+ return 0;
+ }
+ if (newsize > INT_MAX / sizeof(void *)) {
+ return 0;
+ }
+ /* malloc check ok, using calloc for overflow, also checking
+ above due to memcpy below*/
+ newheap = calloc(newsize, sizeof(void *));
+ if (!newheap) {
+ return 0;
+ }
+ memcpy(newheap, h->heap, sizeof(void *) * h->heapsize);
+ free(h->heap);
+ h->heap = newheap;
+ h->heapsize = newsize;
+ return 1;
+}
+
+#ifdef DEBUG
+static int
+_heap_test(Heap *h) {
+ unsigned int k;
+ for (k = 1; k * 2 <= h->heapcount; k++) {
+ if (h->cf(h, h->heap[k], h->heap[k * 2]) < 0) {
+ printf("heap is bad\n");
+ return 0;
+ }
+ if (k * 2 + 1 <= h->heapcount && h->cf(h, h->heap[k], h->heap[k * 2 + 1]) < 0) {
+ printf("heap is bad\n");
+ return 0;
+ }
+ }
+ return 1;
+}
+#endif
+
+int
+ImagingQuantHeapRemove(Heap *h, void **r) {
+ unsigned int k, l;
+ void *v;
+
+ if (!h->heapcount) {
+ return 0;
+ }
+ *r = h->heap[1];
+ v = h->heap[h->heapcount--];
+ for (k = 1; k * 2 <= h->heapcount; k = l) {
+ l = k * 2;
+ if (l < h->heapcount) {
+ if (h->cf(h, h->heap[l], h->heap[l + 1]) < 0) {
+ l++;
+ }
+ }
+ if (h->cf(h, v, h->heap[l]) > 0) {
+ break;
+ }
+ h->heap[k] = h->heap[l];
+ }
+ h->heap[k] = v;
+#ifdef DEBUG
+ if (!_heap_test(h)) {
+ printf("oops - heap_remove messed up the heap\n");
+ exit(1);
+ }
+#endif
+ return 1;
+}
+
+int
+ImagingQuantHeapAdd(Heap *h, void *val) {
+ int k;
+ if (h->heapcount == h->heapsize - 1) {
+ _heap_grow(h, 0);
+ }
+ k = ++h->heapcount;
+ while (k != 1) {
+ if (h->cf(h, val, h->heap[k / 2]) <= 0) {
+ break;
+ }
+ h->heap[k] = h->heap[k / 2];
+ k >>= 1;
+ }
+ h->heap[k] = val;
+#ifdef DEBUG
+ if (!_heap_test(h)) {
+ printf("oops - heap_add messed up the heap\n");
+ exit(1);
+ }
+#endif
+ return 1;
+}
+
+int
+ImagingQuantHeapTop(Heap *h, void **r) {
+ if (!h->heapcount) {
+ return 0;
+ }
+ *r = h->heap[1];
+ return 1;
+}
+
+Heap *
+ImagingQuantHeapNew(HeapCmpFunc cf) {
+ Heap *h;
+
+ /* malloc check ok, small constant allocation */
+ h = malloc(sizeof(Heap));
+ if (!h) {
+ return NULL;
+ }
+ h->heapsize = INITIAL_SIZE;
+ /* malloc check ok, using calloc for overflow */
+ h->heap = calloc(h->heapsize, sizeof(void *));
+ if (!h->heap) {
+ free(h);
+ return NULL;
+ }
+ h->heapcount = 0;
+ h->cf = cf;
+ return h;
+}
diff --git a/contrib/python/Pillow/py3/libImaging/QuantHeap.h b/contrib/python/Pillow/py3/libImaging/QuantHeap.h
new file mode 100644
index 00000000000..c5286dff2ba
--- /dev/null
+++ b/contrib/python/Pillow/py3/libImaging/QuantHeap.h
@@ -0,0 +1,31 @@
+/*
+ * The Python Imaging Library
+ * $Id$
+ *
+ * image quantizer
+ *
+ * Written by Toby J Sargeant <[email protected]>.
+ *
+ * See the README file for information on usage and redistribution.
+ */
+
+#ifndef __QUANTHEAP_H__
+#define __QUANTHEAP_H__
+
+#include "QuantTypes.h"
+
+typedef struct _Heap Heap;
+
+typedef int (*HeapCmpFunc)(const Heap *, const void *, const void *);
+
+void
+ImagingQuantHeapFree(Heap *);
+int
+ImagingQuantHeapRemove(Heap *, void **);
+int
+ImagingQuantHeapAdd(Heap *, void *);
+int
+ImagingQuantHeapTop(Heap *, void **);
+Heap *ImagingQuantHeapNew(HeapCmpFunc);
+
+#endif // __QUANTHEAP_H__
diff --git a/contrib/python/Pillow/py3/libImaging/QuantOctree.c b/contrib/python/Pillow/py3/libImaging/QuantOctree.c
new file mode 100644
index 00000000000..5e79bce358a
--- /dev/null
+++ b/contrib/python/Pillow/py3/libImaging/QuantOctree.c
@@ -0,0 +1,538 @@
+/* Copyright (c) 2010 Oliver Tonnhofer <[email protected]>, Omniscale
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+*/
+
+/*
+// This file implements a variation of the octree color quantization algorithm.
+*/
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <limits.h>
+
+#include "ImagingUtils.h"
+#include "QuantOctree.h"
+
+typedef struct _ColorBucket {
+ /* contains palette index when used for look up cube */
+ uint32_t count;
+ uint64_t r;
+ uint64_t g;
+ uint64_t b;
+ uint64_t a;
+} * ColorBucket;
+
+typedef struct _ColorCube {
+ unsigned int rBits, gBits, bBits, aBits;
+ unsigned int rWidth, gWidth, bWidth, aWidth;
+ unsigned int rOffset, gOffset, bOffset, aOffset;
+
+ unsigned long size;
+ ColorBucket buckets;
+} * ColorCube;
+
+#define MAX(a, b) (a) > (b) ? (a) : (b)
+
+static ColorCube
+new_color_cube(int r, int g, int b, int a) {
+ ColorCube cube;
+
+ /* malloc check ok, small constant allocation */
+ cube = malloc(sizeof(struct _ColorCube));
+ if (!cube) {
+ return NULL;
+ }
+
+ cube->rBits = MAX(r, 0);
+ cube->gBits = MAX(g, 0);
+ cube->bBits = MAX(b, 0);
+ cube->aBits = MAX(a, 0);
+
+ /* overflow check for size multiplication below */
+ if (cube->rBits + cube->gBits + cube->bBits + cube->aBits > 31) {
+ free(cube);
+ return NULL;
+ }
+
+ /* the width of the cube for each dimension */
+ cube->rWidth = 1 << cube->rBits;
+ cube->gWidth = 1 << cube->gBits;
+ cube->bWidth = 1 << cube->bBits;
+ cube->aWidth = 1 << cube->aBits;
+
+ /* the offsets of each color */
+
+ cube->rOffset = cube->gBits + cube->bBits + cube->aBits;
+ cube->gOffset = cube->bBits + cube->aBits;
+ cube->bOffset = cube->aBits;
+ cube->aOffset = 0;
+
+ /* the number of color buckets */
+ cube->size = cube->rWidth * cube->gWidth * cube->bWidth * cube->aWidth;
+ /* malloc check ok, overflow checked above */
+ cube->buckets = calloc(cube->size, sizeof(struct _ColorBucket));
+
+ if (!cube->buckets) {
+ free(cube);
+ return NULL;
+ }
+ return cube;
+}
+
+static void
+free_color_cube(ColorCube cube) {
+ if (cube != NULL) {
+ free(cube->buckets);
+ free(cube);
+ }
+}
+
+static long
+color_bucket_offset_pos(
+ const ColorCube cube,
+ unsigned int r,
+ unsigned int g,
+ unsigned int b,
+ unsigned int a) {
+ return r << cube->rOffset | g << cube->gOffset | b << cube->bOffset |
+ a << cube->aOffset;
+}
+
+static long
+color_bucket_offset(const ColorCube cube, const Pixel *p) {
+ unsigned int r = p->c.r >> (8 - cube->rBits);
+ unsigned int g = p->c.g >> (8 - cube->gBits);
+ unsigned int b = p->c.b >> (8 - cube->bBits);
+ unsigned int a = p->c.a >> (8 - cube->aBits);
+ return color_bucket_offset_pos(cube, r, g, b, a);
+}
+
+static ColorBucket
+color_bucket_from_cube(const ColorCube cube, const Pixel *p) {
+ unsigned int offset = color_bucket_offset(cube, p);
+ return &cube->buckets[offset];
+}
+
+static void
+add_color_to_color_cube(const ColorCube cube, const Pixel *p) {
+ ColorBucket bucket = color_bucket_from_cube(cube, p);
+ bucket->count += 1;
+ bucket->r += p->c.r;
+ bucket->g += p->c.g;
+ bucket->b += p->c.b;
+ bucket->a += p->c.a;
+}
+
+static unsigned long
+count_used_color_buckets(const ColorCube cube) {
+ unsigned long usedBuckets = 0;
+ unsigned long i;
+ for (i = 0; i < cube->size; i++) {
+ if (cube->buckets[i].count > 0) {
+ usedBuckets += 1;
+ }
+ }
+ return usedBuckets;
+}
+
+static void
+avg_color_from_color_bucket(const ColorBucket bucket, Pixel *dst) {
+ float count = bucket->count;
+ if (count != 0) {
+ dst->c.r = CLIP8((int)(bucket->r / count));
+ dst->c.g = CLIP8((int)(bucket->g / count));
+ dst->c.b = CLIP8((int)(bucket->b / count));
+ dst->c.a = CLIP8((int)(bucket->a / count));
+ } else {
+ dst->c.r = 0;
+ dst->c.g = 0;
+ dst->c.b = 0;
+ dst->c.a = 0;
+ }
+}
+
+static int
+compare_bucket_count(const ColorBucket a, const ColorBucket b) {
+ return b->count - a->count;
+}
+
+static ColorBucket
+create_sorted_color_palette(const ColorCube cube) {
+ ColorBucket buckets;
+ if (cube->size > LONG_MAX / sizeof(struct _ColorBucket)) {
+ return NULL;
+ }
+ /* malloc check ok, calloc + overflow check above for memcpy */
+ buckets = calloc(cube->size, sizeof(struct _ColorBucket));
+ if (!buckets) {
+ return NULL;
+ }
+ memcpy(buckets, cube->buckets, sizeof(struct _ColorBucket) * cube->size);
+
+ qsort(
+ buckets,
+ cube->size,
+ sizeof(struct _ColorBucket),
+ (int (*)(void const *, void const *)) & compare_bucket_count);
+
+ return buckets;
+}
+
+void
+add_bucket_values(ColorBucket src, ColorBucket dst) {
+ dst->count += src->count;
+ dst->r += src->r;
+ dst->g += src->g;
+ dst->b += src->b;
+ dst->a += src->a;
+}
+
+/* expand or shrink a given cube to level */
+static ColorCube
+copy_color_cube(
+ const ColorCube cube,
+ unsigned int rBits,
+ unsigned int gBits,
+ unsigned int bBits,
+ unsigned int aBits) {
+ unsigned int r, g, b, a;
+ long src_pos, dst_pos;
+ unsigned int src_reduce[4] = {0}, dst_reduce[4] = {0};
+ unsigned int width[4];
+ ColorCube result;
+
+ result = new_color_cube(rBits, gBits, bBits, aBits);
+ if (!result) {
+ return NULL;
+ }
+
+ if (cube->rBits > rBits) {
+ dst_reduce[0] = cube->rBits - result->rBits;
+ width[0] = cube->rWidth;
+ } else {
+ src_reduce[0] = result->rBits - cube->rBits;
+ width[0] = result->rWidth;
+ }
+ if (cube->gBits > gBits) {
+ dst_reduce[1] = cube->gBits - result->gBits;
+ width[1] = cube->gWidth;
+ } else {
+ src_reduce[1] = result->gBits - cube->gBits;
+ width[1] = result->gWidth;
+ }
+ if (cube->bBits > bBits) {
+ dst_reduce[2] = cube->bBits - result->bBits;
+ width[2] = cube->bWidth;
+ } else {
+ src_reduce[2] = result->bBits - cube->bBits;
+ width[2] = result->bWidth;
+ }
+ if (cube->aBits > aBits) {
+ dst_reduce[3] = cube->aBits - result->aBits;
+ width[3] = cube->aWidth;
+ } else {
+ src_reduce[3] = result->aBits - cube->aBits;
+ width[3] = result->aWidth;
+ }
+
+ for (r = 0; r < width[0]; r++) {
+ for (g = 0; g < width[1]; g++) {
+ for (b = 0; b < width[2]; b++) {
+ for (a = 0; a < width[3]; a++) {
+ src_pos = color_bucket_offset_pos(
+ cube,
+ r >> src_reduce[0],
+ g >> src_reduce[1],
+ b >> src_reduce[2],
+ a >> src_reduce[3]);
+ dst_pos = color_bucket_offset_pos(
+ result,
+ r >> dst_reduce[0],
+ g >> dst_reduce[1],
+ b >> dst_reduce[2],
+ a >> dst_reduce[3]);
+ add_bucket_values(
+ &cube->buckets[src_pos], &result->buckets[dst_pos]);
+ }
+ }
+ }
+ }
+ return result;
+}
+
+void
+subtract_color_buckets(ColorCube cube, ColorBucket buckets, long nBuckets) {
+ ColorBucket minuend, subtrahend;
+ long i;
+ Pixel p;
+ for (i = 0; i < nBuckets; i++) {
+ subtrahend = &buckets[i];
+
+ // If the subtrahend contains no buckets, there is nothing to subtract.
+ if (subtrahend->count == 0) {
+ continue;
+ }
+
+ avg_color_from_color_bucket(subtrahend, &p);
+ minuend = color_bucket_from_cube(cube, &p);
+ minuend->count -= subtrahend->count;
+ minuend->r -= subtrahend->r;
+ minuend->g -= subtrahend->g;
+ minuend->b -= subtrahend->b;
+ minuend->a -= subtrahend->a;
+ }
+}
+
+static void
+set_lookup_value(const ColorCube cube, const Pixel *p, long value) {
+ ColorBucket bucket = color_bucket_from_cube(cube, p);
+ bucket->count = value;
+}
+
+uint64_t
+lookup_color(const ColorCube cube, const Pixel *p) {
+ ColorBucket bucket = color_bucket_from_cube(cube, p);
+ return bucket->count;
+}
+
+void
+add_lookup_buckets(ColorCube cube, ColorBucket palette, long nColors, long offset) {
+ long i;
+ Pixel p;
+ for (i = offset + nColors - 1; i >= offset; i--) {
+ avg_color_from_color_bucket(&palette[i], &p);
+ set_lookup_value(cube, &p, i);
+ }
+}
+
+ColorBucket
+combined_palette(
+ ColorBucket bucketsA,
+ unsigned long nBucketsA,
+ ColorBucket bucketsB,
+ unsigned long nBucketsB) {
+ ColorBucket result;
+ if (nBucketsA > LONG_MAX - nBucketsB ||
+ (nBucketsA + nBucketsB) > LONG_MAX / sizeof(struct _ColorBucket)) {
+ return NULL;
+ }
+ /* malloc check ok, overflow check above */
+ result = calloc(nBucketsA + nBucketsB, sizeof(struct _ColorBucket));
+ if (!result) {
+ return NULL;
+ }
+ memcpy(result, bucketsA, sizeof(struct _ColorBucket) * nBucketsA);
+ memcpy(&result[nBucketsA], bucketsB, sizeof(struct _ColorBucket) * nBucketsB);
+ return result;
+}
+
+static Pixel *
+create_palette_array(const ColorBucket palette, unsigned int paletteLength) {
+ Pixel *paletteArray;
+ unsigned int i;
+
+ /* malloc check ok, calloc for overflow */
+ paletteArray = calloc(paletteLength, sizeof(Pixel));
+ if (!paletteArray) {
+ return NULL;
+ }
+
+ for (i = 0; i < paletteLength; i++) {
+ avg_color_from_color_bucket(&palette[i], &paletteArray[i]);
+ }
+ return paletteArray;
+}
+
+static void
+map_image_pixels(
+ const Pixel *pixelData,
+ uint32_t nPixels,
+ const ColorCube lookupCube,
+ uint32_t *pixelArray) {
+ long i;
+ for (i = 0; i < nPixels; i++) {
+ pixelArray[i] = lookup_color(lookupCube, &pixelData[i]);
+ }
+}
+
+const unsigned int CUBE_LEVELS[8] = {4, 4, 4, 0, 2, 2, 2, 0};
+const unsigned int CUBE_LEVELS_ALPHA[8] = {3, 4, 3, 3, 2, 2, 2, 2};
+
+int
+quantize_octree(
+ Pixel *pixelData,
+ uint32_t nPixels,
+ uint32_t nQuantPixels,
+ Pixel **palette,
+ uint32_t *paletteLength,
+ uint32_t **quantizedPixels,
+ int withAlpha) {
+ ColorCube fineCube = NULL;
+ ColorCube coarseCube = NULL;
+ ColorCube lookupCube = NULL;
+ ColorCube coarseLookupCube = NULL;
+ ColorBucket paletteBucketsCoarse = NULL;
+ ColorBucket paletteBucketsFine = NULL;
+ ColorBucket paletteBuckets = NULL;
+ uint32_t *qp = NULL;
+ long i;
+ unsigned long nCoarseColors, nFineColors, nAlreadySubtracted;
+ const unsigned int *cubeBits;
+
+ if (withAlpha) {
+ cubeBits = CUBE_LEVELS_ALPHA;
+ } else {
+ cubeBits = CUBE_LEVELS;
+ }
+
+ /*
+ Create two color cubes, one fine grained with 8x16x8=1024
+ colors buckets and a coarse with 4x4x4=64 color buckets.
+ The coarse one guarantees that there are color buckets available for
+ the whole color range (assuming nQuantPixels > 64).
+
+ For a quantization to 256 colors all 64 coarse colors will be used
+ plus the 192 most used color buckets from the fine color cube.
+ The average of all colors within one bucket is used as the actual
+ color for that bucket.
+
+ For images with alpha the cubes gets a forth dimension,
+ 8x16x8x8 and 4x4x4x4.
+ */
+
+ /* create fine cube */
+ fineCube = new_color_cube(cubeBits[0], cubeBits[1], cubeBits[2], cubeBits[3]);
+ if (!fineCube) {
+ goto error;
+ }
+ for (i = 0; i < nPixels; i++) {
+ add_color_to_color_cube(fineCube, &pixelData[i]);
+ }
+
+ /* create coarse cube */
+ coarseCube =
+ copy_color_cube(fineCube, cubeBits[4], cubeBits[5], cubeBits[6], cubeBits[7]);
+ if (!coarseCube) {
+ goto error;
+ }
+ nCoarseColors = count_used_color_buckets(coarseCube);
+
+ /* limit to nQuantPixels */
+ if (nCoarseColors > nQuantPixels) {
+ nCoarseColors = nQuantPixels;
+ }
+
+ /* how many space do we have in our palette for fine colors? */
+ nFineColors = nQuantPixels - nCoarseColors;
+
+ /* create fine color palette */
+ paletteBucketsFine = create_sorted_color_palette(fineCube);
+ if (!paletteBucketsFine) {
+ goto error;
+ }
+
+ /* remove the used fine colors from the coarse cube */
+ subtract_color_buckets(coarseCube, paletteBucketsFine, nFineColors);
+
+ /* did the subtraction cleared one or more coarse bucket? */
+ while (nCoarseColors > count_used_color_buckets(coarseCube)) {
+ /* then we can use the free buckets for fine colors */
+ nAlreadySubtracted = nFineColors;
+ nCoarseColors = count_used_color_buckets(coarseCube);
+ nFineColors = nQuantPixels - nCoarseColors;
+ subtract_color_buckets(
+ coarseCube,
+ &paletteBucketsFine[nAlreadySubtracted],
+ nFineColors - nAlreadySubtracted);
+ }
+
+ /* create our palette buckets with fine and coarse combined */
+ paletteBucketsCoarse = create_sorted_color_palette(coarseCube);
+ if (!paletteBucketsCoarse) {
+ goto error;
+ }
+ paletteBuckets = combined_palette(
+ paletteBucketsCoarse, nCoarseColors, paletteBucketsFine, nFineColors);
+
+ free(paletteBucketsFine);
+ paletteBucketsFine = NULL;
+ free(paletteBucketsCoarse);
+ paletteBucketsCoarse = NULL;
+ if (!paletteBuckets) {
+ goto error;
+ }
+
+ /* add all coarse colors to our coarse lookup cube. */
+ coarseLookupCube =
+ new_color_cube(cubeBits[4], cubeBits[5], cubeBits[6], cubeBits[7]);
+ if (!coarseLookupCube) {
+ goto error;
+ }
+ add_lookup_buckets(coarseLookupCube, paletteBuckets, nCoarseColors, 0);
+
+ /* expand coarse cube (64) to larger fine cube (4k). the value of each
+ coarse bucket is then present in the according 64 fine buckets. */
+ lookupCube = copy_color_cube(
+ coarseLookupCube, cubeBits[0], cubeBits[1], cubeBits[2], cubeBits[3]);
+ if (!lookupCube) {
+ goto error;
+ }
+
+ /* add fine colors to the lookup cube */
+ add_lookup_buckets(lookupCube, paletteBuckets, nFineColors, nCoarseColors);
+
+ /* create result pixels and map palette indices */
+ /* malloc check ok, calloc for overflow */
+ qp = calloc(nPixels, sizeof(Pixel));
+ if (!qp) {
+ goto error;
+ }
+ map_image_pixels(pixelData, nPixels, lookupCube, qp);
+
+ /* convert palette buckets to RGB pixel palette */
+ *palette = create_palette_array(paletteBuckets, nQuantPixels);
+ if (!(*palette)) {
+ goto error;
+ }
+
+ *quantizedPixels = qp;
+ *paletteLength = nQuantPixels;
+
+ free_color_cube(coarseCube);
+ free_color_cube(fineCube);
+ free_color_cube(lookupCube);
+ free_color_cube(coarseLookupCube);
+ free(paletteBuckets);
+ return 1;
+
+error:
+ /* everything is initialized to NULL
+ so we are safe to call free */
+ free(qp);
+ free_color_cube(lookupCube);
+ free_color_cube(coarseLookupCube);
+ free(paletteBuckets);
+ free(paletteBucketsCoarse);
+ free(paletteBucketsFine);
+ free_color_cube(coarseCube);
+ free_color_cube(fineCube);
+ return 0;
+}
diff --git a/contrib/python/Pillow/py3/libImaging/QuantOctree.h b/contrib/python/Pillow/py3/libImaging/QuantOctree.h
new file mode 100644
index 00000000000..e1c50407402
--- /dev/null
+++ b/contrib/python/Pillow/py3/libImaging/QuantOctree.h
@@ -0,0 +1,9 @@
+#ifndef __QUANT_OCTREE_H__
+#define __QUANT_OCTREE_H__
+
+#include "QuantTypes.h"
+
+int
+quantize_octree(Pixel *, uint32_t, uint32_t, Pixel **, uint32_t *, uint32_t **, int);
+
+#endif
diff --git a/contrib/python/Pillow/py3/libImaging/QuantPngQuant.c b/contrib/python/Pillow/py3/libImaging/QuantPngQuant.c
new file mode 100644
index 00000000000..7a36300e408
--- /dev/null
+++ b/contrib/python/Pillow/py3/libImaging/QuantPngQuant.c
@@ -0,0 +1,132 @@
+/*
+ * The Python Imaging Library
+ * $Id$
+ *
+ * quantization using libimagequant, a part of pngquant.
+ *
+ * Copyright (c) 2016 Marcin Kurczewski <[email protected]>
+ *
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "QuantPngQuant.h"
+
+#ifdef HAVE_LIBIMAGEQUANT
+#include "libimagequant.h"
+
+int
+quantize_pngquant(
+ Pixel *pixelData,
+ unsigned int width,
+ unsigned int height,
+ uint32_t quantPixels,
+ Pixel **palette,
+ uint32_t *paletteLength,
+ uint32_t **quantizedPixels,
+ int withAlpha) {
+ int result = 0;
+ liq_image *image = NULL;
+ liq_attr *attr = NULL;
+ liq_result *remap = NULL;
+ unsigned char *charMatrix = NULL;
+ unsigned char **charMatrixRows = NULL;
+ unsigned int i, y;
+ *palette = NULL;
+ *paletteLength = 0;
+ *quantizedPixels = NULL;
+
+ /* configure pngquant */
+ attr = liq_attr_create();
+ if (!attr) {
+ goto err;
+ }
+ if (quantPixels) {
+ liq_set_max_colors(attr, quantPixels);
+ }
+
+ /* prepare input image */
+ image = liq_image_create_rgba(attr, pixelData, width, height, 0.45455 /* gamma */);
+ if (!image) {
+ goto err;
+ }
+
+ /* quantize the image */
+ remap = liq_quantize_image(attr, image);
+ if (!remap) {
+ goto err;
+ }
+ liq_set_output_gamma(remap, 0.45455);
+ liq_set_dithering_level(remap, 1);
+
+ /* write output palette */
+ const liq_palette *l_palette = liq_get_palette(remap);
+ *paletteLength = l_palette->count;
+ *palette = malloc(sizeof(Pixel) * l_palette->count);
+ if (!*palette) {
+ goto err;
+ }
+ for (i = 0; i < l_palette->count; i++) {
+ (*palette)[i].c.b = l_palette->entries[i].b;
+ (*palette)[i].c.g = l_palette->entries[i].g;
+ (*palette)[i].c.r = l_palette->entries[i].r;
+ (*palette)[i].c.a = l_palette->entries[i].a;
+ }
+
+ /* write output pixels (pngquant uses char array) */
+ charMatrix = malloc(width * height);
+ if (!charMatrix) {
+ goto err;
+ }
+ charMatrixRows = malloc(height * sizeof(unsigned char *));
+ if (!charMatrixRows) {
+ goto err;
+ }
+ for (y = 0; y < height; y++) {
+ charMatrixRows[y] = &charMatrix[y * width];
+ }
+ if (LIQ_OK != liq_write_remapped_image_rows(remap, image, charMatrixRows)) {
+ goto err;
+ }
+
+ /* transcribe output pixels (pillow uses uint32_t array) */
+ *quantizedPixels = malloc(sizeof(uint32_t) * width * height);
+ if (!*quantizedPixels) {
+ goto err;
+ }
+ for (i = 0; i < width * height; i++) {
+ (*quantizedPixels)[i] = charMatrix[i];
+ }
+
+ result = 1;
+
+err:
+ if (attr) {
+ liq_attr_destroy(attr);
+ }
+ if (image) {
+ liq_image_destroy(image);
+ }
+ if (remap) {
+ liq_result_destroy(remap);
+ }
+ free(charMatrix);
+ free(charMatrixRows);
+ if (!result) {
+ free(*quantizedPixels);
+ free(*palette);
+ }
+ return result;
+}
+
+const char *
+ImagingImageQuantVersion(void) {
+ static char version[20];
+ int number = liq_version();
+ sprintf(version, "%d.%d.%d", number / 10000, (number / 100) % 100, number % 100);
+ return version;
+}
+
+#endif
diff --git a/contrib/python/Pillow/py3/libImaging/QuantPngQuant.h b/contrib/python/Pillow/py3/libImaging/QuantPngQuant.h
new file mode 100644
index 00000000000..d65e42590ca
--- /dev/null
+++ b/contrib/python/Pillow/py3/libImaging/QuantPngQuant.h
@@ -0,0 +1,17 @@
+#ifndef __QUANT_PNGQUANT_H__
+#define __QUANT_PNGQUANT_H__
+
+#include "QuantTypes.h"
+
+int
+quantize_pngquant(
+ Pixel *,
+ unsigned int,
+ unsigned int,
+ uint32_t,
+ Pixel **,
+ uint32_t *,
+ uint32_t **,
+ int);
+
+#endif
diff --git a/contrib/python/Pillow/py3/libImaging/QuantTypes.h b/contrib/python/Pillow/py3/libImaging/QuantTypes.h
new file mode 100644
index 00000000000..986b70806dc
--- /dev/null
+++ b/contrib/python/Pillow/py3/libImaging/QuantTypes.h
@@ -0,0 +1,32 @@
+/*
+ * The Python Imaging Library
+ * $Id$
+ *
+ * image quantizer
+ *
+ * Written by Toby J Sargeant <[email protected]>.
+ *
+ * See the README file for information on usage and redistribution.
+ */
+
+#ifndef __TYPES_H__
+#define __TYPES_H__
+
+#ifdef _MSC_VER
+typedef unsigned __int32 uint32_t;
+typedef unsigned __int64 uint64_t;
+#else
+#include <stdint.h>
+#endif
+
+typedef union {
+ struct {
+ unsigned char r, g, b, a;
+ } c;
+ struct {
+ unsigned char v[4];
+ } a;
+ uint32_t v;
+} Pixel;
+
+#endif
diff --git a/contrib/python/Pillow/py3/libImaging/RankFilter.c b/contrib/python/Pillow/py3/libImaging/RankFilter.c
new file mode 100644
index 00000000000..73a6baecbb2
--- /dev/null
+++ b/contrib/python/Pillow/py3/libImaging/RankFilter.c
@@ -0,0 +1,132 @@
+/*
+ * The Python Imaging Library
+ * $Id$
+ *
+ * min, max, median filters
+ *
+ * history:
+ * 2002-06-08 fl Created
+ *
+ * Copyright (c) Secret Labs AB 2002. All rights reserved.
+ *
+ * See the README file for information on usage and redistribution.
+ */
+
+#include "Imaging.h"
+
+/* Fast rank algorithm (due to Wirth), based on public domain code
+ by Nicolas Devillard, available at http://ndevilla.free.fr */
+
+#define SWAP(type, a, b) \
+ { \
+ register type t = (a); \
+ (a) = (b); \
+ (b) = t; \
+ }
+
+#define MakeRankFunction(type) \
+ static type Rank##type(type a[], int n, int k) { \
+ register int i, j, l, m; \
+ register type x; \
+ l = 0; \
+ m = n - 1; \
+ while (l < m) { \
+ x = a[k]; \
+ i = l; \
+ j = m; \
+ do { \
+ while (a[i] < x) { \
+ i++; \
+ } \
+ while (x < a[j]) { \
+ j--; \
+ } \
+ if (i <= j) { \
+ SWAP(type, a[i], a[j]); \
+ i++; \
+ j--; \
+ } \
+ } while (i <= j); \
+ if (j < k) { \
+ l = i; \
+ } \
+ if (k < i) { \
+ m = j; \
+ } \
+ } \
+ return a[k]; \
+ }
+
+MakeRankFunction(UINT8) MakeRankFunction(INT32) MakeRankFunction(FLOAT32)
+
+ Imaging ImagingRankFilter(Imaging im, int size, int rank) {
+ Imaging imOut = NULL;
+ int x, y;
+ int i, margin, size2;
+
+ if (!im || im->bands != 1 || im->type == IMAGING_TYPE_SPECIAL) {
+ return (Imaging)ImagingError_ModeError();
+ }
+
+ if (!(size & 1)) {
+ return (Imaging)ImagingError_ValueError("bad filter size");
+ }
+
+ /* malloc check ok, for overflow in the define below */
+ if (size > INT_MAX / size || size > INT_MAX / (size * (int)sizeof(FLOAT32))) {
+ return (Imaging)ImagingError_ValueError("filter size too large");
+ }
+
+ size2 = size * size;
+ margin = (size - 1) / 2;
+
+ if (rank < 0 || rank >= size2) {
+ return (Imaging)ImagingError_ValueError("bad rank value");
+ }
+
+ imOut = ImagingNew(im->mode, im->xsize - 2 * margin, im->ysize - 2 * margin);
+ if (!imOut) {
+ return NULL;
+ }
+
+ /* malloc check ok, checked above */
+#define RANK_BODY(type) \
+ do { \
+ type *buf = malloc(size2 * sizeof(type)); \
+ if (!buf) { \
+ goto nomemory; \
+ } \
+ for (y = 0; y < imOut->ysize; y++) { \
+ for (x = 0; x < imOut->xsize; x++) { \
+ for (i = 0; i < size; i++) { \
+ memcpy( \
+ buf + i * size, \
+ &IMAGING_PIXEL_##type(im, x, y + i), \
+ size * sizeof(type)); \
+ } \
+ IMAGING_PIXEL_##type(imOut, x, y) = Rank##type(buf, size2, rank); \
+ } \
+ } \
+ free(buf); \
+ } while (0)
+
+ if (im->image8) {
+ RANK_BODY(UINT8);
+ } else if (im->type == IMAGING_TYPE_INT32) {
+ RANK_BODY(INT32);
+ } else if (im->type == IMAGING_TYPE_FLOAT32) {
+ RANK_BODY(FLOAT32);
+ } else {
+ /* safety net (we shouldn't end up here) */
+ ImagingDelete(imOut);
+ return (Imaging)ImagingError_ModeError();
+ }
+
+ ImagingCopyPalette(imOut, im);
+
+ return imOut;
+
+nomemory:
+ ImagingDelete(imOut);
+ return (Imaging)ImagingError_MemoryError();
+}
diff --git a/contrib/python/Pillow/py3/libImaging/Raw.h b/contrib/python/Pillow/py3/libImaging/Raw.h
new file mode 100644
index 00000000000..ab718837f4a
--- /dev/null
+++ b/contrib/python/Pillow/py3/libImaging/Raw.h
@@ -0,0 +1,14 @@
+/* Raw.h */
+
+typedef struct {
+ /* CONFIGURATION */
+
+ /* Distance between lines (0=no padding) */
+ int stride;
+
+ /* PRIVATE (initialized by decoder) */
+
+ /* Padding between lines */
+ int skip;
+
+} RAWSTATE;
diff --git a/contrib/python/Pillow/py3/libImaging/RawDecode.c b/contrib/python/Pillow/py3/libImaging/RawDecode.c
new file mode 100644
index 00000000000..24abe48041f
--- /dev/null
+++ b/contrib/python/Pillow/py3/libImaging/RawDecode.c
@@ -0,0 +1,91 @@
+/*
+ * The Python Imaging Library.
+ * $Id$
+ *
+ * decoder for raw (uncompressed) image data
+ *
+ * history:
+ * 96-03-07 fl rewritten
+ *
+ * Copyright (c) Fredrik Lundh 1996.
+ * Copyright (c) Secret Labs AB 1997.
+ *
+ * See the README file for information on usage and redistribution.
+ */
+
+#include "Imaging.h"
+
+#include "Raw.h"
+
+int
+ImagingRawDecode(Imaging im, ImagingCodecState state, UINT8 *buf, Py_ssize_t bytes) {
+ enum { LINE = 1, SKIP };
+ RAWSTATE *rawstate = state->context;
+
+ UINT8 *ptr;
+
+ if (state->state == 0) {
+ /* Initialize context variables */
+
+ /* get size of image data and padding */
+ state->bytes = (state->xsize * state->bits + 7) / 8;
+ if (rawstate->stride) {
+ rawstate->skip = rawstate->stride - state->bytes;
+ if (rawstate->skip < 0) {
+ state->errcode = IMAGING_CODEC_CONFIG;
+ return -1;
+ }
+ } else {
+ rawstate->skip = 0;
+ }
+
+ /* check image orientation */
+ if (state->ystep < 0) {
+ state->y = state->ysize - 1;
+ state->ystep = -1;
+ } else {
+ state->ystep = 1;
+ }
+
+ state->state = LINE;
+ }
+
+ ptr = buf;
+
+ for (;;) {
+ if (state->state == SKIP) {
+ /* Skip padding between lines */
+
+ if (bytes < rawstate->skip) {
+ return ptr - buf;
+ }
+
+ ptr += rawstate->skip;
+ bytes -= rawstate->skip;
+
+ state->state = LINE;
+ }
+
+ if (bytes < state->bytes) {
+ return ptr - buf;
+ }
+
+ /* Unpack data */
+ state->shuffle(
+ (UINT8 *)im->image[state->y + state->yoff] + state->xoff * im->pixelsize,
+ ptr,
+ state->xsize);
+
+ ptr += state->bytes;
+ bytes -= state->bytes;
+
+ state->y += state->ystep;
+
+ if (state->y < 0 || state->y >= state->ysize) {
+ /* End of file (errcode = 0) */
+ return -1;
+ }
+
+ state->state = SKIP;
+ }
+}
diff --git a/contrib/python/Pillow/py3/libImaging/RawEncode.c b/contrib/python/Pillow/py3/libImaging/RawEncode.c
new file mode 100644
index 00000000000..50de8d98275
--- /dev/null
+++ b/contrib/python/Pillow/py3/libImaging/RawEncode.c
@@ -0,0 +1,87 @@
+/*
+ * The Python Imaging Library.
+ * $Id$
+ *
+ * coder for raw data
+ *
+ * FIXME: This encoder will fail if the buffer is not large enough to
+ * hold one full line of data. There's a workaround for this problem
+ * in ImageFile.py, but it should be solved here instead.
+ *
+ * history:
+ * 96-04-30 fl created
+ * 97-01-03 fl fixed padding
+ *
+ * Copyright (c) Fredrik Lundh 1996-97.
+ * Copyright (c) Secret Labs AB 1997.
+ *
+ * See the README file for information on usage and redistribution. */
+
+#include "Imaging.h"
+
+int
+ImagingRawEncode(Imaging im, ImagingCodecState state, UINT8 *buf, int bytes) {
+ UINT8 *ptr;
+
+ if (!state->state) {
+ /* The "count" field holds the stride, if specified. Fix
+ things up so "bytes" is the full size, and "count" the
+ packed size */
+
+ if (state->count > 0) {
+ int bytes = state->count;
+
+ /* stride must not be less than real size */
+ if (state->count < state->bytes) {
+ state->errcode = IMAGING_CODEC_CONFIG;
+ return -1;
+ }
+ state->count = state->bytes;
+ state->bytes = bytes;
+ } else {
+ state->count = state->bytes;
+ }
+
+ /* The "ystep" field specifies the orientation */
+
+ if (state->ystep < 0) {
+ state->y = state->ysize - 1;
+ state->ystep = -1;
+ } else {
+ state->ystep = 1;
+ }
+
+ state->state = 1;
+ }
+
+ if (bytes < state->bytes) {
+ state->errcode = IMAGING_CODEC_CONFIG;
+ return 0;
+ }
+
+ ptr = buf;
+
+ while (bytes >= state->bytes) {
+ state->shuffle(
+ ptr,
+ (UINT8 *)im->image[state->y + state->yoff] + state->xoff * im->pixelsize,
+ state->xsize);
+
+ if (state->bytes > state->count) {
+ /* zero-pad the buffer, if necessary */
+ memset(ptr + state->count, 0, state->bytes - state->count);
+ }
+
+ ptr += state->bytes;
+ bytes -= state->bytes;
+
+ state->y += state->ystep;
+
+ if (state->y < 0 || state->y >= state->ysize) {
+ state->errcode = IMAGING_CODEC_END;
+ break;
+ }
+ }
+
+ return ptr - buf;
+}
diff --git a/contrib/python/Pillow/py3/libImaging/Reduce.c b/contrib/python/Pillow/py3/libImaging/Reduce.c
new file mode 100644
index 00000000000..60928d2bc36
--- /dev/null
+++ b/contrib/python/Pillow/py3/libImaging/Reduce.c
@@ -0,0 +1,1483 @@
+#include "Imaging.h"
+
+#include <math.h>
+
+#define ROUND_UP(f) ((int)((f) >= 0.0 ? (f) + 0.5F : (f)-0.5F))
+
+UINT32
+division_UINT32(int divider, int result_bits) {
+ UINT32 max_dividend = (1 << result_bits) * divider;
+ float max_int = (1 << 30) * 4.0;
+ return (UINT32)(max_int / max_dividend);
+}
+
+void
+ImagingReduceNxN(Imaging imOut, Imaging imIn, int box[4], int xscale, int yscale) {
+ /* The most general implementation for any xscale and yscale
+ */
+ int x, y, xx, yy;
+ UINT32 multiplier = division_UINT32(yscale * xscale, 8);
+ UINT32 amend = yscale * xscale / 2;
+
+ if (imIn->image8) {
+ for (y = 0; y < box[3] / yscale; y++) {
+ int yy_from = box[1] + y * yscale;
+ for (x = 0; x < box[2] / xscale; x++) {
+ int xx_from = box[0] + x * xscale;
+ UINT32 ss = amend;
+ for (yy = yy_from; yy < yy_from + yscale - 1; yy += 2) {
+ UINT8 *line0 = (UINT8 *)imIn->image8[yy];
+ UINT8 *line1 = (UINT8 *)imIn->image8[yy + 1];
+ for (xx = xx_from; xx < xx_from + xscale - 1; xx += 2) {
+ ss += line0[xx + 0] + line0[xx + 1] + line1[xx + 0] +
+ line1[xx + 1];
+ }
+ if (xscale & 0x01) {
+ ss += line0[xx + 0] + line1[xx + 0];
+ }
+ }
+ if (yscale & 0x01) {
+ UINT8 *line = (UINT8 *)imIn->image8[yy];
+ for (xx = xx_from; xx < xx_from + xscale - 1; xx += 2) {
+ ss += line[xx + 0] + line[xx + 1];
+ }
+ if (xscale & 0x01) {
+ ss += line[xx + 0];
+ }
+ }
+ imOut->image8[y][x] = (ss * multiplier) >> 24;
+ }
+ }
+ } else {
+ for (y = 0; y < box[3] / yscale; y++) {
+ int yy_from = box[1] + y * yscale;
+ if (imIn->bands == 2) {
+ for (x = 0; x < box[2] / xscale; x++) {
+ int xx_from = box[0] + x * xscale;
+ UINT32 v;
+ UINT32 ss0 = amend, ss3 = amend;
+ for (yy = yy_from; yy < yy_from + yscale - 1; yy += 2) {
+ UINT8 *line0 = (UINT8 *)imIn->image[yy];
+ UINT8 *line1 = (UINT8 *)imIn->image[yy + 1];
+ for (xx = xx_from; xx < xx_from + xscale - 1; xx += 2) {
+ ss0 += line0[xx * 4 + 0] + line0[xx * 4 + 4] +
+ line1[xx * 4 + 0] + line1[xx * 4 + 4];
+ ss3 += line0[xx * 4 + 3] + line0[xx * 4 + 7] +
+ line1[xx * 4 + 3] + line1[xx * 4 + 7];
+ }
+ if (xscale & 0x01) {
+ ss0 += line0[xx * 4 + 0] + line1[xx * 4 + 0];
+ ss3 += line0[xx * 4 + 3] + line1[xx * 4 + 3];
+ }
+ }
+ if (yscale & 0x01) {
+ UINT8 *line = (UINT8 *)imIn->image[yy];
+ for (xx = xx_from; xx < xx_from + xscale - 1; xx += 2) {
+ ss0 += line[xx * 4 + 0] + line[xx * 4 + 4];
+ ss3 += line[xx * 4 + 3] + line[xx * 4 + 7];
+ }
+ if (xscale & 0x01) {
+ ss0 += line[xx * 4 + 0];
+ ss3 += line[xx * 4 + 3];
+ }
+ }
+ v = MAKE_UINT32(
+ (ss0 * multiplier) >> 24, 0, 0, (ss3 * multiplier) >> 24);
+ memcpy(imOut->image[y] + x * sizeof(v), &v, sizeof(v));
+ }
+ } else if (imIn->bands == 3) {
+ for (x = 0; x < box[2] / xscale; x++) {
+ int xx_from = box[0] + x * xscale;
+ UINT32 v;
+ UINT32 ss0 = amend, ss1 = amend, ss2 = amend;
+ for (yy = yy_from; yy < yy_from + yscale - 1; yy += 2) {
+ UINT8 *line0 = (UINT8 *)imIn->image[yy];
+ UINT8 *line1 = (UINT8 *)imIn->image[yy + 1];
+ for (xx = xx_from; xx < xx_from + xscale - 1; xx += 2) {
+ ss0 += line0[xx * 4 + 0] + line0[xx * 4 + 4] +
+ line1[xx * 4 + 0] + line1[xx * 4 + 4];
+ ss1 += line0[xx * 4 + 1] + line0[xx * 4 + 5] +
+ line1[xx * 4 + 1] + line1[xx * 4 + 5];
+ ss2 += line0[xx * 4 + 2] + line0[xx * 4 + 6] +
+ line1[xx * 4 + 2] + line1[xx * 4 + 6];
+ }
+ if (xscale & 0x01) {
+ ss0 += line0[xx * 4 + 0] + line1[xx * 4 + 0];
+ ss1 += line0[xx * 4 + 1] + line1[xx * 4 + 1];
+ ss2 += line0[xx * 4 + 2] + line1[xx * 4 + 2];
+ }
+ }
+ if (yscale & 0x01) {
+ UINT8 *line = (UINT8 *)imIn->image[yy];
+ for (xx = xx_from; xx < xx_from + xscale - 1; xx += 2) {
+ ss0 += line[xx * 4 + 0] + line[xx * 4 + 4];
+ ss1 += line[xx * 4 + 1] + line[xx * 4 + 5];
+ ss2 += line[xx * 4 + 2] + line[xx * 4 + 6];
+ }
+ if (xscale & 0x01) {
+ ss0 += line[xx * 4 + 0];
+ ss1 += line[xx * 4 + 1];
+ ss2 += line[xx * 4 + 2];
+ }
+ }
+ v = MAKE_UINT32(
+ (ss0 * multiplier) >> 24,
+ (ss1 * multiplier) >> 24,
+ (ss2 * multiplier) >> 24,
+ 0);
+ memcpy(imOut->image[y] + x * sizeof(v), &v, sizeof(v));
+ }
+ } else { // bands == 4
+ for (x = 0; x < box[2] / xscale; x++) {
+ int xx_from = box[0] + x * xscale;
+ UINT32 v;
+ UINT32 ss0 = amend, ss1 = amend, ss2 = amend, ss3 = amend;
+ for (yy = yy_from; yy < yy_from + yscale - 1; yy += 2) {
+ UINT8 *line0 = (UINT8 *)imIn->image[yy];
+ UINT8 *line1 = (UINT8 *)imIn->image[yy + 1];
+ for (xx = xx_from; xx < xx_from + xscale - 1; xx += 2) {
+ ss0 += line0[xx * 4 + 0] + line0[xx * 4 + 4] +
+ line1[xx * 4 + 0] + line1[xx * 4 + 4];
+ ss1 += line0[xx * 4 + 1] + line0[xx * 4 + 5] +
+ line1[xx * 4 + 1] + line1[xx * 4 + 5];
+ ss2 += line0[xx * 4 + 2] + line0[xx * 4 + 6] +
+ line1[xx * 4 + 2] + line1[xx * 4 + 6];
+ ss3 += line0[xx * 4 + 3] + line0[xx * 4 + 7] +
+ line1[xx * 4 + 3] + line1[xx * 4 + 7];
+ }
+ if (xscale & 0x01) {
+ ss0 += line0[xx * 4 + 0] + line1[xx * 4 + 0];
+ ss1 += line0[xx * 4 + 1] + line1[xx * 4 + 1];
+ ss2 += line0[xx * 4 + 2] + line1[xx * 4 + 2];
+ ss3 += line0[xx * 4 + 3] + line1[xx * 4 + 3];
+ }
+ }
+ if (yscale & 0x01) {
+ UINT8 *line = (UINT8 *)imIn->image[yy];
+ for (xx = xx_from; xx < xx_from + xscale - 1; xx += 2) {
+ ss0 += line[xx * 4 + 0] + line[xx * 4 + 4];
+ ss1 += line[xx * 4 + 1] + line[xx * 4 + 5];
+ ss2 += line[xx * 4 + 2] + line[xx * 4 + 6];
+ ss3 += line[xx * 4 + 3] + line[xx * 4 + 7];
+ }
+ if (xscale & 0x01) {
+ ss0 += line[xx * 4 + 0];
+ ss1 += line[xx * 4 + 1];
+ ss2 += line[xx * 4 + 2];
+ ss3 += line[xx * 4 + 3];
+ }
+ }
+ v = MAKE_UINT32(
+ (ss0 * multiplier) >> 24,
+ (ss1 * multiplier) >> 24,
+ (ss2 * multiplier) >> 24,
+ (ss3 * multiplier) >> 24);
+ memcpy(imOut->image[y] + x * sizeof(v), &v, sizeof(v));
+ }
+ }
+ }
+ }
+}
+
+void
+ImagingReduce1xN(Imaging imOut, Imaging imIn, int box[4], int yscale) {
+ /* Optimized implementation for xscale = 1.
+ */
+ int x, y, yy;
+ int xscale = 1;
+ UINT32 multiplier = division_UINT32(yscale * xscale, 8);
+ UINT32 amend = yscale * xscale / 2;
+
+ if (imIn->image8) {
+ for (y = 0; y < box[3] / yscale; y++) {
+ int yy_from = box[1] + y * yscale;
+ for (x = 0; x < box[2] / xscale; x++) {
+ int xx = box[0] + x * xscale;
+ UINT32 ss = amend;
+ for (yy = yy_from; yy < yy_from + yscale - 1; yy += 2) {
+ UINT8 *line0 = (UINT8 *)imIn->image8[yy];
+ UINT8 *line1 = (UINT8 *)imIn->image8[yy + 1];
+ ss += line0[xx + 0] + line1[xx + 0];
+ }
+ if (yscale & 0x01) {
+ UINT8 *line = (UINT8 *)imIn->image8[yy];
+ ss += line[xx + 0];
+ }
+ imOut->image8[y][x] = (ss * multiplier) >> 24;
+ }
+ }
+ } else {
+ for (y = 0; y < box[3] / yscale; y++) {
+ int yy_from = box[1] + y * yscale;
+ if (imIn->bands == 2) {
+ for (x = 0; x < box[2] / xscale; x++) {
+ int xx = box[0] + x * xscale;
+ UINT32 v;
+ UINT32 ss0 = amend, ss3 = amend;
+ for (yy = yy_from; yy < yy_from + yscale - 1; yy += 2) {
+ UINT8 *line0 = (UINT8 *)imIn->image[yy];
+ UINT8 *line1 = (UINT8 *)imIn->image[yy + 1];
+ ss0 += line0[xx * 4 + 0] + line1[xx * 4 + 0];
+ ss3 += line0[xx * 4 + 3] + line1[xx * 4 + 3];
+ }
+ if (yscale & 0x01) {
+ UINT8 *line = (UINT8 *)imIn->image[yy];
+ ss0 += line[xx * 4 + 0];
+ ss3 += line[xx * 4 + 3];
+ }
+ v = MAKE_UINT32(
+ (ss0 * multiplier) >> 24, 0, 0, (ss3 * multiplier) >> 24);
+ memcpy(imOut->image[y] + x * sizeof(v), &v, sizeof(v));
+ }
+ } else if (imIn->bands == 3) {
+ for (x = 0; x < box[2] / xscale; x++) {
+ int xx = box[0] + x * xscale;
+ UINT32 v;
+ UINT32 ss0 = amend, ss1 = amend, ss2 = amend;
+ for (yy = yy_from; yy < yy_from + yscale - 1; yy += 2) {
+ UINT8 *line0 = (UINT8 *)imIn->image[yy];
+ UINT8 *line1 = (UINT8 *)imIn->image[yy + 1];
+ ss0 += line0[xx * 4 + 0] + line1[xx * 4 + 0];
+ ss1 += line0[xx * 4 + 1] + line1[xx * 4 + 1];
+ ss2 += line0[xx * 4 + 2] + line1[xx * 4 + 2];
+ }
+ if (yscale & 0x01) {
+ UINT8 *line = (UINT8 *)imIn->image[yy];
+ ss0 += line[xx * 4 + 0];
+ ss1 += line[xx * 4 + 1];
+ ss2 += line[xx * 4 + 2];
+ }
+ v = MAKE_UINT32(
+ (ss0 * multiplier) >> 24,
+ (ss1 * multiplier) >> 24,
+ (ss2 * multiplier) >> 24,
+ 0);
+ memcpy(imOut->image[y] + x * sizeof(v), &v, sizeof(v));
+ }
+ } else { // bands == 4
+ for (x = 0; x < box[2] / xscale; x++) {
+ int xx = box[0] + x * xscale;
+ UINT32 v;
+ UINT32 ss0 = amend, ss1 = amend, ss2 = amend, ss3 = amend;
+ for (yy = yy_from; yy < yy_from + yscale - 1; yy += 2) {
+ UINT8 *line0 = (UINT8 *)imIn->image[yy];
+ UINT8 *line1 = (UINT8 *)imIn->image[yy + 1];
+ ss0 += line0[xx * 4 + 0] + line1[xx * 4 + 0];
+ ss1 += line0[xx * 4 + 1] + line1[xx * 4 + 1];
+ ss2 += line0[xx * 4 + 2] + line1[xx * 4 + 2];
+ ss3 += line0[xx * 4 + 3] + line1[xx * 4 + 3];
+ }
+ if (yscale & 0x01) {
+ UINT8 *line = (UINT8 *)imIn->image[yy];
+ ss0 += line[xx * 4 + 0];
+ ss1 += line[xx * 4 + 1];
+ ss2 += line[xx * 4 + 2];
+ ss3 += line[xx * 4 + 3];
+ }
+ v = MAKE_UINT32(
+ (ss0 * multiplier) >> 24,
+ (ss1 * multiplier) >> 24,
+ (ss2 * multiplier) >> 24,
+ (ss3 * multiplier) >> 24);
+ memcpy(imOut->image[y] + x * sizeof(v), &v, sizeof(v));
+ }
+ }
+ }
+ }
+}
+
+void
+ImagingReduceNx1(Imaging imOut, Imaging imIn, int box[4], int xscale) {
+ /* Optimized implementation for yscale = 1.
+ */
+ int x, y, xx;
+ int yscale = 1;
+ UINT32 multiplier = division_UINT32(yscale * xscale, 8);
+ UINT32 amend = yscale * xscale / 2;
+
+ if (imIn->image8) {
+ for (y = 0; y < box[3] / yscale; y++) {
+ int yy = box[1] + y * yscale;
+ UINT8 *line = (UINT8 *)imIn->image8[yy];
+ for (x = 0; x < box[2] / xscale; x++) {
+ int xx_from = box[0] + x * xscale;
+ UINT32 ss = amend;
+ for (xx = xx_from; xx < xx_from + xscale - 1; xx += 2) {
+ ss += line[xx + 0] + line[xx + 1];
+ }
+ if (xscale & 0x01) {
+ ss += line[xx + 0];
+ }
+ imOut->image8[y][x] = (ss * multiplier) >> 24;
+ }
+ }
+ } else {
+ for (y = 0; y < box[3] / yscale; y++) {
+ int yy = box[1] + y * yscale;
+ UINT8 *line = (UINT8 *)imIn->image[yy];
+ if (imIn->bands == 2) {
+ for (x = 0; x < box[2] / xscale; x++) {
+ int xx_from = box[0] + x * xscale;
+ UINT32 v;
+ UINT32 ss0 = amend, ss3 = amend;
+ for (xx = xx_from; xx < xx_from + xscale - 1; xx += 2) {
+ ss0 += line[xx * 4 + 0] + line[xx * 4 + 4];
+ ss3 += line[xx * 4 + 3] + line[xx * 4 + 7];
+ }
+ if (xscale & 0x01) {
+ ss0 += line[xx * 4 + 0];
+ ss3 += line[xx * 4 + 3];
+ }
+ v = MAKE_UINT32(
+ (ss0 * multiplier) >> 24, 0, 0, (ss3 * multiplier) >> 24);
+ memcpy(imOut->image[y] + x * sizeof(v), &v, sizeof(v));
+ }
+ } else if (imIn->bands == 3) {
+ for (x = 0; x < box[2] / xscale; x++) {
+ int xx_from = box[0] + x * xscale;
+ UINT32 v;
+ UINT32 ss0 = amend, ss1 = amend, ss2 = amend;
+ for (xx = xx_from; xx < xx_from + xscale - 1; xx += 2) {
+ ss0 += line[xx * 4 + 0] + line[xx * 4 + 4];
+ ss1 += line[xx * 4 + 1] + line[xx * 4 + 5];
+ ss2 += line[xx * 4 + 2] + line[xx * 4 + 6];
+ }
+ if (xscale & 0x01) {
+ ss0 += line[xx * 4 + 0];
+ ss1 += line[xx * 4 + 1];
+ ss2 += line[xx * 4 + 2];
+ }
+ v = MAKE_UINT32(
+ (ss0 * multiplier) >> 24,
+ (ss1 * multiplier) >> 24,
+ (ss2 * multiplier) >> 24,
+ 0);
+ memcpy(imOut->image[y] + x * sizeof(v), &v, sizeof(v));
+ }
+ } else { // bands == 4
+ for (x = 0; x < box[2] / xscale; x++) {
+ int xx_from = box[0] + x * xscale;
+ UINT32 v;
+ UINT32 ss0 = amend, ss1 = amend, ss2 = amend, ss3 = amend;
+ for (xx = xx_from; xx < xx_from + xscale - 1; xx += 2) {
+ ss0 += line[xx * 4 + 0] + line[xx * 4 + 4];
+ ss1 += line[xx * 4 + 1] + line[xx * 4 + 5];
+ ss2 += line[xx * 4 + 2] + line[xx * 4 + 6];
+ ss3 += line[xx * 4 + 3] + line[xx * 4 + 7];
+ }
+ if (xscale & 0x01) {
+ ss0 += line[xx * 4 + 0];
+ ss1 += line[xx * 4 + 1];
+ ss2 += line[xx * 4 + 2];
+ ss3 += line[xx * 4 + 3];
+ }
+ v = MAKE_UINT32(
+ (ss0 * multiplier) >> 24,
+ (ss1 * multiplier) >> 24,
+ (ss2 * multiplier) >> 24,
+ (ss3 * multiplier) >> 24);
+ memcpy(imOut->image[y] + x * sizeof(v), &v, sizeof(v));
+ }
+ }
+ }
+ }
+}
+
+void
+ImagingReduce1x2(Imaging imOut, Imaging imIn, int box[4]) {
+ /* Optimized implementation for xscale = 1 and yscale = 2.
+ */
+ int xscale = 1, yscale = 2;
+ int x, y;
+ UINT32 ss0, ss1, ss2, ss3;
+ UINT32 amend = yscale * xscale / 2;
+
+ if (imIn->image8) {
+ for (y = 0; y < box[3] / yscale; y++) {
+ int yy = box[1] + y * yscale;
+ UINT8 *line0 = (UINT8 *)imIn->image8[yy + 0];
+ UINT8 *line1 = (UINT8 *)imIn->image8[yy + 1];
+ for (x = 0; x < box[2] / xscale; x++) {
+ int xx = box[0] + x * xscale;
+ ss0 = line0[xx + 0] + line1[xx + 0];
+ imOut->image8[y][x] = (ss0 + amend) >> 1;
+ }
+ }
+ } else {
+ for (y = 0; y < box[3] / yscale; y++) {
+ int yy = box[1] + y * yscale;
+ UINT8 *line0 = (UINT8 *)imIn->image[yy + 0];
+ UINT8 *line1 = (UINT8 *)imIn->image[yy + 1];
+ if (imIn->bands == 2) {
+ for (x = 0; x < box[2] / xscale; x++) {
+ int xx = box[0] + x * xscale;
+ UINT32 v;
+ ss0 = line0[xx * 4 + 0] + line1[xx * 4 + 0];
+ ss3 = line0[xx * 4 + 3] + line1[xx * 4 + 3];
+ v = MAKE_UINT32((ss0 + amend) >> 1, 0, 0, (ss3 + amend) >> 1);
+ memcpy(imOut->image[y] + x * sizeof(v), &v, sizeof(v));
+ }
+ } else if (imIn->bands == 3) {
+ for (x = 0; x < box[2] / xscale; x++) {
+ int xx = box[0] + x * xscale;
+ UINT32 v;
+ ss0 = line0[xx * 4 + 0] + line1[xx * 4 + 0];
+ ss1 = line0[xx * 4 + 1] + line1[xx * 4 + 1];
+ ss2 = line0[xx * 4 + 2] + line1[xx * 4 + 2];
+ v = MAKE_UINT32(
+ (ss0 + amend) >> 1, (ss1 + amend) >> 1, (ss2 + amend) >> 1, 0);
+ memcpy(imOut->image[y] + x * sizeof(v), &v, sizeof(v));
+ }
+ } else { // bands == 4
+ for (x = 0; x < box[2] / xscale; x++) {
+ int xx = box[0] + x * xscale;
+ UINT32 v;
+ ss0 = line0[xx * 4 + 0] + line1[xx * 4 + 0];
+ ss1 = line0[xx * 4 + 1] + line1[xx * 4 + 1];
+ ss2 = line0[xx * 4 + 2] + line1[xx * 4 + 2];
+ ss3 = line0[xx * 4 + 3] + line1[xx * 4 + 3];
+ v = MAKE_UINT32(
+ (ss0 + amend) >> 1,
+ (ss1 + amend) >> 1,
+ (ss2 + amend) >> 1,
+ (ss3 + amend) >> 1);
+ memcpy(imOut->image[y] + x * sizeof(v), &v, sizeof(v));
+ }
+ }
+ }
+ }
+}
+
+void
+ImagingReduce2x1(Imaging imOut, Imaging imIn, int box[4]) {
+ /* Optimized implementation for xscale = 2 and yscale = 1.
+ */
+ int xscale = 2, yscale = 1;
+ int x, y;
+ UINT32 ss0, ss1, ss2, ss3;
+ UINT32 amend = yscale * xscale / 2;
+
+ if (imIn->image8) {
+ for (y = 0; y < box[3] / yscale; y++) {
+ int yy = box[1] + y * yscale;
+ UINT8 *line0 = (UINT8 *)imIn->image8[yy + 0];
+ for (x = 0; x < box[2] / xscale; x++) {
+ int xx = box[0] + x * xscale;
+ ss0 = line0[xx + 0] + line0[xx + 1];
+ imOut->image8[y][x] = (ss0 + amend) >> 1;
+ }
+ }
+ } else {
+ for (y = 0; y < box[3] / yscale; y++) {
+ int yy = box[1] + y * yscale;
+ UINT8 *line0 = (UINT8 *)imIn->image[yy + 0];
+ if (imIn->bands == 2) {
+ for (x = 0; x < box[2] / xscale; x++) {
+ int xx = box[0] + x * xscale;
+ UINT32 v;
+ ss0 = line0[xx * 4 + 0] + line0[xx * 4 + 4];
+ ss3 = line0[xx * 4 + 3] + line0[xx * 4 + 7];
+ v = MAKE_UINT32((ss0 + amend) >> 1, 0, 0, (ss3 + amend) >> 1);
+ memcpy(imOut->image[y] + x * sizeof(v), &v, sizeof(v));
+ }
+ } else if (imIn->bands == 3) {
+ for (x = 0; x < box[2] / xscale; x++) {
+ int xx = box[0] + x * xscale;
+ UINT32 v;
+ ss0 = line0[xx * 4 + 0] + line0[xx * 4 + 4];
+ ss1 = line0[xx * 4 + 1] + line0[xx * 4 + 5];
+ ss2 = line0[xx * 4 + 2] + line0[xx * 4 + 6];
+ v = MAKE_UINT32(
+ (ss0 + amend) >> 1, (ss1 + amend) >> 1, (ss2 + amend) >> 1, 0);
+ memcpy(imOut->image[y] + x * sizeof(v), &v, sizeof(v));
+ }
+ } else { // bands == 4
+ for (x = 0; x < box[2] / xscale; x++) {
+ int xx = box[0] + x * xscale;
+ UINT32 v;
+ ss0 = line0[xx * 4 + 0] + line0[xx * 4 + 4];
+ ss1 = line0[xx * 4 + 1] + line0[xx * 4 + 5];
+ ss2 = line0[xx * 4 + 2] + line0[xx * 4 + 6];
+ ss3 = line0[xx * 4 + 3] + line0[xx * 4 + 7];
+ v = MAKE_UINT32(
+ (ss0 + amend) >> 1,
+ (ss1 + amend) >> 1,
+ (ss2 + amend) >> 1,
+ (ss3 + amend) >> 1);
+ memcpy(imOut->image[y] + x * sizeof(v), &v, sizeof(v));
+ }
+ }
+ }
+ }
+}
+
+void
+ImagingReduce2x2(Imaging imOut, Imaging imIn, int box[4]) {
+ /* Optimized implementation for xscale = 2 and yscale = 2.
+ */
+ int xscale = 2, yscale = 2;
+ int x, y;
+ UINT32 ss0, ss1, ss2, ss3;
+ UINT32 amend = yscale * xscale / 2;
+
+ if (imIn->image8) {
+ for (y = 0; y < box[3] / yscale; y++) {
+ int yy = box[1] + y * yscale;
+ UINT8 *line0 = (UINT8 *)imIn->image8[yy + 0];
+ UINT8 *line1 = (UINT8 *)imIn->image8[yy + 1];
+ for (x = 0; x < box[2] / xscale; x++) {
+ int xx = box[0] + x * xscale;
+ ss0 = line0[xx + 0] + line0[xx + 1] + line1[xx + 0] + line1[xx + 1];
+ imOut->image8[y][x] = (ss0 + amend) >> 2;
+ }
+ }
+ } else {
+ for (y = 0; y < box[3] / yscale; y++) {
+ int yy = box[1] + y * yscale;
+ UINT8 *line0 = (UINT8 *)imIn->image[yy + 0];
+ UINT8 *line1 = (UINT8 *)imIn->image[yy + 1];
+ if (imIn->bands == 2) {
+ for (x = 0; x < box[2] / xscale; x++) {
+ int xx = box[0] + x * xscale;
+ UINT32 v;
+ ss0 = line0[xx * 4 + 0] + line0[xx * 4 + 4] + line1[xx * 4 + 0] +
+ line1[xx * 4 + 4];
+ ss3 = line0[xx * 4 + 3] + line0[xx * 4 + 7] + line1[xx * 4 + 3] +
+ line1[xx * 4 + 7];
+ v = MAKE_UINT32((ss0 + amend) >> 2, 0, 0, (ss3 + amend) >> 2);
+ memcpy(imOut->image[y] + x * sizeof(v), &v, sizeof(v));
+ }
+ } else if (imIn->bands == 3) {
+ for (x = 0; x < box[2] / xscale; x++) {
+ int xx = box[0] + x * xscale;
+ UINT32 v;
+ ss0 = line0[xx * 4 + 0] + line0[xx * 4 + 4] + line1[xx * 4 + 0] +
+ line1[xx * 4 + 4];
+ ss1 = line0[xx * 4 + 1] + line0[xx * 4 + 5] + line1[xx * 4 + 1] +
+ line1[xx * 4 + 5];
+ ss2 = line0[xx * 4 + 2] + line0[xx * 4 + 6] + line1[xx * 4 + 2] +
+ line1[xx * 4 + 6];
+ v = MAKE_UINT32(
+ (ss0 + amend) >> 2, (ss1 + amend) >> 2, (ss2 + amend) >> 2, 0);
+ memcpy(imOut->image[y] + x * sizeof(v), &v, sizeof(v));
+ }
+ } else { // bands == 4
+ for (x = 0; x < box[2] / xscale; x++) {
+ int xx = box[0] + x * xscale;
+ UINT32 v;
+ ss0 = line0[xx * 4 + 0] + line0[xx * 4 + 4] + line1[xx * 4 + 0] +
+ line1[xx * 4 + 4];
+ ss1 = line0[xx * 4 + 1] + line0[xx * 4 + 5] + line1[xx * 4 + 1] +
+ line1[xx * 4 + 5];
+ ss2 = line0[xx * 4 + 2] + line0[xx * 4 + 6] + line1[xx * 4 + 2] +
+ line1[xx * 4 + 6];
+ ss3 = line0[xx * 4 + 3] + line0[xx * 4 + 7] + line1[xx * 4 + 3] +
+ line1[xx * 4 + 7];
+ v = MAKE_UINT32(
+ (ss0 + amend) >> 2,
+ (ss1 + amend) >> 2,
+ (ss2 + amend) >> 2,
+ (ss3 + amend) >> 2);
+ memcpy(imOut->image[y] + x * sizeof(v), &v, sizeof(v));
+ }
+ }
+ }
+ }
+}
+
+void
+ImagingReduce1x3(Imaging imOut, Imaging imIn, int box[4]) {
+ /* Optimized implementation for xscale = 1 and yscale = 3.
+ */
+ int xscale = 1, yscale = 3;
+ int x, y;
+ UINT32 ss0, ss1, ss2, ss3;
+ UINT32 multiplier = division_UINT32(yscale * xscale, 8);
+ UINT32 amend = yscale * xscale / 2;
+
+ if (imIn->image8) {
+ for (y = 0; y < box[3] / yscale; y++) {
+ int yy = box[1] + y * yscale;
+ UINT8 *line0 = (UINT8 *)imIn->image8[yy + 0];
+ UINT8 *line1 = (UINT8 *)imIn->image8[yy + 1];
+ UINT8 *line2 = (UINT8 *)imIn->image8[yy + 2];
+ for (x = 0; x < box[2] / xscale; x++) {
+ int xx = box[0] + x * xscale;
+ ss0 = line0[xx + 0] + line1[xx + 0] + line2[xx + 0];
+ imOut->image8[y][x] = ((ss0 + amend) * multiplier) >> 24;
+ }
+ }
+ } else {
+ for (y = 0; y < box[3] / yscale; y++) {
+ int yy = box[1] + y * yscale;
+ UINT8 *line0 = (UINT8 *)imIn->image[yy + 0];
+ UINT8 *line1 = (UINT8 *)imIn->image[yy + 1];
+ UINT8 *line2 = (UINT8 *)imIn->image[yy + 2];
+ if (imIn->bands == 2) {
+ for (x = 0; x < box[2] / xscale; x++) {
+ int xx = box[0] + x * xscale;
+ UINT32 v;
+ ss0 = line0[xx * 4 + 0] + line1[xx * 4 + 0] + line2[xx * 4 + 0];
+ ss3 = line0[xx * 4 + 3] + line1[xx * 4 + 3] + line2[xx * 4 + 3];
+ v = MAKE_UINT32(
+ ((ss0 + amend) * multiplier) >> 24,
+ 0,
+ 0,
+ ((ss3 + amend) * multiplier) >> 24);
+ memcpy(imOut->image[y] + x * sizeof(v), &v, sizeof(v));
+ }
+ } else if (imIn->bands == 3) {
+ for (x = 0; x < box[2] / xscale; x++) {
+ int xx = box[0] + x * xscale;
+ UINT32 v;
+ ss0 = line0[xx * 4 + 0] + line1[xx * 4 + 0] + line2[xx * 4 + 0];
+ ss1 = line0[xx * 4 + 1] + line1[xx * 4 + 1] + line2[xx * 4 + 1];
+ ss2 = line0[xx * 4 + 2] + line1[xx * 4 + 2] + line2[xx * 4 + 2];
+ v = MAKE_UINT32(
+ ((ss0 + amend) * multiplier) >> 24,
+ ((ss1 + amend) * multiplier) >> 24,
+ ((ss2 + amend) * multiplier) >> 24,
+ 0);
+ memcpy(imOut->image[y] + x * sizeof(v), &v, sizeof(v));
+ }
+ } else { // bands == 4
+ for (x = 0; x < box[2] / xscale; x++) {
+ int xx = box[0] + x * xscale;
+ UINT32 v;
+ ss0 = line0[xx * 4 + 0] + line1[xx * 4 + 0] + line2[xx * 4 + 0];
+ ss1 = line0[xx * 4 + 1] + line1[xx * 4 + 1] + line2[xx * 4 + 1];
+ ss2 = line0[xx * 4 + 2] + line1[xx * 4 + 2] + line2[xx * 4 + 2];
+ ss3 = line0[xx * 4 + 3] + line1[xx * 4 + 3] + line2[xx * 4 + 3];
+ v = MAKE_UINT32(
+ ((ss0 + amend) * multiplier) >> 24,
+ ((ss1 + amend) * multiplier) >> 24,
+ ((ss2 + amend) * multiplier) >> 24,
+ ((ss3 + amend) * multiplier) >> 24);
+ memcpy(imOut->image[y] + x * sizeof(v), &v, sizeof(v));
+ }
+ }
+ }
+ }
+}
+
+void
+ImagingReduce3x1(Imaging imOut, Imaging imIn, int box[4]) {
+ /* Optimized implementation for xscale = 3 and yscale = 1.
+ */
+ int xscale = 3, yscale = 1;
+ int x, y;
+ UINT32 ss0, ss1, ss2, ss3;
+ UINT32 multiplier = division_UINT32(yscale * xscale, 8);
+ UINT32 amend = yscale * xscale / 2;
+
+ if (imIn->image8) {
+ for (y = 0; y < box[3] / yscale; y++) {
+ int yy = box[1] + y * yscale;
+ UINT8 *line0 = (UINT8 *)imIn->image8[yy + 0];
+ for (x = 0; x < box[2] / xscale; x++) {
+ int xx = box[0] + x * xscale;
+ ss0 = line0[xx + 0] + line0[xx + 1] + line0[xx + 2];
+ imOut->image8[y][x] = ((ss0 + amend) * multiplier) >> 24;
+ }
+ }
+ } else {
+ for (y = 0; y < box[3] / yscale; y++) {
+ int yy = box[1] + y * yscale;
+ UINT8 *line0 = (UINT8 *)imIn->image[yy + 0];
+ if (imIn->bands == 2) {
+ for (x = 0; x < box[2] / xscale; x++) {
+ int xx = box[0] + x * xscale;
+ UINT32 v;
+ ss0 = line0[xx * 4 + 0] + line0[xx * 4 + 4] + line0[xx * 4 + 8];
+ ss3 = line0[xx * 4 + 3] + line0[xx * 4 + 7] + line0[xx * 4 + 11];
+ v = MAKE_UINT32(
+ ((ss0 + amend) * multiplier) >> 24,
+ 0,
+ 0,
+ ((ss3 + amend) * multiplier) >> 24);
+ memcpy(imOut->image[y] + x * sizeof(v), &v, sizeof(v));
+ }
+ } else if (imIn->bands == 3) {
+ for (x = 0; x < box[2] / xscale; x++) {
+ int xx = box[0] + x * xscale;
+ UINT32 v;
+ ss0 = line0[xx * 4 + 0] + line0[xx * 4 + 4] + line0[xx * 4 + 8];
+ ss1 = line0[xx * 4 + 1] + line0[xx * 4 + 5] + line0[xx * 4 + 9];
+ ss2 = line0[xx * 4 + 2] + line0[xx * 4 + 6] + line0[xx * 4 + 10];
+ v = MAKE_UINT32(
+ ((ss0 + amend) * multiplier) >> 24,
+ ((ss1 + amend) * multiplier) >> 24,
+ ((ss2 + amend) * multiplier) >> 24,
+ 0);
+ memcpy(imOut->image[y] + x * sizeof(v), &v, sizeof(v));
+ }
+ } else { // bands == 4
+ for (x = 0; x < box[2] / xscale; x++) {
+ int xx = box[0] + x * xscale;
+ UINT32 v;
+ ss0 = line0[xx * 4 + 0] + line0[xx * 4 + 4] + line0[xx * 4 + 8];
+ ss1 = line0[xx * 4 + 1] + line0[xx * 4 + 5] + line0[xx * 4 + 9];
+ ss2 = line0[xx * 4 + 2] + line0[xx * 4 + 6] + line0[xx * 4 + 10];
+ ss3 = line0[xx * 4 + 3] + line0[xx * 4 + 7] + line0[xx * 4 + 11];
+ v = MAKE_UINT32(
+ ((ss0 + amend) * multiplier) >> 24,
+ ((ss1 + amend) * multiplier) >> 24,
+ ((ss2 + amend) * multiplier) >> 24,
+ ((ss3 + amend) * multiplier) >> 24);
+ memcpy(imOut->image[y] + x * sizeof(v), &v, sizeof(v));
+ }
+ }
+ }
+ }
+}
+
+void
+ImagingReduce3x3(Imaging imOut, Imaging imIn, int box[4]) {
+ /* Optimized implementation for xscale = 3 and yscale = 3.
+ */
+ int xscale = 3, yscale = 3;
+ int x, y;
+ UINT32 ss0, ss1, ss2, ss3;
+ UINT32 multiplier = division_UINT32(yscale * xscale, 8);
+ UINT32 amend = yscale * xscale / 2;
+
+ if (imIn->image8) {
+ for (y = 0; y < box[3] / yscale; y++) {
+ int yy = box[1] + y * yscale;
+ UINT8 *line0 = (UINT8 *)imIn->image8[yy + 0];
+ UINT8 *line1 = (UINT8 *)imIn->image8[yy + 1];
+ UINT8 *line2 = (UINT8 *)imIn->image8[yy + 2];
+ for (x = 0; x < box[2] / xscale; x++) {
+ int xx = box[0] + x * xscale;
+ ss0 = line0[xx + 0] + line0[xx + 1] + line0[xx + 2] + line1[xx + 0] +
+ line1[xx + 1] + line1[xx + 2] + line2[xx + 0] + line2[xx + 1] +
+ line2[xx + 2];
+ imOut->image8[y][x] = ((ss0 + amend) * multiplier) >> 24;
+ }
+ }
+ } else {
+ for (y = 0; y < box[3] / yscale; y++) {
+ int yy = box[1] + y * yscale;
+ UINT8 *line0 = (UINT8 *)imIn->image[yy + 0];
+ UINT8 *line1 = (UINT8 *)imIn->image[yy + 1];
+ UINT8 *line2 = (UINT8 *)imIn->image[yy + 2];
+ if (imIn->bands == 2) {
+ for (x = 0; x < box[2] / xscale; x++) {
+ int xx = box[0] + x * xscale;
+ UINT32 v;
+ ss0 = line0[xx * 4 + 0] + line0[xx * 4 + 4] + line0[xx * 4 + 8] +
+ line1[xx * 4 + 0] + line1[xx * 4 + 4] + line1[xx * 4 + 8] +
+ line2[xx * 4 + 0] + line2[xx * 4 + 4] + line2[xx * 4 + 8];
+ ss3 = line0[xx * 4 + 3] + line0[xx * 4 + 7] + line0[xx * 4 + 11] +
+ line1[xx * 4 + 3] + line1[xx * 4 + 7] + line1[xx * 4 + 11] +
+ line2[xx * 4 + 3] + line2[xx * 4 + 7] + line2[xx * 4 + 11];
+ v = MAKE_UINT32(
+ ((ss0 + amend) * multiplier) >> 24,
+ 0,
+ 0,
+ ((ss3 + amend) * multiplier) >> 24);
+ memcpy(imOut->image[y] + x * sizeof(v), &v, sizeof(v));
+ }
+ } else if (imIn->bands == 3) {
+ for (x = 0; x < box[2] / xscale; x++) {
+ int xx = box[0] + x * xscale;
+ UINT32 v;
+ ss0 = line0[xx * 4 + 0] + line0[xx * 4 + 4] + line0[xx * 4 + 8] +
+ line1[xx * 4 + 0] + line1[xx * 4 + 4] + line1[xx * 4 + 8] +
+ line2[xx * 4 + 0] + line2[xx * 4 + 4] + line2[xx * 4 + 8];
+ ss1 = line0[xx * 4 + 1] + line0[xx * 4 + 5] + line0[xx * 4 + 9] +
+ line1[xx * 4 + 1] + line1[xx * 4 + 5] + line1[xx * 4 + 9] +
+ line2[xx * 4 + 1] + line2[xx * 4 + 5] + line2[xx * 4 + 9];
+ ss2 = line0[xx * 4 + 2] + line0[xx * 4 + 6] + line0[xx * 4 + 10] +
+ line1[xx * 4 + 2] + line1[xx * 4 + 6] + line1[xx * 4 + 10] +
+ line2[xx * 4 + 2] + line2[xx * 4 + 6] + line2[xx * 4 + 10];
+ v = MAKE_UINT32(
+ ((ss0 + amend) * multiplier) >> 24,
+ ((ss1 + amend) * multiplier) >> 24,
+ ((ss2 + amend) * multiplier) >> 24,
+ 0);
+ memcpy(imOut->image[y] + x * sizeof(v), &v, sizeof(v));
+ }
+ } else { // bands == 4
+ for (x = 0; x < box[2] / xscale; x++) {
+ int xx = box[0] + x * xscale;
+ UINT32 v;
+ ss0 = line0[xx * 4 + 0] + line0[xx * 4 + 4] + line0[xx * 4 + 8] +
+ line1[xx * 4 + 0] + line1[xx * 4 + 4] + line1[xx * 4 + 8] +
+ line2[xx * 4 + 0] + line2[xx * 4 + 4] + line2[xx * 4 + 8];
+ ss1 = line0[xx * 4 + 1] + line0[xx * 4 + 5] + line0[xx * 4 + 9] +
+ line1[xx * 4 + 1] + line1[xx * 4 + 5] + line1[xx * 4 + 9] +
+ line2[xx * 4 + 1] + line2[xx * 4 + 5] + line2[xx * 4 + 9];
+ ss2 = line0[xx * 4 + 2] + line0[xx * 4 + 6] + line0[xx * 4 + 10] +
+ line1[xx * 4 + 2] + line1[xx * 4 + 6] + line1[xx * 4 + 10] +
+ line2[xx * 4 + 2] + line2[xx * 4 + 6] + line2[xx * 4 + 10];
+ ss3 = line0[xx * 4 + 3] + line0[xx * 4 + 7] + line0[xx * 4 + 11] +
+ line1[xx * 4 + 3] + line1[xx * 4 + 7] + line1[xx * 4 + 11] +
+ line2[xx * 4 + 3] + line2[xx * 4 + 7] + line2[xx * 4 + 11];
+ v = MAKE_UINT32(
+ ((ss0 + amend) * multiplier) >> 24,
+ ((ss1 + amend) * multiplier) >> 24,
+ ((ss2 + amend) * multiplier) >> 24,
+ ((ss3 + amend) * multiplier) >> 24);
+ memcpy(imOut->image[y] + x * sizeof(v), &v, sizeof(v));
+ }
+ }
+ }
+ }
+}
+
+void
+ImagingReduce4x4(Imaging imOut, Imaging imIn, int box[4]) {
+ /* Optimized implementation for xscale = 4 and yscale = 4.
+ */
+ int xscale = 4, yscale = 4;
+ int x, y;
+ UINT32 ss0, ss1, ss2, ss3;
+ UINT32 amend = yscale * xscale / 2;
+
+ if (imIn->image8) {
+ for (y = 0; y < box[3] / yscale; y++) {
+ int yy = box[1] + y * yscale;
+ UINT8 *line0 = (UINT8 *)imIn->image8[yy + 0];
+ UINT8 *line1 = (UINT8 *)imIn->image8[yy + 1];
+ UINT8 *line2 = (UINT8 *)imIn->image8[yy + 2];
+ UINT8 *line3 = (UINT8 *)imIn->image8[yy + 3];
+ for (x = 0; x < box[2] / xscale; x++) {
+ int xx = box[0] + x * xscale;
+ ss0 = line0[xx + 0] + line0[xx + 1] + line0[xx + 2] + line0[xx + 3] +
+ line1[xx + 0] + line1[xx + 1] + line1[xx + 2] + line1[xx + 3] +
+ line2[xx + 0] + line2[xx + 1] + line2[xx + 2] + line2[xx + 3] +
+ line3[xx + 0] + line3[xx + 1] + line3[xx + 2] + line3[xx + 3];
+ imOut->image8[y][x] = (ss0 + amend) >> 4;
+ }
+ }
+ } else {
+ for (y = 0; y < box[3] / yscale; y++) {
+ int yy = box[1] + y * yscale;
+ UINT8 *line0 = (UINT8 *)imIn->image[yy + 0];
+ UINT8 *line1 = (UINT8 *)imIn->image[yy + 1];
+ UINT8 *line2 = (UINT8 *)imIn->image[yy + 2];
+ UINT8 *line3 = (UINT8 *)imIn->image[yy + 3];
+ if (imIn->bands == 2) {
+ for (x = 0; x < box[2] / xscale; x++) {
+ int xx = box[0] + x * xscale;
+ UINT32 v;
+ ss0 = line0[xx * 4 + 0] + line0[xx * 4 + 4] + line0[xx * 4 + 8] +
+ line0[xx * 4 + 12] + line1[xx * 4 + 0] + line1[xx * 4 + 4] +
+ line1[xx * 4 + 8] + line1[xx * 4 + 12] + line2[xx * 4 + 0] +
+ line2[xx * 4 + 4] + line2[xx * 4 + 8] + line2[xx * 4 + 12] +
+ line3[xx * 4 + 0] + line3[xx * 4 + 4] + line3[xx * 4 + 8] +
+ line3[xx * 4 + 12];
+ ss3 = line0[xx * 4 + 3] + line0[xx * 4 + 7] + line0[xx * 4 + 11] +
+ line0[xx * 4 + 15] + line1[xx * 4 + 3] + line1[xx * 4 + 7] +
+ line1[xx * 4 + 11] + line1[xx * 4 + 15] + line2[xx * 4 + 3] +
+ line2[xx * 4 + 7] + line2[xx * 4 + 11] + line2[xx * 4 + 15] +
+ line3[xx * 4 + 3] + line3[xx * 4 + 7] + line3[xx * 4 + 11] +
+ line3[xx * 4 + 15];
+ v = MAKE_UINT32((ss0 + amend) >> 4, 0, 0, (ss3 + amend) >> 4);
+ memcpy(imOut->image[y] + x * sizeof(v), &v, sizeof(v));
+ }
+ } else if (imIn->bands == 3) {
+ for (x = 0; x < box[2] / xscale; x++) {
+ int xx = box[0] + x * xscale;
+ UINT32 v;
+ ss0 = line0[xx * 4 + 0] + line0[xx * 4 + 4] + line0[xx * 4 + 8] +
+ line0[xx * 4 + 12] + line1[xx * 4 + 0] + line1[xx * 4 + 4] +
+ line1[xx * 4 + 8] + line1[xx * 4 + 12] + line2[xx * 4 + 0] +
+ line2[xx * 4 + 4] + line2[xx * 4 + 8] + line2[xx * 4 + 12] +
+ line3[xx * 4 + 0] + line3[xx * 4 + 4] + line3[xx * 4 + 8] +
+ line3[xx * 4 + 12];
+ ss1 = line0[xx * 4 + 1] + line0[xx * 4 + 5] + line0[xx * 4 + 9] +
+ line0[xx * 4 + 13] + line1[xx * 4 + 1] + line1[xx * 4 + 5] +
+ line1[xx * 4 + 9] + line1[xx * 4 + 13] + line2[xx * 4 + 1] +
+ line2[xx * 4 + 5] + line2[xx * 4 + 9] + line2[xx * 4 + 13] +
+ line3[xx * 4 + 1] + line3[xx * 4 + 5] + line3[xx * 4 + 9] +
+ line3[xx * 4 + 13];
+ ss2 = line0[xx * 4 + 2] + line0[xx * 4 + 6] + line0[xx * 4 + 10] +
+ line0[xx * 4 + 14] + line1[xx * 4 + 2] + line1[xx * 4 + 6] +
+ line1[xx * 4 + 10] + line1[xx * 4 + 14] + line2[xx * 4 + 2] +
+ line2[xx * 4 + 6] + line2[xx * 4 + 10] + line2[xx * 4 + 14] +
+ line3[xx * 4 + 2] + line3[xx * 4 + 6] + line3[xx * 4 + 10] +
+ line3[xx * 4 + 14];
+ v = MAKE_UINT32(
+ (ss0 + amend) >> 4, (ss1 + amend) >> 4, (ss2 + amend) >> 4, 0);
+ memcpy(imOut->image[y] + x * sizeof(v), &v, sizeof(v));
+ }
+ } else { // bands == 4
+ for (x = 0; x < box[2] / xscale; x++) {
+ int xx = box[0] + x * xscale;
+ UINT32 v;
+ ss0 = line0[xx * 4 + 0] + line0[xx * 4 + 4] + line0[xx * 4 + 8] +
+ line0[xx * 4 + 12] + line1[xx * 4 + 0] + line1[xx * 4 + 4] +
+ line1[xx * 4 + 8] + line1[xx * 4 + 12] + line2[xx * 4 + 0] +
+ line2[xx * 4 + 4] + line2[xx * 4 + 8] + line2[xx * 4 + 12] +
+ line3[xx * 4 + 0] + line3[xx * 4 + 4] + line3[xx * 4 + 8] +
+ line3[xx * 4 + 12];
+ ss1 = line0[xx * 4 + 1] + line0[xx * 4 + 5] + line0[xx * 4 + 9] +
+ line0[xx * 4 + 13] + line1[xx * 4 + 1] + line1[xx * 4 + 5] +
+ line1[xx * 4 + 9] + line1[xx * 4 + 13] + line2[xx * 4 + 1] +
+ line2[xx * 4 + 5] + line2[xx * 4 + 9] + line2[xx * 4 + 13] +
+ line3[xx * 4 + 1] + line3[xx * 4 + 5] + line3[xx * 4 + 9] +
+ line3[xx * 4 + 13];
+ ss2 = line0[xx * 4 + 2] + line0[xx * 4 + 6] + line0[xx * 4 + 10] +
+ line0[xx * 4 + 14] + line1[xx * 4 + 2] + line1[xx * 4 + 6] +
+ line1[xx * 4 + 10] + line1[xx * 4 + 14] + line2[xx * 4 + 2] +
+ line2[xx * 4 + 6] + line2[xx * 4 + 10] + line2[xx * 4 + 14] +
+ line3[xx * 4 + 2] + line3[xx * 4 + 6] + line3[xx * 4 + 10] +
+ line3[xx * 4 + 14];
+ ss3 = line0[xx * 4 + 3] + line0[xx * 4 + 7] + line0[xx * 4 + 11] +
+ line0[xx * 4 + 15] + line1[xx * 4 + 3] + line1[xx * 4 + 7] +
+ line1[xx * 4 + 11] + line1[xx * 4 + 15] + line2[xx * 4 + 3] +
+ line2[xx * 4 + 7] + line2[xx * 4 + 11] + line2[xx * 4 + 15] +
+ line3[xx * 4 + 3] + line3[xx * 4 + 7] + line3[xx * 4 + 11] +
+ line3[xx * 4 + 15];
+ v = MAKE_UINT32(
+ (ss0 + amend) >> 4,
+ (ss1 + amend) >> 4,
+ (ss2 + amend) >> 4,
+ (ss3 + amend) >> 4);
+ memcpy(imOut->image[y] + x * sizeof(v), &v, sizeof(v));
+ }
+ }
+ }
+ }
+}
+
+void
+ImagingReduce5x5(Imaging imOut, Imaging imIn, int box[4]) {
+ /* Fast special case for xscale = 5 and yscale = 5.
+ */
+ int xscale = 5, yscale = 5;
+ int x, y;
+ UINT32 ss0, ss1, ss2, ss3;
+ UINT32 multiplier = division_UINT32(yscale * xscale, 8);
+ UINT32 amend = yscale * xscale / 2;
+
+ if (imIn->image8) {
+ for (y = 0; y < box[3] / yscale; y++) {
+ int yy = box[1] + y * yscale;
+ UINT8 *line0 = (UINT8 *)imIn->image8[yy + 0];
+ UINT8 *line1 = (UINT8 *)imIn->image8[yy + 1];
+ UINT8 *line2 = (UINT8 *)imIn->image8[yy + 2];
+ UINT8 *line3 = (UINT8 *)imIn->image8[yy + 3];
+ UINT8 *line4 = (UINT8 *)imIn->image8[yy + 4];
+ for (x = 0; x < box[2] / xscale; x++) {
+ int xx = box[0] + x * xscale;
+ ss0 = line0[xx + 0] + line0[xx + 1] + line0[xx + 2] + line0[xx + 3] +
+ line0[xx + 4] + line1[xx + 0] + line1[xx + 1] + line1[xx + 2] +
+ line1[xx + 3] + line1[xx + 4] + line2[xx + 0] + line2[xx + 1] +
+ line2[xx + 2] + line2[xx + 3] + line2[xx + 4] + line3[xx + 0] +
+ line3[xx + 1] + line3[xx + 2] + line3[xx + 3] + line3[xx + 4] +
+ line4[xx + 0] + line4[xx + 1] + line4[xx + 2] + line4[xx + 3] +
+ line4[xx + 4];
+ imOut->image8[y][x] = ((ss0 + amend) * multiplier) >> 24;
+ }
+ }
+ } else {
+ for (y = 0; y < box[3] / yscale; y++) {
+ int yy = box[1] + y * yscale;
+ UINT8 *line0 = (UINT8 *)imIn->image[yy + 0];
+ UINT8 *line1 = (UINT8 *)imIn->image[yy + 1];
+ UINT8 *line2 = (UINT8 *)imIn->image[yy + 2];
+ UINT8 *line3 = (UINT8 *)imIn->image[yy + 3];
+ UINT8 *line4 = (UINT8 *)imIn->image[yy + 4];
+ if (imIn->bands == 2) {
+ for (x = 0; x < box[2] / xscale; x++) {
+ int xx = box[0] + x * xscale;
+ UINT32 v;
+ ss0 = line0[xx * 4 + 0] + line0[xx * 4 + 4] + line0[xx * 4 + 8] +
+ line0[xx * 4 + 12] + line0[xx * 4 + 16] + line1[xx * 4 + 0] +
+ line1[xx * 4 + 4] + line1[xx * 4 + 8] + line1[xx * 4 + 12] +
+ line1[xx * 4 + 16] + line2[xx * 4 + 0] + line2[xx * 4 + 4] +
+ line2[xx * 4 + 8] + line2[xx * 4 + 12] + line2[xx * 4 + 16] +
+ line3[xx * 4 + 0] + line3[xx * 4 + 4] + line3[xx * 4 + 8] +
+ line3[xx * 4 + 12] + line3[xx * 4 + 16] + line4[xx * 4 + 0] +
+ line4[xx * 4 + 4] + line4[xx * 4 + 8] + line4[xx * 4 + 12] +
+ line4[xx * 4 + 16];
+ ss3 = line0[xx * 4 + 3] + line0[xx * 4 + 7] + line0[xx * 4 + 11] +
+ line0[xx * 4 + 15] + line0[xx * 4 + 19] + line1[xx * 4 + 3] +
+ line1[xx * 4 + 7] + line1[xx * 4 + 11] + line1[xx * 4 + 15] +
+ line1[xx * 4 + 19] + line2[xx * 4 + 3] + line2[xx * 4 + 7] +
+ line2[xx * 4 + 11] + line2[xx * 4 + 15] + line2[xx * 4 + 19] +
+ line3[xx * 4 + 3] + line3[xx * 4 + 7] + line3[xx * 4 + 11] +
+ line3[xx * 4 + 15] + line3[xx * 4 + 19] + line4[xx * 4 + 3] +
+ line4[xx * 4 + 7] + line4[xx * 4 + 11] + line4[xx * 4 + 15] +
+ line4[xx * 4 + 19];
+ v = MAKE_UINT32(
+ ((ss0 + amend) * multiplier) >> 24,
+ 0,
+ 0,
+ ((ss3 + amend) * multiplier) >> 24);
+ memcpy(imOut->image[y] + x * sizeof(v), &v, sizeof(v));
+ }
+ } else if (imIn->bands == 3) {
+ for (x = 0; x < box[2] / xscale; x++) {
+ int xx = box[0] + x * xscale;
+ UINT32 v;
+ ss0 = line0[xx * 4 + 0] + line0[xx * 4 + 4] + line0[xx * 4 + 8] +
+ line0[xx * 4 + 12] + line0[xx * 4 + 16] + line1[xx * 4 + 0] +
+ line1[xx * 4 + 4] + line1[xx * 4 + 8] + line1[xx * 4 + 12] +
+ line1[xx * 4 + 16] + line2[xx * 4 + 0] + line2[xx * 4 + 4] +
+ line2[xx * 4 + 8] + line2[xx * 4 + 12] + line2[xx * 4 + 16] +
+ line3[xx * 4 + 0] + line3[xx * 4 + 4] + line3[xx * 4 + 8] +
+ line3[xx * 4 + 12] + line3[xx * 4 + 16] + line4[xx * 4 + 0] +
+ line4[xx * 4 + 4] + line4[xx * 4 + 8] + line4[xx * 4 + 12] +
+ line4[xx * 4 + 16];
+ ss1 = line0[xx * 4 + 1] + line0[xx * 4 + 5] + line0[xx * 4 + 9] +
+ line0[xx * 4 + 13] + line0[xx * 4 + 17] + line1[xx * 4 + 1] +
+ line1[xx * 4 + 5] + line1[xx * 4 + 9] + line1[xx * 4 + 13] +
+ line1[xx * 4 + 17] + line2[xx * 4 + 1] + line2[xx * 4 + 5] +
+ line2[xx * 4 + 9] + line2[xx * 4 + 13] + line2[xx * 4 + 17] +
+ line3[xx * 4 + 1] + line3[xx * 4 + 5] + line3[xx * 4 + 9] +
+ line3[xx * 4 + 13] + line3[xx * 4 + 17] + line4[xx * 4 + 1] +
+ line4[xx * 4 + 5] + line4[xx * 4 + 9] + line4[xx * 4 + 13] +
+ line4[xx * 4 + 17];
+ ss2 = line0[xx * 4 + 2] + line0[xx * 4 + 6] + line0[xx * 4 + 10] +
+ line0[xx * 4 + 14] + line0[xx * 4 + 18] + line1[xx * 4 + 2] +
+ line1[xx * 4 + 6] + line1[xx * 4 + 10] + line1[xx * 4 + 14] +
+ line1[xx * 4 + 18] + line2[xx * 4 + 2] + line2[xx * 4 + 6] +
+ line2[xx * 4 + 10] + line2[xx * 4 + 14] + line2[xx * 4 + 18] +
+ line3[xx * 4 + 2] + line3[xx * 4 + 6] + line3[xx * 4 + 10] +
+ line3[xx * 4 + 14] + line3[xx * 4 + 18] + line4[xx * 4 + 2] +
+ line4[xx * 4 + 6] + line4[xx * 4 + 10] + line4[xx * 4 + 14] +
+ line4[xx * 4 + 18];
+ v = MAKE_UINT32(
+ ((ss0 + amend) * multiplier) >> 24,
+ ((ss1 + amend) * multiplier) >> 24,
+ ((ss2 + amend) * multiplier) >> 24,
+ 0);
+ memcpy(imOut->image[y] + x * sizeof(v), &v, sizeof(v));
+ }
+ } else { // bands == 4
+ for (x = 0; x < box[2] / xscale; x++) {
+ int xx = box[0] + x * xscale;
+ UINT32 v;
+ ss0 = line0[xx * 4 + 0] + line0[xx * 4 + 4] + line0[xx * 4 + 8] +
+ line0[xx * 4 + 12] + line0[xx * 4 + 16] + line1[xx * 4 + 0] +
+ line1[xx * 4 + 4] + line1[xx * 4 + 8] + line1[xx * 4 + 12] +
+ line1[xx * 4 + 16] + line2[xx * 4 + 0] + line2[xx * 4 + 4] +
+ line2[xx * 4 + 8] + line2[xx * 4 + 12] + line2[xx * 4 + 16] +
+ line3[xx * 4 + 0] + line3[xx * 4 + 4] + line3[xx * 4 + 8] +
+ line3[xx * 4 + 12] + line3[xx * 4 + 16] + line4[xx * 4 + 0] +
+ line4[xx * 4 + 4] + line4[xx * 4 + 8] + line4[xx * 4 + 12] +
+ line4[xx * 4 + 16];
+ ss1 = line0[xx * 4 + 1] + line0[xx * 4 + 5] + line0[xx * 4 + 9] +
+ line0[xx * 4 + 13] + line0[xx * 4 + 17] + line1[xx * 4 + 1] +
+ line1[xx * 4 + 5] + line1[xx * 4 + 9] + line1[xx * 4 + 13] +
+ line1[xx * 4 + 17] + line2[xx * 4 + 1] + line2[xx * 4 + 5] +
+ line2[xx * 4 + 9] + line2[xx * 4 + 13] + line2[xx * 4 + 17] +
+ line3[xx * 4 + 1] + line3[xx * 4 + 5] + line3[xx * 4 + 9] +
+ line3[xx * 4 + 13] + line3[xx * 4 + 17] + line4[xx * 4 + 1] +
+ line4[xx * 4 + 5] + line4[xx * 4 + 9] + line4[xx * 4 + 13] +
+ line4[xx * 4 + 17];
+ ss2 = line0[xx * 4 + 2] + line0[xx * 4 + 6] + line0[xx * 4 + 10] +
+ line0[xx * 4 + 14] + line0[xx * 4 + 18] + line1[xx * 4 + 2] +
+ line1[xx * 4 + 6] + line1[xx * 4 + 10] + line1[xx * 4 + 14] +
+ line1[xx * 4 + 18] + line2[xx * 4 + 2] + line2[xx * 4 + 6] +
+ line2[xx * 4 + 10] + line2[xx * 4 + 14] + line2[xx * 4 + 18] +
+ line3[xx * 4 + 2] + line3[xx * 4 + 6] + line3[xx * 4 + 10] +
+ line3[xx * 4 + 14] + line3[xx * 4 + 18] + line4[xx * 4 + 2] +
+ line4[xx * 4 + 6] + line4[xx * 4 + 10] + line4[xx * 4 + 14] +
+ line4[xx * 4 + 18];
+ ss3 = line0[xx * 4 + 3] + line0[xx * 4 + 7] + line0[xx * 4 + 11] +
+ line0[xx * 4 + 15] + line0[xx * 4 + 19] + line1[xx * 4 + 3] +
+ line1[xx * 4 + 7] + line1[xx * 4 + 11] + line1[xx * 4 + 15] +
+ line1[xx * 4 + 19] + line2[xx * 4 + 3] + line2[xx * 4 + 7] +
+ line2[xx * 4 + 11] + line2[xx * 4 + 15] + line2[xx * 4 + 19] +
+ line3[xx * 4 + 3] + line3[xx * 4 + 7] + line3[xx * 4 + 11] +
+ line3[xx * 4 + 15] + line3[xx * 4 + 19] + line4[xx * 4 + 3] +
+ line4[xx * 4 + 7] + line4[xx * 4 + 11] + line4[xx * 4 + 15] +
+ line4[xx * 4 + 19];
+ v = MAKE_UINT32(
+ ((ss0 + amend) * multiplier) >> 24,
+ ((ss1 + amend) * multiplier) >> 24,
+ ((ss2 + amend) * multiplier) >> 24,
+ ((ss3 + amend) * multiplier) >> 24);
+ memcpy(imOut->image[y] + x * sizeof(v), &v, sizeof(v));
+ }
+ }
+ }
+ }
+}
+
+void
+ImagingReduceCorners(Imaging imOut, Imaging imIn, int box[4], int xscale, int yscale) {
+ /* Fill the last row and the last column for any xscale and yscale.
+ */
+ int x, y, xx, yy;
+
+ if (imIn->image8) {
+ if (box[2] % xscale) {
+ int scale = (box[2] % xscale) * yscale;
+ UINT32 multiplier = division_UINT32(scale, 8);
+ UINT32 amend = scale / 2;
+ for (y = 0; y < box[3] / yscale; y++) {
+ int yy_from = box[1] + y * yscale;
+ UINT32 ss = amend;
+ x = box[2] / xscale;
+
+ for (yy = yy_from; yy < yy_from + yscale; yy++) {
+ UINT8 *line = (UINT8 *)imIn->image8[yy];
+ for (xx = box[0] + x * xscale; xx < box[0] + box[2]; xx++) {
+ ss += line[xx + 0];
+ }
+ }
+ imOut->image8[y][x] = (ss * multiplier) >> 24;
+ }
+ }
+ if (box[3] % yscale) {
+ int scale = xscale * (box[3] % yscale);
+ UINT32 multiplier = division_UINT32(scale, 8);
+ UINT32 amend = scale / 2;
+ y = box[3] / yscale;
+ for (x = 0; x < box[2] / xscale; x++) {
+ int xx_from = box[0] + x * xscale;
+ UINT32 ss = amend;
+ for (yy = box[1] + y * yscale; yy < box[1] + box[3]; yy++) {
+ UINT8 *line = (UINT8 *)imIn->image8[yy];
+ for (xx = xx_from; xx < xx_from + xscale; xx++) {
+ ss += line[xx + 0];
+ }
+ }
+ imOut->image8[y][x] = (ss * multiplier) >> 24;
+ }
+ }
+ if (box[2] % xscale && box[3] % yscale) {
+ int scale = (box[2] % xscale) * (box[3] % yscale);
+ UINT32 multiplier = division_UINT32(scale, 8);
+ UINT32 amend = scale / 2;
+ UINT32 ss = amend;
+ x = box[2] / xscale;
+ y = box[3] / yscale;
+ for (yy = box[1] + y * yscale; yy < box[1] + box[3]; yy++) {
+ UINT8 *line = (UINT8 *)imIn->image8[yy];
+ for (xx = box[0] + x * xscale; xx < box[0] + box[2]; xx++) {
+ ss += line[xx + 0];
+ }
+ }
+ imOut->image8[y][x] = (ss * multiplier) >> 24;
+ }
+ } else {
+ if (box[2] % xscale) {
+ int scale = (box[2] % xscale) * yscale;
+ UINT32 multiplier = division_UINT32(scale, 8);
+ UINT32 amend = scale / 2;
+ for (y = 0; y < box[3] / yscale; y++) {
+ int yy_from = box[1] + y * yscale;
+ UINT32 v;
+ UINT32 ss0 = amend, ss1 = amend, ss2 = amend, ss3 = amend;
+ x = box[2] / xscale;
+
+ for (yy = yy_from; yy < yy_from + yscale; yy++) {
+ UINT8 *line = (UINT8 *)imIn->image[yy];
+ for (xx = box[0] + x * xscale; xx < box[0] + box[2]; xx++) {
+ ss0 += line[xx * 4 + 0];
+ ss1 += line[xx * 4 + 1];
+ ss2 += line[xx * 4 + 2];
+ ss3 += line[xx * 4 + 3];
+ }
+ }
+ v = MAKE_UINT32(
+ (ss0 * multiplier) >> 24,
+ (ss1 * multiplier) >> 24,
+ (ss2 * multiplier) >> 24,
+ (ss3 * multiplier) >> 24);
+ memcpy(imOut->image[y] + x * sizeof(v), &v, sizeof(v));
+ }
+ }
+ if (box[3] % yscale) {
+ int scale = xscale * (box[3] % yscale);
+ UINT32 multiplier = division_UINT32(scale, 8);
+ UINT32 amend = scale / 2;
+ y = box[3] / yscale;
+ for (x = 0; x < box[2] / xscale; x++) {
+ int xx_from = box[0] + x * xscale;
+ UINT32 v;
+ UINT32 ss0 = amend, ss1 = amend, ss2 = amend, ss3 = amend;
+ for (yy = box[1] + y * yscale; yy < box[1] + box[3]; yy++) {
+ UINT8 *line = (UINT8 *)imIn->image[yy];
+ for (xx = xx_from; xx < xx_from + xscale; xx++) {
+ ss0 += line[xx * 4 + 0];
+ ss1 += line[xx * 4 + 1];
+ ss2 += line[xx * 4 + 2];
+ ss3 += line[xx * 4 + 3];
+ }
+ }
+ v = MAKE_UINT32(
+ (ss0 * multiplier) >> 24,
+ (ss1 * multiplier) >> 24,
+ (ss2 * multiplier) >> 24,
+ (ss3 * multiplier) >> 24);
+ memcpy(imOut->image[y] + x * sizeof(v), &v, sizeof(v));
+ }
+ }
+ if (box[2] % xscale && box[3] % yscale) {
+ int scale = (box[2] % xscale) * (box[3] % yscale);
+ UINT32 multiplier = division_UINT32(scale, 8);
+ UINT32 amend = scale / 2;
+ UINT32 v;
+ UINT32 ss0 = amend, ss1 = amend, ss2 = amend, ss3 = amend;
+ x = box[2] / xscale;
+ y = box[3] / yscale;
+ for (yy = box[1] + y * yscale; yy < box[1] + box[3]; yy++) {
+ UINT8 *line = (UINT8 *)imIn->image[yy];
+ for (xx = box[0] + x * xscale; xx < box[0] + box[2]; xx++) {
+ ss0 += line[xx * 4 + 0];
+ ss1 += line[xx * 4 + 1];
+ ss2 += line[xx * 4 + 2];
+ ss3 += line[xx * 4 + 3];
+ }
+ }
+ v = MAKE_UINT32(
+ (ss0 * multiplier) >> 24,
+ (ss1 * multiplier) >> 24,
+ (ss2 * multiplier) >> 24,
+ (ss3 * multiplier) >> 24);
+ memcpy(imOut->image[y] + x * sizeof(v), &v, sizeof(v));
+ }
+ }
+}
+
+void
+ImagingReduceNxN_32bpc(
+ Imaging imOut, Imaging imIn, int box[4], int xscale, int yscale) {
+ /* The most general implementation for any xscale and yscale
+ */
+ int x, y, xx, yy;
+ double multiplier = 1.0 / (yscale * xscale);
+
+ switch (imIn->type) {
+ case IMAGING_TYPE_INT32:
+ for (y = 0; y < box[3] / yscale; y++) {
+ int yy_from = box[1] + y * yscale;
+ for (x = 0; x < box[2] / xscale; x++) {
+ int xx_from = box[0] + x * xscale;
+ double ss = 0;
+ for (yy = yy_from; yy < yy_from + yscale - 1; yy += 2) {
+ INT32 *line0 = (INT32 *)imIn->image32[yy];
+ INT32 *line1 = (INT32 *)imIn->image32[yy + 1];
+ for (xx = xx_from; xx < xx_from + xscale - 1; xx += 2) {
+ ss += line0[xx + 0] + line0[xx + 1] + line1[xx + 0] +
+ line1[xx + 1];
+ }
+ if (xscale & 0x01) {
+ ss += line0[xx + 0] + line1[xx + 0];
+ }
+ }
+ if (yscale & 0x01) {
+ INT32 *line = (INT32 *)imIn->image32[yy];
+ for (xx = xx_from; xx < xx_from + xscale - 1; xx += 2) {
+ ss += line[xx + 0] + line[xx + 1];
+ }
+ if (xscale & 0x01) {
+ ss += line[xx + 0];
+ }
+ }
+ IMAGING_PIXEL_I(imOut, x, y) = ROUND_UP(ss * multiplier);
+ }
+ }
+ break;
+
+ case IMAGING_TYPE_FLOAT32:
+ for (y = 0; y < box[3] / yscale; y++) {
+ int yy_from = box[1] + y * yscale;
+ for (x = 0; x < box[2] / xscale; x++) {
+ int xx_from = box[0] + x * xscale;
+ double ss = 0;
+ for (yy = yy_from; yy < yy_from + yscale - 1; yy += 2) {
+ FLOAT32 *line0 = (FLOAT32 *)imIn->image32[yy];
+ FLOAT32 *line1 = (FLOAT32 *)imIn->image32[yy + 1];
+ for (xx = xx_from; xx < xx_from + xscale - 1; xx += 2) {
+ ss += line0[xx + 0] + line0[xx + 1] + line1[xx + 0] +
+ line1[xx + 1];
+ }
+ if (xscale & 0x01) {
+ ss += line0[xx + 0] + line1[xx + 0];
+ }
+ }
+ if (yscale & 0x01) {
+ FLOAT32 *line = (FLOAT32 *)imIn->image32[yy];
+ for (xx = xx_from; xx < xx_from + xscale - 1; xx += 2) {
+ ss += line[xx + 0] + line[xx + 1];
+ }
+ if (xscale & 0x01) {
+ ss += line[xx + 0];
+ }
+ }
+ IMAGING_PIXEL_F(imOut, x, y) = ss * multiplier;
+ }
+ }
+ break;
+ }
+}
+
+void
+ImagingReduceCorners_32bpc(
+ Imaging imOut, Imaging imIn, int box[4], int xscale, int yscale) {
+ /* Fill the last row and the last column for any xscale and yscale.
+ */
+ int x, y, xx, yy;
+
+ switch (imIn->type) {
+ case IMAGING_TYPE_INT32:
+ if (box[2] % xscale) {
+ double multiplier = 1.0 / ((box[2] % xscale) * yscale);
+ for (y = 0; y < box[3] / yscale; y++) {
+ int yy_from = box[1] + y * yscale;
+ double ss = 0;
+ x = box[2] / xscale;
+ for (yy = yy_from; yy < yy_from + yscale; yy++) {
+ INT32 *line = (INT32 *)imIn->image32[yy];
+ for (xx = box[0] + x * xscale; xx < box[0] + box[2]; xx++) {
+ ss += line[xx + 0];
+ }
+ }
+ IMAGING_PIXEL_I(imOut, x, y) = ROUND_UP(ss * multiplier);
+ }
+ }
+ if (box[3] % yscale) {
+ double multiplier = 1.0 / (xscale * (box[3] % yscale));
+ y = box[3] / yscale;
+ for (x = 0; x < box[2] / xscale; x++) {
+ int xx_from = box[0] + x * xscale;
+ double ss = 0;
+ for (yy = box[1] + y * yscale; yy < box[1] + box[3]; yy++) {
+ INT32 *line = (INT32 *)imIn->image32[yy];
+ for (xx = xx_from; xx < xx_from + xscale; xx++) {
+ ss += line[xx + 0];
+ }
+ }
+ IMAGING_PIXEL_I(imOut, x, y) = ROUND_UP(ss * multiplier);
+ }
+ }
+ if (box[2] % xscale && box[3] % yscale) {
+ double multiplier = 1.0 / ((box[2] % xscale) * (box[3] % yscale));
+ double ss = 0;
+ x = box[2] / xscale;
+ y = box[3] / yscale;
+ for (yy = box[1] + y * yscale; yy < box[1] + box[3]; yy++) {
+ INT32 *line = (INT32 *)imIn->image32[yy];
+ for (xx = box[0] + x * xscale; xx < box[0] + box[2]; xx++) {
+ ss += line[xx + 0];
+ }
+ }
+ IMAGING_PIXEL_I(imOut, x, y) = ROUND_UP(ss * multiplier);
+ }
+ break;
+
+ case IMAGING_TYPE_FLOAT32:
+ if (box[2] % xscale) {
+ double multiplier = 1.0 / ((box[2] % xscale) * yscale);
+ for (y = 0; y < box[3] / yscale; y++) {
+ int yy_from = box[1] + y * yscale;
+ double ss = 0;
+ x = box[2] / xscale;
+ for (yy = yy_from; yy < yy_from + yscale; yy++) {
+ FLOAT32 *line = (FLOAT32 *)imIn->image32[yy];
+ for (xx = box[0] + x * xscale; xx < box[0] + box[2]; xx++) {
+ ss += line[xx + 0];
+ }
+ }
+ IMAGING_PIXEL_F(imOut, x, y) = ss * multiplier;
+ }
+ }
+ if (box[3] % yscale) {
+ double multiplier = 1.0 / (xscale * (box[3] % yscale));
+ y = box[3] / yscale;
+ for (x = 0; x < box[2] / xscale; x++) {
+ int xx_from = box[0] + x * xscale;
+ double ss = 0;
+ for (yy = box[1] + y * yscale; yy < box[1] + box[3]; yy++) {
+ FLOAT32 *line = (FLOAT32 *)imIn->image32[yy];
+ for (xx = xx_from; xx < xx_from + xscale; xx++) {
+ ss += line[xx + 0];
+ }
+ }
+ IMAGING_PIXEL_F(imOut, x, y) = ss * multiplier;
+ }
+ }
+ if (box[2] % xscale && box[3] % yscale) {
+ double multiplier = 1.0 / ((box[2] % xscale) * (box[3] % yscale));
+ double ss = 0;
+ x = box[2] / xscale;
+ y = box[3] / yscale;
+ for (yy = box[1] + y * yscale; yy < box[1] + box[3]; yy++) {
+ FLOAT32 *line = (FLOAT32 *)imIn->image32[yy];
+ for (xx = box[0] + x * xscale; xx < box[0] + box[2]; xx++) {
+ ss += line[xx + 0];
+ }
+ }
+ IMAGING_PIXEL_F(imOut, x, y) = ss * multiplier;
+ }
+ break;
+ }
+}
+
+Imaging
+ImagingReduce(Imaging imIn, int xscale, int yscale, int box[4]) {
+ ImagingSectionCookie cookie;
+ Imaging imOut = NULL;
+
+ if (strcmp(imIn->mode, "P") == 0 || strcmp(imIn->mode, "1") == 0) {
+ return (Imaging)ImagingError_ModeError();
+ }
+
+ if (imIn->type == IMAGING_TYPE_SPECIAL) {
+ return (Imaging)ImagingError_ModeError();
+ }
+
+ imOut = ImagingNewDirty(
+ imIn->mode, (box[2] + xscale - 1) / xscale, (box[3] + yscale - 1) / yscale);
+ if (!imOut) {
+ return NULL;
+ }
+
+ ImagingSectionEnter(&cookie);
+
+ switch (imIn->type) {
+ case IMAGING_TYPE_UINT8:
+ if (xscale == 1) {
+ if (yscale == 2) {
+ ImagingReduce1x2(imOut, imIn, box);
+ } else if (yscale == 3) {
+ ImagingReduce1x3(imOut, imIn, box);
+ } else {
+ ImagingReduce1xN(imOut, imIn, box, yscale);
+ }
+ } else if (yscale == 1) {
+ if (xscale == 2) {
+ ImagingReduce2x1(imOut, imIn, box);
+ } else if (xscale == 3) {
+ ImagingReduce3x1(imOut, imIn, box);
+ } else {
+ ImagingReduceNx1(imOut, imIn, box, xscale);
+ }
+ } else if (xscale == yscale && xscale <= 5) {
+ if (xscale == 2) {
+ ImagingReduce2x2(imOut, imIn, box);
+ } else if (xscale == 3) {
+ ImagingReduce3x3(imOut, imIn, box);
+ } else if (xscale == 4) {
+ ImagingReduce4x4(imOut, imIn, box);
+ } else {
+ ImagingReduce5x5(imOut, imIn, box);
+ }
+ } else {
+ ImagingReduceNxN(imOut, imIn, box, xscale, yscale);
+ }
+
+ ImagingReduceCorners(imOut, imIn, box, xscale, yscale);
+ break;
+
+ case IMAGING_TYPE_INT32:
+ case IMAGING_TYPE_FLOAT32:
+ ImagingReduceNxN_32bpc(imOut, imIn, box, xscale, yscale);
+
+ ImagingReduceCorners_32bpc(imOut, imIn, box, xscale, yscale);
+ break;
+ }
+
+ ImagingSectionLeave(&cookie);
+
+ return imOut;
+}
diff --git a/contrib/python/Pillow/py3/libImaging/Resample.c b/contrib/python/Pillow/py3/libImaging/Resample.c
new file mode 100644
index 00000000000..cf79d8a4e4d
--- /dev/null
+++ b/contrib/python/Pillow/py3/libImaging/Resample.c
@@ -0,0 +1,708 @@
+#include "Imaging.h"
+
+#include <math.h>
+
+#define ROUND_UP(f) ((int)((f) >= 0.0 ? (f) + 0.5F : (f)-0.5F))
+
+struct filter {
+ double (*filter)(double x);
+ double support;
+};
+
+static inline double
+box_filter(double x) {
+ if (x > -0.5 && x <= 0.5) {
+ return 1.0;
+ }
+ return 0.0;
+}
+
+static inline double
+bilinear_filter(double x) {
+ if (x < 0.0) {
+ x = -x;
+ }
+ if (x < 1.0) {
+ return 1.0 - x;
+ }
+ return 0.0;
+}
+
+static inline double
+hamming_filter(double x) {
+ if (x < 0.0) {
+ x = -x;
+ }
+ if (x == 0.0) {
+ return 1.0;
+ }
+ if (x >= 1.0) {
+ return 0.0;
+ }
+ x = x * M_PI;
+ return sin(x) / x * (0.54f + 0.46f * cos(x));
+}
+
+static inline double
+bicubic_filter(double x) {
+ /* https://en.wikipedia.org/wiki/Bicubic_interpolation#Bicubic_convolution_algorithm
+ */
+#define a -0.5
+ if (x < 0.0) {
+ x = -x;
+ }
+ if (x < 1.0) {
+ return ((a + 2.0) * x - (a + 3.0)) * x * x + 1;
+ }
+ if (x < 2.0) {
+ return (((x - 5) * x + 8) * x - 4) * a;
+ }
+ return 0.0;
+#undef a
+}
+
+static inline double
+sinc_filter(double x) {
+ if (x == 0.0) {
+ return 1.0;
+ }
+ x = x * M_PI;
+ return sin(x) / x;
+}
+
+static inline double
+lanczos_filter(double x) {
+ /* truncated sinc */
+ if (-3.0 <= x && x < 3.0) {
+ return sinc_filter(x) * sinc_filter(x / 3);
+ }
+ return 0.0;
+}
+
+static struct filter BOX = {box_filter, 0.5};
+static struct filter BILINEAR = {bilinear_filter, 1.0};
+static struct filter HAMMING = {hamming_filter, 1.0};
+static struct filter BICUBIC = {bicubic_filter, 2.0};
+static struct filter LANCZOS = {lanczos_filter, 3.0};
+
+/* 8 bits for result. Filter can have negative areas.
+ In one cases the sum of the coefficients will be negative,
+ in the other it will be more than 1.0. That is why we need
+ two extra bits for overflow and int type. */
+#define PRECISION_BITS (32 - 8 - 2)
+
+/* Handles values form -640 to 639. */
+UINT8 _clip8_lookups[1280] = {
+ 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, 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, 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, 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, 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, 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, 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, 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, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 2, 3, 4, 5,
+ 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22,
+ 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39,
+ 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56,
+ 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73,
+ 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90,
+ 91, 92, 93, 94, 95, 96, 97, 98, 99, 100, 101, 102, 103, 104, 105, 106, 107,
+ 108, 109, 110, 111, 112, 113, 114, 115, 116, 117, 118, 119, 120, 121, 122, 123, 124,
+ 125, 126, 127, 128, 129, 130, 131, 132, 133, 134, 135, 136, 137, 138, 139, 140, 141,
+ 142, 143, 144, 145, 146, 147, 148, 149, 150, 151, 152, 153, 154, 155, 156, 157, 158,
+ 159, 160, 161, 162, 163, 164, 165, 166, 167, 168, 169, 170, 171, 172, 173, 174, 175,
+ 176, 177, 178, 179, 180, 181, 182, 183, 184, 185, 186, 187, 188, 189, 190, 191, 192,
+ 193, 194, 195, 196, 197, 198, 199, 200, 201, 202, 203, 204, 205, 206, 207, 208, 209,
+ 210, 211, 212, 213, 214, 215, 216, 217, 218, 219, 220, 221, 222, 223, 224, 225, 226,
+ 227, 228, 229, 230, 231, 232, 233, 234, 235, 236, 237, 238, 239, 240, 241, 242, 243,
+ 244, 245, 246, 247, 248, 249, 250, 251, 252, 253, 254, 255, 255, 255, 255, 255, 255,
+ 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
+ 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
+ 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
+ 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
+ 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
+ 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
+ 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
+ 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
+ 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
+ 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
+ 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
+ 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
+ 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
+ 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
+ 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
+ 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
+ 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
+ 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
+ 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
+ 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
+ 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
+ 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
+ 255, 255, 255, 255, 255,
+};
+
+UINT8 *clip8_lookups = &_clip8_lookups[640];
+
+static inline UINT8
+clip8(int in) {
+ return clip8_lookups[in >> PRECISION_BITS];
+}
+
+int
+precompute_coeffs(
+ int inSize,
+ float in0,
+ float in1,
+ int outSize,
+ struct filter *filterp,
+ int **boundsp,
+ double **kkp) {
+ double support, scale, filterscale;
+ double center, ww, ss;
+ int xx, x, ksize, xmin, xmax;
+ int *bounds;
+ double *kk, *k;
+
+ /* prepare for horizontal stretch */
+ filterscale = scale = (double)(in1 - in0) / outSize;
+ if (filterscale < 1.0) {
+ filterscale = 1.0;
+ }
+
+ /* determine support size (length of resampling filter) */
+ support = filterp->support * filterscale;
+
+ /* maximum number of coeffs */
+ ksize = (int)ceil(support) * 2 + 1;
+
+ // check for overflow
+ if (outSize > INT_MAX / (ksize * (int)sizeof(double))) {
+ ImagingError_MemoryError();
+ return 0;
+ }
+
+ /* coefficient buffer */
+ /* malloc check ok, overflow checked above */
+ kk = malloc(outSize * ksize * sizeof(double));
+ if (!kk) {
+ ImagingError_MemoryError();
+ return 0;
+ }
+
+ /* malloc check ok, ksize*sizeof(double) > 2*sizeof(int) */
+ bounds = malloc(outSize * 2 * sizeof(int));
+ if (!bounds) {
+ free(kk);
+ ImagingError_MemoryError();
+ return 0;
+ }
+
+ for (xx = 0; xx < outSize; xx++) {
+ center = in0 + (xx + 0.5) * scale;
+ ww = 0.0;
+ ss = 1.0 / filterscale;
+ // Round the value
+ xmin = (int)(center - support + 0.5);
+ if (xmin < 0) {
+ xmin = 0;
+ }
+ // Round the value
+ xmax = (int)(center + support + 0.5);
+ if (xmax > inSize) {
+ xmax = inSize;
+ }
+ xmax -= xmin;
+ k = &kk[xx * ksize];
+ for (x = 0; x < xmax; x++) {
+ double w = filterp->filter((x + xmin - center + 0.5) * ss);
+ k[x] = w;
+ ww += w;
+ }
+ for (x = 0; x < xmax; x++) {
+ if (ww != 0.0) {
+ k[x] /= ww;
+ }
+ }
+ // Remaining values should stay empty if they are used despite of xmax.
+ for (; x < ksize; x++) {
+ k[x] = 0;
+ }
+ bounds[xx * 2 + 0] = xmin;
+ bounds[xx * 2 + 1] = xmax;
+ }
+ *boundsp = bounds;
+ *kkp = kk;
+ return ksize;
+}
+
+void
+normalize_coeffs_8bpc(int outSize, int ksize, double *prekk) {
+ int x;
+ INT32 *kk;
+
+ // use the same buffer for normalized coefficients
+ kk = (INT32 *)prekk;
+
+ for (x = 0; x < outSize * ksize; x++) {
+ if (prekk[x] < 0) {
+ kk[x] = (int)(-0.5 + prekk[x] * (1 << PRECISION_BITS));
+ } else {
+ kk[x] = (int)(0.5 + prekk[x] * (1 << PRECISION_BITS));
+ }
+ }
+}
+
+void
+ImagingResampleHorizontal_8bpc(
+ Imaging imOut, Imaging imIn, int offset, int ksize, int *bounds, double *prekk) {
+ ImagingSectionCookie cookie;
+ int ss0, ss1, ss2, ss3;
+ int xx, yy, x, xmin, xmax;
+ INT32 *k, *kk;
+
+ // use the same buffer for normalized coefficients
+ kk = (INT32 *)prekk;
+ normalize_coeffs_8bpc(imOut->xsize, ksize, prekk);
+
+ ImagingSectionEnter(&cookie);
+ if (imIn->image8) {
+ for (yy = 0; yy < imOut->ysize; yy++) {
+ for (xx = 0; xx < imOut->xsize; xx++) {
+ xmin = bounds[xx * 2 + 0];
+ xmax = bounds[xx * 2 + 1];
+ k = &kk[xx * ksize];
+ ss0 = 1 << (PRECISION_BITS - 1);
+ for (x = 0; x < xmax; x++) {
+ ss0 += ((UINT8)imIn->image8[yy + offset][x + xmin]) * k[x];
+ }
+ imOut->image8[yy][xx] = clip8(ss0);
+ }
+ }
+ } else if (imIn->type == IMAGING_TYPE_UINT8) {
+ if (imIn->bands == 2) {
+ for (yy = 0; yy < imOut->ysize; yy++) {
+ for (xx = 0; xx < imOut->xsize; xx++) {
+ UINT32 v;
+ xmin = bounds[xx * 2 + 0];
+ xmax = bounds[xx * 2 + 1];
+ k = &kk[xx * ksize];
+ ss0 = ss3 = 1 << (PRECISION_BITS - 1);
+ for (x = 0; x < xmax; x++) {
+ ss0 += ((UINT8)imIn->image[yy + offset][(x + xmin) * 4 + 0]) *
+ k[x];
+ ss3 += ((UINT8)imIn->image[yy + offset][(x + xmin) * 4 + 3]) *
+ k[x];
+ }
+ v = MAKE_UINT32(clip8(ss0), 0, 0, clip8(ss3));
+ memcpy(imOut->image[yy] + xx * sizeof(v), &v, sizeof(v));
+ }
+ }
+ } else if (imIn->bands == 3) {
+ for (yy = 0; yy < imOut->ysize; yy++) {
+ for (xx = 0; xx < imOut->xsize; xx++) {
+ UINT32 v;
+ xmin = bounds[xx * 2 + 0];
+ xmax = bounds[xx * 2 + 1];
+ k = &kk[xx * ksize];
+ ss0 = ss1 = ss2 = 1 << (PRECISION_BITS - 1);
+ for (x = 0; x < xmax; x++) {
+ ss0 += ((UINT8)imIn->image[yy + offset][(x + xmin) * 4 + 0]) *
+ k[x];
+ ss1 += ((UINT8)imIn->image[yy + offset][(x + xmin) * 4 + 1]) *
+ k[x];
+ ss2 += ((UINT8)imIn->image[yy + offset][(x + xmin) * 4 + 2]) *
+ k[x];
+ }
+ v = MAKE_UINT32(clip8(ss0), clip8(ss1), clip8(ss2), 0);
+ memcpy(imOut->image[yy] + xx * sizeof(v), &v, sizeof(v));
+ }
+ }
+ } else {
+ for (yy = 0; yy < imOut->ysize; yy++) {
+ for (xx = 0; xx < imOut->xsize; xx++) {
+ UINT32 v;
+ xmin = bounds[xx * 2 + 0];
+ xmax = bounds[xx * 2 + 1];
+ k = &kk[xx * ksize];
+ ss0 = ss1 = ss2 = ss3 = 1 << (PRECISION_BITS - 1);
+ for (x = 0; x < xmax; x++) {
+ ss0 += ((UINT8)imIn->image[yy + offset][(x + xmin) * 4 + 0]) *
+ k[x];
+ ss1 += ((UINT8)imIn->image[yy + offset][(x + xmin) * 4 + 1]) *
+ k[x];
+ ss2 += ((UINT8)imIn->image[yy + offset][(x + xmin) * 4 + 2]) *
+ k[x];
+ ss3 += ((UINT8)imIn->image[yy + offset][(x + xmin) * 4 + 3]) *
+ k[x];
+ }
+ v = MAKE_UINT32(clip8(ss0), clip8(ss1), clip8(ss2), clip8(ss3));
+ memcpy(imOut->image[yy] + xx * sizeof(v), &v, sizeof(v));
+ }
+ }
+ }
+ }
+ ImagingSectionLeave(&cookie);
+}
+
+void
+ImagingResampleVertical_8bpc(
+ Imaging imOut, Imaging imIn, int offset, int ksize, int *bounds, double *prekk) {
+ ImagingSectionCookie cookie;
+ int ss0, ss1, ss2, ss3;
+ int xx, yy, y, ymin, ymax;
+ INT32 *k, *kk;
+
+ // use the same buffer for normalized coefficients
+ kk = (INT32 *)prekk;
+ normalize_coeffs_8bpc(imOut->ysize, ksize, prekk);
+
+ ImagingSectionEnter(&cookie);
+ if (imIn->image8) {
+ for (yy = 0; yy < imOut->ysize; yy++) {
+ k = &kk[yy * ksize];
+ ymin = bounds[yy * 2 + 0];
+ ymax = bounds[yy * 2 + 1];
+ for (xx = 0; xx < imOut->xsize; xx++) {
+ ss0 = 1 << (PRECISION_BITS - 1);
+ for (y = 0; y < ymax; y++) {
+ ss0 += ((UINT8)imIn->image8[y + ymin][xx]) * k[y];
+ }
+ imOut->image8[yy][xx] = clip8(ss0);
+ }
+ }
+ } else if (imIn->type == IMAGING_TYPE_UINT8) {
+ if (imIn->bands == 2) {
+ for (yy = 0; yy < imOut->ysize; yy++) {
+ k = &kk[yy * ksize];
+ ymin = bounds[yy * 2 + 0];
+ ymax = bounds[yy * 2 + 1];
+ for (xx = 0; xx < imOut->xsize; xx++) {
+ UINT32 v;
+ ss0 = ss3 = 1 << (PRECISION_BITS - 1);
+ for (y = 0; y < ymax; y++) {
+ ss0 += ((UINT8)imIn->image[y + ymin][xx * 4 + 0]) * k[y];
+ ss3 += ((UINT8)imIn->image[y + ymin][xx * 4 + 3]) * k[y];
+ }
+ v = MAKE_UINT32(clip8(ss0), 0, 0, clip8(ss3));
+ memcpy(imOut->image[yy] + xx * sizeof(v), &v, sizeof(v));
+ }
+ }
+ } else if (imIn->bands == 3) {
+ for (yy = 0; yy < imOut->ysize; yy++) {
+ k = &kk[yy * ksize];
+ ymin = bounds[yy * 2 + 0];
+ ymax = bounds[yy * 2 + 1];
+ for (xx = 0; xx < imOut->xsize; xx++) {
+ UINT32 v;
+ ss0 = ss1 = ss2 = 1 << (PRECISION_BITS - 1);
+ for (y = 0; y < ymax; y++) {
+ ss0 += ((UINT8)imIn->image[y + ymin][xx * 4 + 0]) * k[y];
+ ss1 += ((UINT8)imIn->image[y + ymin][xx * 4 + 1]) * k[y];
+ ss2 += ((UINT8)imIn->image[y + ymin][xx * 4 + 2]) * k[y];
+ }
+ v = MAKE_UINT32(clip8(ss0), clip8(ss1), clip8(ss2), 0);
+ memcpy(imOut->image[yy] + xx * sizeof(v), &v, sizeof(v));
+ }
+ }
+ } else {
+ for (yy = 0; yy < imOut->ysize; yy++) {
+ k = &kk[yy * ksize];
+ ymin = bounds[yy * 2 + 0];
+ ymax = bounds[yy * 2 + 1];
+ for (xx = 0; xx < imOut->xsize; xx++) {
+ UINT32 v;
+ ss0 = ss1 = ss2 = ss3 = 1 << (PRECISION_BITS - 1);
+ for (y = 0; y < ymax; y++) {
+ ss0 += ((UINT8)imIn->image[y + ymin][xx * 4 + 0]) * k[y];
+ ss1 += ((UINT8)imIn->image[y + ymin][xx * 4 + 1]) * k[y];
+ ss2 += ((UINT8)imIn->image[y + ymin][xx * 4 + 2]) * k[y];
+ ss3 += ((UINT8)imIn->image[y + ymin][xx * 4 + 3]) * k[y];
+ }
+ v = MAKE_UINT32(clip8(ss0), clip8(ss1), clip8(ss2), clip8(ss3));
+ memcpy(imOut->image[yy] + xx * sizeof(v), &v, sizeof(v));
+ }
+ }
+ }
+ }
+ ImagingSectionLeave(&cookie);
+}
+
+void
+ImagingResampleHorizontal_32bpc(
+ Imaging imOut, Imaging imIn, int offset, int ksize, int *bounds, double *kk) {
+ ImagingSectionCookie cookie;
+ double ss;
+ int xx, yy, x, xmin, xmax;
+ double *k;
+
+ ImagingSectionEnter(&cookie);
+ switch (imIn->type) {
+ case IMAGING_TYPE_INT32:
+ for (yy = 0; yy < imOut->ysize; yy++) {
+ for (xx = 0; xx < imOut->xsize; xx++) {
+ xmin = bounds[xx * 2 + 0];
+ xmax = bounds[xx * 2 + 1];
+ k = &kk[xx * ksize];
+ ss = 0.0;
+ for (x = 0; x < xmax; x++) {
+ ss += IMAGING_PIXEL_I(imIn, x + xmin, yy + offset) * k[x];
+ }
+ IMAGING_PIXEL_I(imOut, xx, yy) = ROUND_UP(ss);
+ }
+ }
+ break;
+
+ case IMAGING_TYPE_FLOAT32:
+ for (yy = 0; yy < imOut->ysize; yy++) {
+ for (xx = 0; xx < imOut->xsize; xx++) {
+ xmin = bounds[xx * 2 + 0];
+ xmax = bounds[xx * 2 + 1];
+ k = &kk[xx * ksize];
+ ss = 0.0;
+ for (x = 0; x < xmax; x++) {
+ ss += IMAGING_PIXEL_F(imIn, x + xmin, yy + offset) * k[x];
+ }
+ IMAGING_PIXEL_F(imOut, xx, yy) = ss;
+ }
+ }
+ break;
+ }
+ ImagingSectionLeave(&cookie);
+}
+
+void
+ImagingResampleVertical_32bpc(
+ Imaging imOut, Imaging imIn, int offset, int ksize, int *bounds, double *kk) {
+ ImagingSectionCookie cookie;
+ double ss;
+ int xx, yy, y, ymin, ymax;
+ double *k;
+
+ ImagingSectionEnter(&cookie);
+ switch (imIn->type) {
+ case IMAGING_TYPE_INT32:
+ for (yy = 0; yy < imOut->ysize; yy++) {
+ ymin = bounds[yy * 2 + 0];
+ ymax = bounds[yy * 2 + 1];
+ k = &kk[yy * ksize];
+ for (xx = 0; xx < imOut->xsize; xx++) {
+ ss = 0.0;
+ for (y = 0; y < ymax; y++) {
+ ss += IMAGING_PIXEL_I(imIn, xx, y + ymin) * k[y];
+ }
+ IMAGING_PIXEL_I(imOut, xx, yy) = ROUND_UP(ss);
+ }
+ }
+ break;
+
+ case IMAGING_TYPE_FLOAT32:
+ for (yy = 0; yy < imOut->ysize; yy++) {
+ ymin = bounds[yy * 2 + 0];
+ ymax = bounds[yy * 2 + 1];
+ k = &kk[yy * ksize];
+ for (xx = 0; xx < imOut->xsize; xx++) {
+ ss = 0.0;
+ for (y = 0; y < ymax; y++) {
+ ss += IMAGING_PIXEL_F(imIn, xx, y + ymin) * k[y];
+ }
+ IMAGING_PIXEL_F(imOut, xx, yy) = ss;
+ }
+ }
+ break;
+ }
+ ImagingSectionLeave(&cookie);
+}
+
+typedef void (*ResampleFunction)(
+ Imaging imOut, Imaging imIn, int offset, int ksize, int *bounds, double *kk);
+
+Imaging
+ImagingResampleInner(
+ Imaging imIn,
+ int xsize,
+ int ysize,
+ struct filter *filterp,
+ float box[4],
+ ResampleFunction ResampleHorizontal,
+ ResampleFunction ResampleVertical);
+
+Imaging
+ImagingResample(Imaging imIn, int xsize, int ysize, int filter, float box[4]) {
+ struct filter *filterp;
+ ResampleFunction ResampleHorizontal;
+ ResampleFunction ResampleVertical;
+
+ if (strcmp(imIn->mode, "P") == 0 || strcmp(imIn->mode, "1") == 0) {
+ return (Imaging)ImagingError_ModeError();
+ }
+
+ if (imIn->type == IMAGING_TYPE_SPECIAL) {
+ return (Imaging)ImagingError_ModeError();
+ } else if (imIn->image8) {
+ ResampleHorizontal = ImagingResampleHorizontal_8bpc;
+ ResampleVertical = ImagingResampleVertical_8bpc;
+ } else {
+ switch (imIn->type) {
+ case IMAGING_TYPE_UINT8:
+ ResampleHorizontal = ImagingResampleHorizontal_8bpc;
+ ResampleVertical = ImagingResampleVertical_8bpc;
+ break;
+ case IMAGING_TYPE_INT32:
+ case IMAGING_TYPE_FLOAT32:
+ ResampleHorizontal = ImagingResampleHorizontal_32bpc;
+ ResampleVertical = ImagingResampleVertical_32bpc;
+ break;
+ default:
+ return (Imaging)ImagingError_ModeError();
+ }
+ }
+
+ /* check filter */
+ switch (filter) {
+ case IMAGING_TRANSFORM_BOX:
+ filterp = &BOX;
+ break;
+ case IMAGING_TRANSFORM_BILINEAR:
+ filterp = &BILINEAR;
+ break;
+ case IMAGING_TRANSFORM_HAMMING:
+ filterp = &HAMMING;
+ break;
+ case IMAGING_TRANSFORM_BICUBIC:
+ filterp = &BICUBIC;
+ break;
+ case IMAGING_TRANSFORM_LANCZOS:
+ filterp = &LANCZOS;
+ break;
+ default:
+ return (Imaging)ImagingError_ValueError("unsupported resampling filter");
+ }
+
+ return ImagingResampleInner(
+ imIn, xsize, ysize, filterp, box, ResampleHorizontal, ResampleVertical);
+}
+
+Imaging
+ImagingResampleInner(
+ Imaging imIn,
+ int xsize,
+ int ysize,
+ struct filter *filterp,
+ float box[4],
+ ResampleFunction ResampleHorizontal,
+ ResampleFunction ResampleVertical) {
+ Imaging imTemp = NULL;
+ Imaging imOut = NULL;
+
+ int i, need_horizontal, need_vertical;
+ int ybox_first, ybox_last;
+ int ksize_horiz, ksize_vert;
+ int *bounds_horiz, *bounds_vert;
+ double *kk_horiz, *kk_vert;
+
+ need_horizontal = xsize != imIn->xsize || box[0] || box[2] != xsize;
+ need_vertical = ysize != imIn->ysize || box[1] || box[3] != ysize;
+
+ ksize_horiz = precompute_coeffs(
+ imIn->xsize, box[0], box[2], xsize, filterp, &bounds_horiz, &kk_horiz);
+ if (!ksize_horiz) {
+ return NULL;
+ }
+
+ ksize_vert = precompute_coeffs(
+ imIn->ysize, box[1], box[3], ysize, filterp, &bounds_vert, &kk_vert);
+ if (!ksize_vert) {
+ free(bounds_horiz);
+ free(kk_horiz);
+ return NULL;
+ }
+
+ // First used row in the source image
+ ybox_first = bounds_vert[0];
+ // Last used row in the source image
+ ybox_last = bounds_vert[ysize * 2 - 2] + bounds_vert[ysize * 2 - 1];
+
+ /* two-pass resize, horizontal pass */
+ if (need_horizontal) {
+ // Shift bounds for vertical pass
+ for (i = 0; i < ysize; i++) {
+ bounds_vert[i * 2] -= ybox_first;
+ }
+
+ imTemp = ImagingNewDirty(imIn->mode, xsize, ybox_last - ybox_first);
+ if (imTemp) {
+ ResampleHorizontal(
+ imTemp, imIn, ybox_first, ksize_horiz, bounds_horiz, kk_horiz);
+ }
+ free(bounds_horiz);
+ free(kk_horiz);
+ if (!imTemp) {
+ free(bounds_vert);
+ free(kk_vert);
+ return NULL;
+ }
+ imOut = imIn = imTemp;
+ } else {
+ // Free in any case
+ free(bounds_horiz);
+ free(kk_horiz);
+ }
+
+ /* vertical pass */
+ if (need_vertical) {
+ imOut = ImagingNewDirty(imIn->mode, imIn->xsize, ysize);
+ if (imOut) {
+ /* imIn can be the original image or horizontally resampled one */
+ ResampleVertical(imOut, imIn, 0, ksize_vert, bounds_vert, kk_vert);
+ }
+ /* it's safe to call ImagingDelete with empty value
+ if previous step was not performed. */
+ ImagingDelete(imTemp);
+ free(bounds_vert);
+ free(kk_vert);
+ if (!imOut) {
+ return NULL;
+ }
+ } else {
+ // Free in any case
+ free(bounds_vert);
+ free(kk_vert);
+ }
+
+ /* none of the previous steps are performed, copying */
+ if (!imOut) {
+ imOut = ImagingCopy(imIn);
+ }
+
+ return imOut;
+}
diff --git a/contrib/python/Pillow/py3/libImaging/Sgi.h b/contrib/python/Pillow/py3/libImaging/Sgi.h
new file mode 100644
index 00000000000..797e5cbf9e1
--- /dev/null
+++ b/contrib/python/Pillow/py3/libImaging/Sgi.h
@@ -0,0 +1,39 @@
+/* Sgi.h */
+
+typedef struct {
+ /* CONFIGURATION */
+
+ /* Number of bytes per channel per pixel */
+ int bpc;
+
+ /* RLE offsets table */
+ UINT32 *starttab;
+
+ /* RLE lengths table */
+ UINT32 *lengthtab;
+
+ /* current row offset */
+ UINT32 rleoffset;
+
+ /* current row length */
+ UINT32 rlelength;
+
+ /* RLE table size */
+ int tablen;
+
+ /* RLE table index */
+ int tabindex;
+
+ /* buffer index */
+ int bufindex;
+
+ /* current row index */
+ int rowno;
+
+ /* current channel index */
+ int channo;
+
+ /* image data size from file descriptor */
+ long bufsize;
+
+} SGISTATE;
diff --git a/contrib/python/Pillow/py3/libImaging/SgiRleDecode.c b/contrib/python/Pillow/py3/libImaging/SgiRleDecode.c
new file mode 100644
index 00000000000..4eef44ba510
--- /dev/null
+++ b/contrib/python/Pillow/py3/libImaging/SgiRleDecode.c
@@ -0,0 +1,288 @@
+/*
+ * The Python Imaging Library.
+ * $Id$
+ *
+ * decoder for Sgi RLE data.
+ *
+ * history:
+ * 2017-07-28 mb fixed for images larger than 64KB
+ * 2017-07-20 mb created
+ *
+ * Copyright (c) Mickael Bonfill 2017.
+ *
+ * See the README file for information on usage and redistribution.
+ */
+
+#include "Imaging.h"
+#include "Sgi.h"
+
+#define SGI_HEADER_SIZE 512
+#define RLE_COPY_FLAG 0x80
+#define RLE_MAX_RUN 0x7f
+
+static void
+read4B(UINT32 *dest, UINT8 *buf) {
+ *dest = (UINT32)((buf[0] << 24) | (buf[1] << 16) | (buf[2] << 8) | buf[3]);
+}
+
+/*
+ SgiRleDecoding is done in a single channel row oriented set of RLE chunks.
+
+ * The file is arranged as
+ - SGI Header
+ - Rle Offset Table
+ - Rle Length Table
+ - Scanline Data
+
+ * Each RLE atom is c->bpc bytes wide (1 or 2)
+
+ * Each RLE Chunk is [specifier atom] [ 1 or n data atoms ]
+
+ * Copy Atoms are a byte with the high bit set, and the low 7 are
+ the number of bytes to copy from the source to the
+ destination. e.g.
+
+ CBBBBBBBB or 0CHLHLHLHLHLHL (B=byte, H/L = Hi low bytes)
+
+ * Run atoms do not have the high bit set, and the low 7 bits are
+ the number of copies of the next atom to copy to the
+ destination. e.g.:
+
+ RB -> BBBBB or RHL -> HLHLHLHLHL
+
+ The upshot of this is, there is no way to determine the required
+ length of the input buffer from reloffset and rlelength without
+ going through the data at that scan line.
+
+ Furthermore, there's no requirement that individual scan lines
+ pointed to from the rleoffset table are in any sort of order or
+ used only once, or even disjoint. There's also no requirement that
+ all of the data in the scan line area of the image file be used
+
+ */
+static int
+expandrow(UINT8 *dest, UINT8 *src, int n, int z, int xsize, UINT8 *end_of_buffer) {
+ /*
+ * n here is the number of rlechunks
+ * z is the number of channels, for calculating the interleave
+ * offset to go to RGBA style pixels
+ * xsize is the row width
+ * end_of_buffer is the address of the end of the input buffer
+ */
+
+ UINT8 pixel, count;
+ int x = 0;
+
+ for (; n > 0; n--) {
+ if (src > end_of_buffer) {
+ return -1;
+ }
+ pixel = *src++;
+ if (n == 1 && pixel != 0) {
+ return n;
+ }
+ count = pixel & RLE_MAX_RUN;
+ if (!count) {
+ return count;
+ }
+ if (x + count > xsize) {
+ return -1;
+ }
+ x += count;
+ if (pixel & RLE_COPY_FLAG) {
+ if (src + count > end_of_buffer) {
+ return -1;
+ }
+ while (count--) {
+ *dest = *src++;
+ dest += z;
+ }
+
+ } else {
+ if (src > end_of_buffer) {
+ return -1;
+ }
+ pixel = *src++;
+ while (count--) {
+ *dest = pixel;
+ dest += z;
+ }
+ }
+ }
+ return 0;
+}
+
+static int
+expandrow2(UINT8 *dest, const UINT8 *src, int n, int z, int xsize, UINT8 *end_of_buffer) {
+ UINT8 pixel, count;
+ int x = 0;
+
+ for (; n > 0; n--) {
+ if (src + 1 > end_of_buffer) {
+ return -1;
+ }
+ pixel = src[1];
+ src += 2;
+ if (n == 1 && pixel != 0) {
+ return n;
+ }
+ count = pixel & RLE_MAX_RUN;
+ if (!count) {
+ return count;
+ }
+ if (x + count > xsize) {
+ return -1;
+ }
+ x += count;
+ if (pixel & RLE_COPY_FLAG) {
+ if (src + 2 * count > end_of_buffer) {
+ return -1;
+ }
+ while (count--) {
+ memcpy(dest, src, 2);
+ src += 2;
+ dest += z * 2;
+ }
+ } else {
+ if (src + 2 > end_of_buffer) {
+ return -1;
+ }
+ while (count--) {
+ memcpy(dest, src, 2);
+ dest += z * 2;
+ }
+ src += 2;
+ }
+ }
+ return 0;
+}
+
+int
+ImagingSgiRleDecode(Imaging im, ImagingCodecState state, UINT8 *buf, Py_ssize_t bytes) {
+ UINT8 *ptr;
+ SGISTATE *c;
+ int err = 0;
+ int status;
+
+ /* size check */
+ if (im->xsize > INT_MAX / im->bands || im->ysize > INT_MAX / im->bands) {
+ state->errcode = IMAGING_CODEC_MEMORY;
+ return -1;
+ }
+
+ /* Get all data from File descriptor */
+ c = (SGISTATE *)state->context;
+ _imaging_seek_pyFd(state->fd, 0L, SEEK_END);
+ c->bufsize = _imaging_tell_pyFd(state->fd);
+ c->bufsize -= SGI_HEADER_SIZE;
+
+ c->tablen = im->bands * im->ysize;
+ /* below, we populate the starttab and lentab into the bufsize,
+ each with 4 bytes per element of tablen
+ Check here before we allocate any memory
+ */
+ if (c->bufsize < 8 * c->tablen) {
+ state->errcode = IMAGING_CODEC_OVERRUN;
+ return -1;
+ }
+
+ ptr = malloc(sizeof(UINT8) * c->bufsize);
+ if (!ptr) {
+ state->errcode = IMAGING_CODEC_MEMORY;
+ return -1;
+ }
+ _imaging_seek_pyFd(state->fd, SGI_HEADER_SIZE, SEEK_SET);
+ if (_imaging_read_pyFd(state->fd, (char *)ptr, c->bufsize) != c->bufsize) {
+ state->errcode = IMAGING_CODEC_UNKNOWN;
+ return -1;
+ }
+
+
+ /* decoder initialization */
+ state->count = 0;
+ state->y = 0;
+ if (state->ystep < 0) {
+ state->y = im->ysize - 1;
+ } else {
+ state->ystep = 1;
+ }
+
+ /* Allocate memory for RLE tables and rows */
+ free(state->buffer);
+ state->buffer = NULL;
+ /* malloc overflow check above */
+ state->buffer = calloc(im->xsize * im->bands, sizeof(UINT8) * 2);
+ c->starttab = calloc(c->tablen, sizeof(UINT32));
+ c->lengthtab = calloc(c->tablen, sizeof(UINT32));
+ if (!state->buffer || !c->starttab || !c->lengthtab) {
+ err = IMAGING_CODEC_MEMORY;
+ goto sgi_finish_decode;
+ }
+ /* populate offsets table */
+ for (c->tabindex = 0, c->bufindex = 0; c->tabindex < c->tablen;
+ c->tabindex++, c->bufindex += 4) {
+ read4B(&c->starttab[c->tabindex], &ptr[c->bufindex]);
+ }
+ /* populate lengths table */
+ for (c->tabindex = 0, c->bufindex = c->tablen * sizeof(UINT32);
+ c->tabindex < c->tablen;
+ c->tabindex++, c->bufindex += 4) {
+ read4B(&c->lengthtab[c->tabindex], &ptr[c->bufindex]);
+ }
+
+ /* read compressed rows */
+ for (c->rowno = 0; c->rowno < im->ysize; c->rowno++, state->y += state->ystep) {
+ for (c->channo = 0; c->channo < im->bands; c->channo++) {
+ c->rleoffset = c->starttab[c->rowno + c->channo * im->ysize];
+ c->rlelength = c->lengthtab[c->rowno + c->channo * im->ysize];
+
+ // Check for underflow of rleoffset-SGI_HEADER_SIZE
+ if (c->rleoffset < SGI_HEADER_SIZE) {
+ state->errcode = IMAGING_CODEC_OVERRUN;
+ goto sgi_finish_decode;
+ }
+
+ c->rleoffset -= SGI_HEADER_SIZE;
+
+ /* row decompression */
+ if (c->bpc == 1) {
+ status = expandrow(
+ &state->buffer[c->channo],
+ &ptr[c->rleoffset],
+ c->rlelength,
+ im->bands,
+ im->xsize,
+ &ptr[c->bufsize-1]);
+ } else {
+ status = expandrow2(
+ &state->buffer[c->channo * 2],
+ &ptr[c->rleoffset],
+ c->rlelength,
+ im->bands,
+ im->xsize,
+ &ptr[c->bufsize-1]);
+ }
+ if (status == -1) {
+ state->errcode = IMAGING_CODEC_OVERRUN;
+ goto sgi_finish_decode;
+ } else if (status == 1) {
+ goto sgi_finish_decode;
+ }
+
+ }
+
+ /* store decompressed data in image */
+ state->shuffle((UINT8 *)im->image[state->y], state->buffer, im->xsize);
+ }
+
+sgi_finish_decode:;
+
+ free(c->starttab);
+ free(c->lengthtab);
+ free(ptr);
+ if (err != 0) {
+ state->errcode = err;
+ return -1;
+ }
+ return 0;
+}
diff --git a/contrib/python/Pillow/py3/libImaging/Storage.c b/contrib/python/Pillow/py3/libImaging/Storage.c
new file mode 100644
index 00000000000..128595f6547
--- /dev/null
+++ b/contrib/python/Pillow/py3/libImaging/Storage.c
@@ -0,0 +1,577 @@
+/*
+ * The Python Imaging Library
+ * $Id$
+ *
+ * imaging storage object
+ *
+ * This baseline implementation is designed to efficiently handle
+ * large images, provided they fit into the available memory.
+ *
+ * history:
+ * 1995-06-15 fl Created
+ * 1995-09-12 fl Updated API, compiles silently under ANSI C++
+ * 1995-11-26 fl Compiles silently under Borland 4.5 as well
+ * 1996-05-05 fl Correctly test status from Prologue
+ * 1997-05-12 fl Increased THRESHOLD (to speed up Tk interface)
+ * 1997-05-30 fl Added support for floating point images
+ * 1997-11-17 fl Added support for "RGBX" images
+ * 1998-01-11 fl Added support for integer images
+ * 1998-03-05 fl Exported Prologue/Epilogue functions
+ * 1998-07-01 fl Added basic "YCrCb" support
+ * 1998-07-03 fl Attach palette in prologue for "P" images
+ * 1998-07-09 hk Don't report MemoryError on zero-size images
+ * 1998-07-12 fl Change "YCrCb" to "YCbCr" (!)
+ * 1998-10-26 fl Added "I;16" and "I;16B" storage modes (experimental)
+ * 1998-12-29 fl Fixed allocation bug caused by previous fix
+ * 1999-02-03 fl Added "RGBa" and "BGR" modes (experimental)
+ * 2001-04-22 fl Fixed potential memory leak in ImagingCopyPalette
+ * 2003-09-26 fl Added "LA" and "PA" modes (experimental)
+ * 2005-10-02 fl Added image counter
+ *
+ * Copyright (c) 1998-2005 by Secret Labs AB
+ * Copyright (c) 1995-2005 by Fredrik Lundh
+ *
+ * See the README file for information on usage and redistribution.
+ */
+
+#include "Imaging.h"
+#include <string.h>
+
+/* --------------------------------------------------------------------
+ * Standard image object.
+ */
+
+Imaging
+ImagingNewPrologueSubtype(const char *mode, int xsize, int ysize, int size) {
+ Imaging im;
+
+ /* linesize overflow check, roughly the current largest space req'd */
+ if (xsize > (INT_MAX / 4) - 1) {
+ return (Imaging)ImagingError_MemoryError();
+ }
+
+ im = (Imaging)calloc(1, size);
+ if (!im) {
+ return (Imaging)ImagingError_MemoryError();
+ }
+
+ /* Setup image descriptor */
+ im->xsize = xsize;
+ im->ysize = ysize;
+
+ im->type = IMAGING_TYPE_UINT8;
+
+ if (strcmp(mode, "1") == 0) {
+ /* 1-bit images */
+ im->bands = im->pixelsize = 1;
+ im->linesize = xsize;
+
+ } else if (strcmp(mode, "P") == 0) {
+ /* 8-bit palette mapped images */
+ im->bands = im->pixelsize = 1;
+ im->linesize = xsize;
+ im->palette = ImagingPaletteNew("RGB");
+
+ } else if (strcmp(mode, "PA") == 0) {
+ /* 8-bit palette with alpha */
+ im->bands = 2;
+ im->pixelsize = 4; /* store in image32 memory */
+ im->linesize = xsize * 4;
+ im->palette = ImagingPaletteNew("RGB");
+
+ } else if (strcmp(mode, "L") == 0) {
+ /* 8-bit greyscale (luminance) images */
+ im->bands = im->pixelsize = 1;
+ im->linesize = xsize;
+
+ } else if (strcmp(mode, "LA") == 0) {
+ /* 8-bit greyscale (luminance) with alpha */
+ im->bands = 2;
+ im->pixelsize = 4; /* store in image32 memory */
+ im->linesize = xsize * 4;
+
+ } else if (strcmp(mode, "La") == 0) {
+ /* 8-bit greyscale (luminance) with premultiplied alpha */
+ im->bands = 2;
+ im->pixelsize = 4; /* store in image32 memory */
+ im->linesize = xsize * 4;
+
+ } else if (strcmp(mode, "F") == 0) {
+ /* 32-bit floating point images */
+ im->bands = 1;
+ im->pixelsize = 4;
+ im->linesize = xsize * 4;
+ im->type = IMAGING_TYPE_FLOAT32;
+
+ } else if (strcmp(mode, "I") == 0) {
+ /* 32-bit integer images */
+ im->bands = 1;
+ im->pixelsize = 4;
+ im->linesize = xsize * 4;
+ im->type = IMAGING_TYPE_INT32;
+
+ } else if (
+ strcmp(mode, "I;16") == 0 || strcmp(mode, "I;16L") == 0 ||
+ strcmp(mode, "I;16B") == 0 || strcmp(mode, "I;16N") == 0) {
+ /* EXPERIMENTAL */
+ /* 16-bit raw integer images */
+ im->bands = 1;
+ im->pixelsize = 2;
+ im->linesize = xsize * 2;
+ im->type = IMAGING_TYPE_SPECIAL;
+
+ } else if (strcmp(mode, "RGB") == 0) {
+ /* 24-bit true colour images */
+ im->bands = 3;
+ im->pixelsize = 4;
+ im->linesize = xsize * 4;
+
+ } else if (strcmp(mode, "BGR;15") == 0) {
+ /* EXPERIMENTAL */
+ /* 15-bit reversed true colour */
+ im->bands = 3;
+ im->pixelsize = 2;
+ im->linesize = (xsize * 2 + 3) & -4;
+ im->type = IMAGING_TYPE_SPECIAL;
+
+ } else if (strcmp(mode, "BGR;16") == 0) {
+ /* EXPERIMENTAL */
+ /* 16-bit reversed true colour */
+ im->bands = 3;
+ im->pixelsize = 2;
+ im->linesize = (xsize * 2 + 3) & -4;
+ im->type = IMAGING_TYPE_SPECIAL;
+
+ } else if (strcmp(mode, "BGR;24") == 0) {
+ /* EXPERIMENTAL */
+ /* 24-bit reversed true colour */
+ im->bands = 3;
+ im->pixelsize = 3;
+ im->linesize = (xsize * 3 + 3) & -4;
+ im->type = IMAGING_TYPE_SPECIAL;
+
+ } else if (strcmp(mode, "RGBX") == 0) {
+ /* 32-bit true colour images with padding */
+ im->bands = im->pixelsize = 4;
+ im->linesize = xsize * 4;
+
+ } else if (strcmp(mode, "RGBA") == 0) {
+ /* 32-bit true colour images with alpha */
+ im->bands = im->pixelsize = 4;
+ im->linesize = xsize * 4;
+
+ } else if (strcmp(mode, "RGBa") == 0) {
+ /* 32-bit true colour images with premultiplied alpha */
+ im->bands = im->pixelsize = 4;
+ im->linesize = xsize * 4;
+
+ } else if (strcmp(mode, "CMYK") == 0) {
+ /* 32-bit colour separation */
+ im->bands = im->pixelsize = 4;
+ im->linesize = xsize * 4;
+
+ } else if (strcmp(mode, "YCbCr") == 0) {
+ /* 24-bit video format */
+ im->bands = 3;
+ im->pixelsize = 4;
+ im->linesize = xsize * 4;
+
+ } else if (strcmp(mode, "LAB") == 0) {
+ /* 24-bit color, luminance, + 2 color channels */
+ /* L is uint8, a,b are int8 */
+ im->bands = 3;
+ im->pixelsize = 4;
+ im->linesize = xsize * 4;
+
+ } else if (strcmp(mode, "HSV") == 0) {
+ /* 24-bit color, luminance, + 2 color channels */
+ /* L is uint8, a,b are int8 */
+ im->bands = 3;
+ im->pixelsize = 4;
+ im->linesize = xsize * 4;
+
+ } else {
+ free(im);
+ return (Imaging)ImagingError_ValueError("unrecognized image mode");
+ }
+
+ /* Setup image descriptor */
+ strcpy(im->mode, mode);
+
+ /* Pointer array (allocate at least one line, to avoid MemoryError
+ exceptions on platforms where calloc(0, x) returns NULL) */
+ im->image = (char **)calloc((ysize > 0) ? ysize : 1, sizeof(void *));
+
+ if (!im->image) {
+ free(im);
+ return (Imaging)ImagingError_MemoryError();
+ }
+
+ /* Initialize alias pointers to pixel data. */
+ switch (im->pixelsize) {
+ case 1:
+ case 2:
+ case 3:
+ im->image8 = (UINT8 **)im->image;
+ break;
+ case 4:
+ im->image32 = (INT32 **)im->image;
+ break;
+ }
+
+ ImagingDefaultArena.stats_new_count += 1;
+
+ return im;
+}
+
+Imaging
+ImagingNewPrologue(const char *mode, int xsize, int ysize) {
+ return ImagingNewPrologueSubtype(
+ mode, xsize, ysize, sizeof(struct ImagingMemoryInstance));
+}
+
+void
+ImagingDelete(Imaging im) {
+ if (!im) {
+ return;
+ }
+
+ if (im->palette) {
+ ImagingPaletteDelete(im->palette);
+ }
+
+ if (im->destroy) {
+ im->destroy(im);
+ }
+
+ if (im->image) {
+ free(im->image);
+ }
+
+ free(im);
+}
+
+/* Array Storage Type */
+/* ------------------ */
+/* Allocate image as an array of line buffers. */
+
+#define IMAGING_PAGE_SIZE (4096)
+
+struct ImagingMemoryArena ImagingDefaultArena = {
+ 1, // alignment
+ 16 * 1024 * 1024, // block_size
+ 0, // blocks_max
+ 0, // blocks_cached
+ NULL, // blocks_pool
+ 0,
+ 0,
+ 0,
+ 0,
+ 0 // Stats
+};
+
+int
+ImagingMemorySetBlocksMax(ImagingMemoryArena arena, int blocks_max) {
+ void *p;
+ /* Free already cached blocks */
+ ImagingMemoryClearCache(arena, blocks_max);
+
+ if (blocks_max == 0 && arena->blocks_pool != NULL) {
+ free(arena->blocks_pool);
+ arena->blocks_pool = NULL;
+ } else if (arena->blocks_pool != NULL) {
+ p = realloc(arena->blocks_pool, sizeof(*arena->blocks_pool) * blocks_max);
+ if (!p) {
+ // Leave previous blocks_max value
+ return 0;
+ }
+ arena->blocks_pool = p;
+ } else {
+ arena->blocks_pool = calloc(sizeof(*arena->blocks_pool), blocks_max);
+ if (!arena->blocks_pool) {
+ return 0;
+ }
+ }
+ arena->blocks_max = blocks_max;
+
+ return 1;
+}
+
+void
+ImagingMemoryClearCache(ImagingMemoryArena arena, int new_size) {
+ while (arena->blocks_cached > new_size) {
+ arena->blocks_cached -= 1;
+ free(arena->blocks_pool[arena->blocks_cached].ptr);
+ arena->stats_freed_blocks += 1;
+ }
+}
+
+ImagingMemoryBlock
+memory_get_block(ImagingMemoryArena arena, int requested_size, int dirty) {
+ ImagingMemoryBlock block = {NULL, 0};
+
+ if (arena->blocks_cached > 0) {
+ // Get block from cache
+ arena->blocks_cached -= 1;
+ block = arena->blocks_pool[arena->blocks_cached];
+ // Reallocate if needed
+ if (block.size != requested_size) {
+ block.ptr = realloc(block.ptr, requested_size);
+ }
+ if (!block.ptr) {
+ // Can't allocate, free previous pointer (it is still valid)
+ free(arena->blocks_pool[arena->blocks_cached].ptr);
+ arena->stats_freed_blocks += 1;
+ return block;
+ }
+ if (!dirty) {
+ memset(block.ptr, 0, requested_size);
+ }
+ arena->stats_reused_blocks += 1;
+ if (block.ptr != arena->blocks_pool[arena->blocks_cached].ptr) {
+ arena->stats_reallocated_blocks += 1;
+ }
+ } else {
+ if (dirty) {
+ block.ptr = malloc(requested_size);
+ } else {
+ block.ptr = calloc(1, requested_size);
+ }
+ arena->stats_allocated_blocks += 1;
+ }
+ block.size = requested_size;
+ return block;
+}
+
+void
+memory_return_block(ImagingMemoryArena arena, ImagingMemoryBlock block) {
+ if (arena->blocks_cached < arena->blocks_max) {
+ // Reduce block size
+ if (block.size > arena->block_size) {
+ block.size = arena->block_size;
+ block.ptr = realloc(block.ptr, arena->block_size);
+ }
+ arena->blocks_pool[arena->blocks_cached] = block;
+ arena->blocks_cached += 1;
+ } else {
+ free(block.ptr);
+ arena->stats_freed_blocks += 1;
+ }
+}
+
+static void
+ImagingDestroyArray(Imaging im) {
+ int y = 0;
+
+ if (im->blocks) {
+ while (im->blocks[y].ptr) {
+ memory_return_block(&ImagingDefaultArena, im->blocks[y]);
+ y += 1;
+ }
+ free(im->blocks);
+ }
+}
+
+Imaging
+ImagingAllocateArray(Imaging im, int dirty, int block_size) {
+ int y, line_in_block, current_block;
+ ImagingMemoryArena arena = &ImagingDefaultArena;
+ ImagingMemoryBlock block = {NULL, 0};
+ int aligned_linesize, lines_per_block, blocks_count;
+ char *aligned_ptr = NULL;
+
+ /* 0-width or 0-height image. No need to do anything */
+ if (!im->linesize || !im->ysize) {
+ return im;
+ }
+
+ aligned_linesize = (im->linesize + arena->alignment - 1) & -arena->alignment;
+ lines_per_block = (block_size - (arena->alignment - 1)) / aligned_linesize;
+ if (lines_per_block == 0) {
+ lines_per_block = 1;
+ }
+ blocks_count = (im->ysize + lines_per_block - 1) / lines_per_block;
+ // printf("NEW size: %dx%d, ls: %d, lpb: %d, blocks: %d\n",
+ // im->xsize, im->ysize, aligned_linesize, lines_per_block, blocks_count);
+
+ /* One extra pointer is always NULL */
+ im->blocks = calloc(sizeof(*im->blocks), blocks_count + 1);
+ if (!im->blocks) {
+ return (Imaging)ImagingError_MemoryError();
+ }
+
+ /* Allocate image as an array of lines */
+ line_in_block = 0;
+ current_block = 0;
+ for (y = 0; y < im->ysize; y++) {
+ if (line_in_block == 0) {
+ int required;
+ int lines_remaining = lines_per_block;
+ if (lines_remaining > im->ysize - y) {
+ lines_remaining = im->ysize - y;
+ }
+ required = lines_remaining * aligned_linesize + arena->alignment - 1;
+ block = memory_get_block(arena, required, dirty);
+ if (!block.ptr) {
+ ImagingDestroyArray(im);
+ return (Imaging)ImagingError_MemoryError();
+ }
+ im->blocks[current_block] = block;
+ /* Bulletproof code from libc _int_memalign */
+ aligned_ptr = (char *)(
+ ((size_t) (block.ptr + arena->alignment - 1)) &
+ -((Py_ssize_t) arena->alignment));
+ }
+
+ im->image[y] = aligned_ptr + aligned_linesize * line_in_block;
+
+ line_in_block += 1;
+ if (line_in_block >= lines_per_block) {
+ /* Reset counter and start new block */
+ line_in_block = 0;
+ current_block += 1;
+ }
+ }
+
+ im->destroy = ImagingDestroyArray;
+
+ return im;
+}
+
+/* Block Storage Type */
+/* ------------------ */
+/* Allocate image as a single block. */
+
+static void
+ImagingDestroyBlock(Imaging im) {
+ if (im->block) {
+ free(im->block);
+ }
+}
+
+Imaging
+ImagingAllocateBlock(Imaging im) {
+ Py_ssize_t y, i;
+
+ /* overflow check for malloc */
+ if (im->linesize && im->ysize > INT_MAX / im->linesize) {
+ return (Imaging)ImagingError_MemoryError();
+ }
+
+ if (im->ysize * im->linesize <= 0) {
+ /* some platforms return NULL for malloc(0); this fix
+ prevents MemoryError on zero-sized images on such
+ platforms */
+ im->block = (char *)malloc(1);
+ } else {
+ /* malloc check ok, overflow check above */
+ im->block = (char *)calloc(im->ysize, im->linesize);
+ }
+
+ if (!im->block) {
+ return (Imaging)ImagingError_MemoryError();
+ }
+
+ for (y = i = 0; y < im->ysize; y++) {
+ im->image[y] = im->block + i;
+ i += im->linesize;
+ }
+
+ im->destroy = ImagingDestroyBlock;
+
+ return im;
+}
+
+/* --------------------------------------------------------------------
+ * Create a new, internally allocated, image.
+ */
+
+Imaging
+ImagingNewInternal(const char *mode, int xsize, int ysize, int dirty) {
+ Imaging im;
+
+ if (xsize < 0 || ysize < 0) {
+ return (Imaging)ImagingError_ValueError("bad image size");
+ }
+
+ im = ImagingNewPrologue(mode, xsize, ysize);
+ if (!im) {
+ return NULL;
+ }
+
+ if (ImagingAllocateArray(im, dirty, ImagingDefaultArena.block_size)) {
+ return im;
+ }
+
+ ImagingError_Clear();
+
+ // Try to allocate the image once more with smallest possible block size
+ if (ImagingAllocateArray(im, dirty, IMAGING_PAGE_SIZE)) {
+ return im;
+ }
+
+ ImagingDelete(im);
+ return NULL;
+}
+
+Imaging
+ImagingNew(const char *mode, int xsize, int ysize) {
+ return ImagingNewInternal(mode, xsize, ysize, 0);
+}
+
+Imaging
+ImagingNewDirty(const char *mode, int xsize, int ysize) {
+ return ImagingNewInternal(mode, xsize, ysize, 1);
+}
+
+Imaging
+ImagingNewBlock(const char *mode, int xsize, int ysize) {
+ Imaging im;
+
+ if (xsize < 0 || ysize < 0) {
+ return (Imaging)ImagingError_ValueError("bad image size");
+ }
+
+ im = ImagingNewPrologue(mode, xsize, ysize);
+ if (!im) {
+ return NULL;
+ }
+
+ if (ImagingAllocateBlock(im)) {
+ return im;
+ }
+
+ ImagingDelete(im);
+ return NULL;
+}
+
+Imaging
+ImagingNew2Dirty(const char *mode, Imaging imOut, Imaging imIn) {
+ /* allocate or validate output image */
+
+ if (imOut) {
+ /* make sure images match */
+ if (strcmp(imOut->mode, mode) != 0 || imOut->xsize != imIn->xsize ||
+ imOut->ysize != imIn->ysize) {
+ return ImagingError_Mismatch();
+ }
+ } else {
+ /* create new image */
+ imOut = ImagingNewDirty(mode, imIn->xsize, imIn->ysize);
+ if (!imOut) {
+ return NULL;
+ }
+ }
+
+ return imOut;
+}
+
+void
+ImagingCopyPalette(Imaging destination, Imaging source) {
+ if (source->palette) {
+ if (destination->palette) {
+ ImagingPaletteDelete(destination->palette);
+ }
+ destination->palette = ImagingPaletteDuplicate(source->palette);
+ }
+}
diff --git a/contrib/python/Pillow/py3/libImaging/SunRleDecode.c b/contrib/python/Pillow/py3/libImaging/SunRleDecode.c
new file mode 100644
index 00000000000..9d8e1292a47
--- /dev/null
+++ b/contrib/python/Pillow/py3/libImaging/SunRleDecode.c
@@ -0,0 +1,139 @@
+/*
+ * THIS IS WORK IN PROGRESS
+ *
+ * The Python Imaging Library.
+ * $Id$
+ *
+ * decoder for SUN RLE data.
+ *
+ * history:
+ * 97-01-04 fl Created
+ *
+ * Copyright (c) Fredrik Lundh 1997.
+ * Copyright (c) Secret Labs AB 1997.
+ *
+ * See the README file for information on usage and redistribution.
+ */
+
+#include "Imaging.h"
+
+int
+ImagingSunRleDecode(Imaging im, ImagingCodecState state, UINT8 *buf, Py_ssize_t bytes) {
+ int n;
+ UINT8 *ptr;
+ UINT8 extra_data = 0;
+ UINT8 extra_bytes = 0;
+
+ ptr = buf;
+
+ for (;;) {
+ if (bytes < 1) {
+ return ptr - buf;
+ }
+
+ if (ptr[0] == 0x80) {
+ if (bytes < 2) {
+ break;
+ }
+
+ n = ptr[1];
+
+ if (n == 0) {
+ /* Literal 0x80 (2 bytes) */
+ n = 1;
+
+ state->buffer[state->x] = 0x80;
+
+ ptr += 2;
+ bytes -= 2;
+
+ } else {
+ /* Run (3 bytes) */
+ if (bytes < 3) {
+ break;
+ }
+
+ /* from (https://www.fileformat.info/format/sunraster/egff.htm)
+
+ For example, a run of 100 pixels with the value of
+ 0Ah would encode as the values 80h 64h 0Ah. A
+ single pixel value of 80h would encode as the
+ values 80h 00h. The four unencoded bytes 12345678h
+ would be stored in the RLE stream as 12h 34h 56h
+ 78h. 100 pixels, n=100, not 100 pixels, n=99.
+
+ But Wait! There's More!
+ (https://www.fileformat.info/format/sunraster/spec/598a59c4fac64c52897585d390d86360/view.htm)
+
+ If the first byte is 0x80, and the second byte is
+ not zero, the record is three bytes long. The
+ second byte is a count and the third byte is a
+ value. Output (count+1) pixels of that value.
+
+ 2 specs, same site, but Imagemagick and GIMP seem
+ to agree on the second one.
+ */
+ n += 1;
+
+ if (state->x + n > state->bytes) {
+ extra_bytes = n; /* full value */
+ n = state->bytes - state->x;
+ extra_bytes -= n;
+ extra_data = ptr[2];
+ }
+
+ memset(state->buffer + state->x, ptr[2], n);
+
+ ptr += 3;
+ bytes -= 3;
+ }
+
+ } else {
+ /* Literal byte */
+ n = 1;
+
+ state->buffer[state->x] = ptr[0];
+
+ ptr += 1;
+ bytes -= 1;
+ }
+
+ for (;;) {
+ state->x += n;
+
+ if (state->x >= state->bytes) {
+ /* Got a full line, unpack it */
+ state->shuffle(
+ (UINT8 *)im->image[state->y + state->yoff] +
+ state->xoff * im->pixelsize,
+ state->buffer,
+ state->xsize);
+
+ state->x = 0;
+
+ if (++state->y >= state->ysize) {
+ /* End of file (errcode = 0) */
+ return -1;
+ }
+ }
+
+ if (extra_bytes == 0) {
+ break;
+ }
+
+ if (state->x > 0) {
+ break; // assert
+ }
+
+ if (extra_bytes >= state->bytes) {
+ n = state->bytes;
+ } else {
+ n = extra_bytes;
+ }
+ memset(state->buffer + state->x, extra_data, n);
+ extra_bytes -= n;
+ }
+ }
+
+ return ptr - buf;
+}
diff --git a/contrib/python/Pillow/py3/libImaging/TgaRleDecode.c b/contrib/python/Pillow/py3/libImaging/TgaRleDecode.c
new file mode 100644
index 00000000000..95ae9b62228
--- /dev/null
+++ b/contrib/python/Pillow/py3/libImaging/TgaRleDecode.c
@@ -0,0 +1,129 @@
+/*
+ * The Python Imaging Library.
+ * $Id$
+ *
+ * decoder for Targa RLE data.
+ *
+ * history:
+ * 97-01-04 fl created
+ * 98-09-11 fl don't one byte per pixel; take orientation into account
+ *
+ * Copyright (c) Fredrik Lundh 1997.
+ * Copyright (c) Secret Labs AB 1997-98.
+ *
+ * See the README file for information on usage and redistribution.
+ */
+
+#include "Imaging.h"
+
+int
+ImagingTgaRleDecode(Imaging im, ImagingCodecState state, UINT8 *buf, Py_ssize_t bytes) {
+ int n, depth;
+ UINT8 *ptr;
+ int extra_bytes = 0;
+
+ ptr = buf;
+
+ if (state->state == 0) {
+ /* check image orientation */
+ if (state->ystep < 0) {
+ state->y = state->ysize - 1;
+ state->ystep = -1;
+ } else {
+ state->ystep = 1;
+ }
+
+ state->state = 1;
+ }
+
+ depth = state->count;
+
+ for (;;) {
+ if (bytes < 1) {
+ return ptr - buf;
+ }
+
+ n = depth * ((ptr[0] & 0x7f) + 1);
+ if (ptr[0] & 0x80) {
+ /* Run (1 + pixelsize bytes) */
+ if (bytes < 1 + depth) {
+ break;
+ }
+
+ if (state->x + n > state->bytes) {
+ state->errcode = IMAGING_CODEC_OVERRUN;
+ return -1;
+ }
+
+ if (depth == 1) {
+ memset(state->buffer + state->x, ptr[1], n);
+ } else {
+ int i;
+ for (i = 0; i < n; i += depth) {
+ memcpy(state->buffer + state->x + i, ptr + 1, depth);
+ }
+ }
+
+ ptr += 1 + depth;
+ bytes -= 1 + depth;
+ } else {
+ /* Literal (1+n+1 bytes block) */
+ if (bytes < 1 + n) {
+ break;
+ }
+
+ if (state->x + n > state->bytes) {
+ extra_bytes = n; /* full value */
+ n = state->bytes - state->x;
+ extra_bytes -= n;
+ }
+
+ memcpy(state->buffer + state->x, ptr + 1, n);
+
+ ptr += 1 + n;
+ bytes -= 1 + n;
+ }
+
+ for (;;) {
+ state->x += n;
+
+ if (state->x >= state->bytes) {
+ /* Got a full line, unpack it */
+ state->shuffle(
+ (UINT8 *)im->image[state->y + state->yoff] +
+ state->xoff * im->pixelsize,
+ state->buffer,
+ state->xsize);
+
+ state->x = 0;
+
+ state->y += state->ystep;
+
+ if (state->y < 0 || state->y >= state->ysize) {
+ /* End of file (errcode = 0) */
+ return -1;
+ }
+ }
+
+ if (extra_bytes == 0) {
+ break;
+ }
+
+ if (state->x > 0) {
+ break; // assert
+ }
+
+ if (extra_bytes >= state->bytes) {
+ n = state->bytes;
+ } else {
+ n = extra_bytes;
+ }
+ memcpy(state->buffer + state->x, ptr, n);
+ ptr += n;
+ bytes -= n;
+ extra_bytes -= n;
+ }
+ }
+
+ return ptr - buf;
+}
diff --git a/contrib/python/Pillow/py3/libImaging/TgaRleEncode.c b/contrib/python/Pillow/py3/libImaging/TgaRleEncode.c
new file mode 100644
index 00000000000..aa7e7b96d81
--- /dev/null
+++ b/contrib/python/Pillow/py3/libImaging/TgaRleEncode.c
@@ -0,0 +1,157 @@
+
+#include "Imaging.h"
+
+#include <assert.h>
+#include <string.h>
+
+static int
+comparePixels(const UINT8 *buf, int x, int bytesPerPixel) {
+ buf += x * bytesPerPixel;
+ return memcmp(buf, buf + bytesPerPixel, bytesPerPixel) == 0;
+}
+
+int
+ImagingTgaRleEncode(Imaging im, ImagingCodecState state, UINT8 *buf, int bytes) {
+ UINT8 *dst;
+ int bytesPerPixel;
+
+ if (state->state == 0) {
+ if (state->ystep < 0) {
+ state->ystep = -1;
+ state->y = state->ysize - 1;
+ } else {
+ state->ystep = 1;
+ }
+
+ state->state = 1;
+ }
+
+ dst = buf;
+ bytesPerPixel = (state->bits + 7) / 8;
+
+ while (1) {
+ int flushCount;
+
+ /*
+ * state->count is the numbers of bytes in the packet,
+ * excluding the 1-byte descriptor.
+ */
+ if (state->count == 0) {
+ UINT8 *row;
+ UINT8 descriptor;
+ int startX;
+
+ assert(state->x <= state->xsize);
+
+ /* Make sure we have space for the descriptor. */
+ if (bytes < 1) {
+ break;
+ }
+
+ if (state->x == state->xsize) {
+ state->x = 0;
+
+ state->y += state->ystep;
+ if (state->y < 0 || state->y >= state->ysize) {
+ state->errcode = IMAGING_CODEC_END;
+ break;
+ }
+ }
+
+ if (state->x == 0) {
+ state->shuffle(
+ state->buffer,
+ (UINT8 *)im->image[state->y + state->yoff] +
+ state->xoff * im->pixelsize,
+ state->xsize);
+ }
+
+ row = state->buffer;
+
+ /* Start with a raw packet for 1 px. */
+ descriptor = 0;
+ startX = state->x;
+ state->count = bytesPerPixel;
+
+ if (state->x + 1 < state->xsize) {
+ int maxLookup;
+ int isRaw;
+
+ isRaw = !comparePixels(row, state->x, bytesPerPixel);
+ ++state->x;
+
+ /*
+ * A packet can contain up to 128 pixels;
+ * 2 are already behind (state->x points to
+ * the second one).
+ */
+ maxLookup = state->x + 126;
+ /* A packet must not span multiple rows. */
+ if (maxLookup > state->xsize - 1) {
+ maxLookup = state->xsize - 1;
+ }
+
+ if (isRaw) {
+ while (state->x < maxLookup) {
+ if (!comparePixels(row, state->x, bytesPerPixel)) {
+ ++state->x;
+ } else {
+ /* Two identical pixels will go to RLE packet. */
+ --state->x;
+ break;
+ }
+ }
+
+ state->count += (state->x - startX) * bytesPerPixel;
+ } else {
+ descriptor |= 0x80;
+
+ while (state->x < maxLookup) {
+ if (comparePixels(row, state->x, bytesPerPixel)) {
+ ++state->x;
+ } else {
+ break;
+ }
+ }
+ }
+ }
+
+ /*
+ * state->x currently points to the last pixel to be
+ * included in the packet. The pixel count in the
+ * descriptor is 1 less than actual number of pixels in
+ * the packet, that is, state->x == startX if we encode
+ * only 1 pixel.
+ */
+ descriptor += state->x - startX;
+ *dst++ = descriptor;
+ --bytes;
+
+ /* Advance to past-the-last encoded pixel. */
+ ++state->x;
+ }
+
+ assert(bytes >= 0);
+ assert(state->count > 0);
+ assert(state->x > 0);
+ assert(state->count <= state->x * bytesPerPixel);
+
+ if (bytes == 0) {
+ break;
+ }
+
+ flushCount = state->count;
+ if (flushCount > bytes) {
+ flushCount = bytes;
+ }
+
+ memcpy(
+ dst, state->buffer + (state->x * bytesPerPixel - state->count), flushCount);
+ dst += flushCount;
+ bytes -= flushCount;
+
+ state->count -= flushCount;
+ }
+
+ return dst - buf;
+}
diff --git a/contrib/python/Pillow/py3/libImaging/TiffDecode.c b/contrib/python/Pillow/py3/libImaging/TiffDecode.c
new file mode 100644
index 00000000000..35122f18245
--- /dev/null
+++ b/contrib/python/Pillow/py3/libImaging/TiffDecode.c
@@ -0,0 +1,997 @@
+/*
+ * The Python Imaging Library.
+ * $Id: //modules/pil/libImaging/TiffDecode.c#1 $
+ *
+ * LibTiff-based Group3 and Group4 decoder
+ *
+ *
+ * started modding to use non-private tiff functions to port to libtiff 4.x
+ * eds 3/12/12
+ *
+ */
+
+#include "Imaging.h"
+
+#ifdef HAVE_LIBTIFF
+
+#ifndef uint
+#define uint uint32
+#endif
+
+#include "TiffDecode.h"
+
+/* Convert C file descriptor to WinApi HFILE if LibTiff was compiled with tif_win32.c
+ *
+ * This cast is safe, as the top 32-bits of HFILE are guaranteed to be zero,
+ * see
+ * https://learn.microsoft.com/en-us/windows/win32/winprog64/interprocess-communication
+ */
+#ifndef USE_WIN32_FILEIO
+#define fd_to_tiff_fd(fd) (fd)
+#else
+#define fd_to_tiff_fd(fd) ((int)_get_osfhandle(fd))
+#endif
+
+void
+dump_state(const TIFFSTATE *state) {
+ TRACE(
+ ("State: Location %u size %d eof %d data: %p ifd: %d\n",
+ (uint)state->loc,
+ (int)state->size,
+ (uint)state->eof,
+ state->data,
+ state->ifd));
+}
+
+/*
+ procs for TIFFOpenClient
+*/
+
+tsize_t
+_tiffReadProc(thandle_t hdata, tdata_t buf, tsize_t size) {
+ TIFFSTATE *state = (TIFFSTATE *)hdata;
+ tsize_t to_read;
+
+ TRACE(("_tiffReadProc: %d \n", (int)size));
+ dump_state(state);
+
+ if (state->loc > state->eof) {
+ TIFFError("_tiffReadProc", "Invalid Read at loc %" PRIu64 ", eof: %" PRIu64, state->loc, state->eof);
+ return 0;
+ }
+ to_read = min(size, min(state->size, (tsize_t)state->eof) - (tsize_t)state->loc);
+ TRACE(("to_read: %d\n", (int)to_read));
+
+ _TIFFmemcpy(buf, (UINT8 *)state->data + state->loc, to_read);
+ state->loc += (toff_t)to_read;
+
+ TRACE(("location: %u\n", (uint)state->loc));
+ return to_read;
+}
+
+tsize_t
+_tiffWriteProc(thandle_t hdata, tdata_t buf, tsize_t size) {
+ TIFFSTATE *state = (TIFFSTATE *)hdata;
+ tsize_t to_write;
+
+ TRACE(("_tiffWriteProc: %d \n", (int)size));
+ dump_state(state);
+
+ to_write = min(size, state->size - (tsize_t)state->loc);
+ if (state->flrealloc && size > to_write) {
+ tdata_t new_data;
+ tsize_t newsize = state->size;
+ while (newsize < (size + state->size)) {
+ if (newsize > INT_MAX - 64 * 1024) {
+ return 0;
+ }
+ newsize += 64 * 1024;
+ // newsize*=2; // UNDONE, by 64k chunks?
+ }
+ TRACE(("Reallocing in write to %d bytes\n", (int)newsize));
+ /* malloc check ok, overflow checked above */
+ new_data = realloc(state->data, newsize);
+ if (!new_data) {
+ // fail out
+ return 0;
+ }
+ state->data = new_data;
+ state->size = newsize;
+ to_write = size;
+ }
+
+ TRACE(("to_write: %d\n", (int)to_write));
+
+ _TIFFmemcpy((UINT8 *)state->data + state->loc, buf, to_write);
+ state->loc += (toff_t)to_write;
+ state->eof = max(state->loc, state->eof);
+
+ dump_state(state);
+ return to_write;
+}
+
+toff_t
+_tiffSeekProc(thandle_t hdata, toff_t off, int whence) {
+ TIFFSTATE *state = (TIFFSTATE *)hdata;
+
+ TRACE(("_tiffSeekProc: off: %u whence: %d \n", (uint)off, whence));
+ dump_state(state);
+ switch (whence) {
+ case 0:
+ state->loc = off;
+ break;
+ case 1:
+ state->loc += off;
+ break;
+ case 2:
+ state->loc = state->eof + off;
+ break;
+ }
+ dump_state(state);
+ return state->loc;
+}
+
+int
+_tiffCloseProc(thandle_t hdata) {
+ TIFFSTATE *state = (TIFFSTATE *)hdata;
+
+ TRACE(("_tiffCloseProc \n"));
+ dump_state(state);
+
+ return 0;
+}
+
+toff_t
+_tiffSizeProc(thandle_t hdata) {
+ TIFFSTATE *state = (TIFFSTATE *)hdata;
+
+ TRACE(("_tiffSizeProc \n"));
+ dump_state(state);
+
+ return (toff_t)state->size;
+}
+
+int
+_tiffMapProc(thandle_t hdata, tdata_t *pbase, toff_t *psize) {
+ TIFFSTATE *state = (TIFFSTATE *)hdata;
+
+ TRACE(("_tiffMapProc input size: %u, data: %p\n", (uint)*psize, *pbase));
+ dump_state(state);
+
+ *pbase = state->data;
+ *psize = state->size;
+ TRACE(("_tiffMapProc returning size: %u, data: %p\n", (uint)*psize, *pbase));
+ return (1);
+}
+
+int
+_tiffNullMapProc(thandle_t hdata, tdata_t *pbase, toff_t *psize) {
+ (void)hdata;
+ (void)pbase;
+ (void)psize;
+ return (0);
+}
+
+void
+_tiffUnmapProc(thandle_t hdata, tdata_t base, toff_t size) {
+ TRACE(("_tiffUnMapProc\n"));
+ (void)hdata;
+ (void)base;
+ (void)size;
+}
+
+int
+ImagingLibTiffInit(ImagingCodecState state, int fp, uint32_t offset) {
+ TIFFSTATE *clientstate = (TIFFSTATE *)state->context;
+
+ TRACE(("initing libtiff\n"));
+ TRACE(("filepointer: %d \n", fp));
+ TRACE(
+ ("State: count %d, state %d, x %d, y %d, ystep %d\n",
+ state->count,
+ state->state,
+ state->x,
+ state->y,
+ state->ystep));
+ TRACE(
+ ("State: xsize %d, ysize %d, xoff %d, yoff %d \n",
+ state->xsize,
+ state->ysize,
+ state->xoff,
+ state->yoff));
+ TRACE(("State: bits %d, bytes %d \n", state->bits, state->bytes));
+ TRACE(("State: context %p \n", state->context));
+
+ clientstate->loc = 0;
+ clientstate->size = 0;
+ clientstate->data = 0;
+ clientstate->fp = fp;
+ clientstate->ifd = offset;
+ clientstate->eof = 0;
+
+ return 1;
+}
+
+int
+_pickUnpackers(Imaging im, ImagingCodecState state, TIFF *tiff, uint16_t planarconfig, ImagingShuffler *unpackers) {
+ // if number of bands is 1, there is no difference with contig case
+ if (planarconfig == PLANARCONFIG_SEPARATE && im->bands > 1) {
+ uint16_t bits_per_sample = 8;
+
+ TIFFGetFieldDefaulted(tiff, TIFFTAG_BITSPERSAMPLE, &bits_per_sample);
+ if (bits_per_sample != 8 && bits_per_sample != 16) {
+ TRACE(("Invalid value for bits per sample: %d\n", bits_per_sample));
+ state->errcode = IMAGING_CODEC_BROKEN;
+ return -1;
+ }
+
+ // We'll pick appropriate set of unpackers depending on planar_configuration
+ // It does not matter if data is RGB(A), CMYK or LUV really,
+ // we just copy it plane by plane
+ unpackers[0] = ImagingFindUnpacker("RGBA", bits_per_sample == 16 ? "R;16N" : "R", NULL);
+ unpackers[1] = ImagingFindUnpacker("RGBA", bits_per_sample == 16 ? "G;16N" : "G", NULL);
+ unpackers[2] = ImagingFindUnpacker("RGBA", bits_per_sample == 16 ? "B;16N" : "B", NULL);
+ unpackers[3] = ImagingFindUnpacker("RGBA", bits_per_sample == 16 ? "A;16N" : "A", NULL);
+
+ return im->bands;
+ } else {
+ unpackers[0] = state->shuffle;
+
+ return 1;
+ }
+}
+
+int
+_decodeAsRGBA(Imaging im, ImagingCodecState state, TIFF *tiff) {
+ // To avoid dealing with YCbCr subsampling and other complications, let libtiff handle it
+ // Use a TIFFRGBAImage wrapping the tiff image, and let libtiff handle
+ // all of the conversion. Metadata read from the TIFFRGBAImage could
+ // be different from the metadata that the base tiff returns.
+
+ INT32 current_row;
+ UINT8 *new_data;
+ UINT32 rows_per_block, row_byte_size, rows_to_read;
+ int ret;
+ TIFFRGBAImage img;
+ char emsg[1024] = "";
+
+ // Since using TIFFRGBAImage* functions, we can read whole tiff into rastrr in one call
+ // Let's select smaller block size. Multiplying image width by (tile length OR rows per strip)
+ // gives us manageable block size in pixels
+ if (TIFFIsTiled(tiff)) {
+ ret = TIFFGetFieldDefaulted(tiff, TIFFTAG_TILELENGTH, &rows_per_block);
+ }
+ else {
+ ret = TIFFGetFieldDefaulted(tiff, TIFFTAG_ROWSPERSTRIP, &rows_per_block);
+ }
+
+ if (ret != 1 || rows_per_block==(UINT32)(-1)) {
+ rows_per_block = state->ysize;
+ }
+
+ TRACE(("RowsPerBlock: %u \n", rows_per_block));
+
+ if (!(TIFFRGBAImageOK(tiff, emsg) && TIFFRGBAImageBegin(&img, tiff, 0, emsg))) {
+ TRACE(("Decode error, msg: %s", emsg));
+ state->errcode = IMAGING_CODEC_BROKEN;
+ // nothing to clean up, just return
+ return -1;
+ }
+
+ img.req_orientation = ORIENTATION_TOPLEFT;
+ img.col_offset = 0;
+
+ /* overflow check for row byte size */
+ if (INT_MAX / 4 < img.width) {
+ state->errcode = IMAGING_CODEC_MEMORY;
+ goto decodergba_err;
+ }
+
+ // TiffRGBAImages are 32bits/pixel.
+ row_byte_size = img.width * 4;
+
+ /* overflow check for realloc */
+ if (INT_MAX / row_byte_size < rows_per_block) {
+ state->errcode = IMAGING_CODEC_MEMORY;
+ goto decodergba_err;
+ }
+
+ state->bytes = rows_per_block * row_byte_size;
+
+ TRACE(("BlockSize: %d \n", state->bytes));
+
+ /* realloc to fit whole strip */
+ /* malloc check above */
+ new_data = realloc(state->buffer, state->bytes);
+ if (!new_data) {
+ state->errcode = IMAGING_CODEC_MEMORY;
+ goto decodergba_err;
+ }
+
+ state->buffer = new_data;
+
+ for (; state->y < state->ysize; state->y += rows_per_block) {
+ img.row_offset = state->y;
+ rows_to_read = min(rows_per_block, img.height - state->y);
+
+ if (!TIFFRGBAImageGet(&img, (UINT32 *)state->buffer, img.width, rows_to_read)) {
+ TRACE(("Decode Error, y: %d\n", state->y));
+ state->errcode = IMAGING_CODEC_BROKEN;
+ goto decodergba_err;
+ }
+
+#if WORDS_BIGENDIAN
+ TIFFSwabArrayOfLong((UINT32 *)state->buffer, img.width * rows_to_read);
+#endif
+
+ TRACE(("Decoded strip for row %d \n", state->y));
+
+ // iterate over each row in the strip and stuff data into image
+ for (current_row = 0;
+ current_row < min((INT32)rows_per_block, state->ysize - state->y);
+ current_row++) {
+ TRACE(("Writing data into line %d ; \n", state->y + current_row));
+
+ // UINT8 * bbb = state->buffer + current_row * (state->bytes /
+ // rows_per_block); TRACE(("chars: %x %x %x %x\n", ((UINT8 *)bbb)[0],
+ // ((UINT8 *)bbb)[1], ((UINT8 *)bbb)[2], ((UINT8 *)bbb)[3]));
+
+ state->shuffle(
+ (UINT8 *)im->image[state->y + state->yoff + current_row] +
+ state->xoff * im->pixelsize,
+ state->buffer + current_row * row_byte_size,
+ state->xsize);
+ }
+ }
+
+decodergba_err:
+ TIFFRGBAImageEnd(&img);
+ if (state->errcode != 0) {
+ return -1;
+ }
+ return 0;
+}
+
+int
+_decodeTile(Imaging im, ImagingCodecState state, TIFF *tiff, int planes, ImagingShuffler *unpackers) {
+ INT32 x, y, tile_y, current_tile_length, current_tile_width;
+ UINT32 tile_width, tile_length;
+ tsize_t tile_bytes_size, row_byte_size;
+ UINT8 *new_data;
+
+ tile_bytes_size = TIFFTileSize(tiff);
+
+ if (tile_bytes_size == 0) {
+ TRACE(("Decode Error, Can not calculate TileSize\n"));
+ state->errcode = IMAGING_CODEC_BROKEN;
+ return -1;
+ }
+
+ row_byte_size = TIFFTileRowSize(tiff);
+
+ if (row_byte_size == 0 || row_byte_size > tile_bytes_size) {
+ TRACE(("Decode Error, Can not calculate TileRowSize\n"));
+ state->errcode = IMAGING_CODEC_BROKEN;
+ return -1;
+ }
+
+ /* overflow check for realloc */
+ if (tile_bytes_size > INT_MAX - 1) {
+ state->errcode = IMAGING_CODEC_MEMORY;
+ return -1;
+ }
+
+ TIFFGetField(tiff, TIFFTAG_TILEWIDTH, &tile_width);
+ TIFFGetField(tiff, TIFFTAG_TILELENGTH, &tile_length);
+
+ if (tile_width > INT_MAX || tile_length > INT_MAX) {
+ // state->x and state->y are ints
+ state->errcode = IMAGING_CODEC_MEMORY;
+ return -1;
+ }
+
+ if (tile_bytes_size > ((tile_length * state->bits / planes + 7) / 8) * tile_width) {
+ // If the tile size as expected by LibTiff isn't what we're expecting, abort.
+ // man: TIFFTileSize returns the equivalent size for a tile of data as it would be returned in a
+ // call to TIFFReadTile ...
+ state->errcode = IMAGING_CODEC_BROKEN;
+ return -1;
+ }
+
+ state->bytes = tile_bytes_size;
+
+ TRACE(("TIFFTileSize: %d\n", state->bytes));
+
+ /* realloc to fit whole tile */
+ /* malloc check above */
+ new_data = realloc(state->buffer, state->bytes);
+ if (!new_data) {
+ state->errcode = IMAGING_CODEC_MEMORY;
+ return -1;
+ }
+ state->buffer = new_data;
+
+ for (y = state->yoff; y < state->ysize; y += tile_length) {
+ int plane;
+ for (plane = 0; plane < planes; plane++) {
+ ImagingShuffler shuffler = unpackers[plane];
+ for (x = state->xoff; x < state->xsize; x += tile_width) {
+ if (TIFFReadTile(tiff, (tdata_t)state->buffer, x, y, 0, plane) == -1) {
+ TRACE(("Decode Error, Tile at %dx%d\n", x, y));
+ state->errcode = IMAGING_CODEC_BROKEN;
+ return -1;
+ }
+
+ TRACE(("Read tile at %dx%d; \n\n", x, y));
+
+ current_tile_width = min((INT32) tile_width, state->xsize - x);
+ current_tile_length = min((INT32) tile_length, state->ysize - y);
+ // iterate over each line in the tile and stuff data into image
+ for (tile_y = 0; tile_y < current_tile_length; tile_y++) {
+ TRACE(("Writing tile data at %dx%d using tile_width: %d; \n", tile_y + y, x, current_tile_width));
+
+ // UINT8 * bbb = state->buffer + tile_y * row_byte_size;
+ // TRACE(("chars: %x%x%x%x\n", ((UINT8 *)bbb)[0], ((UINT8 *)bbb)[1], ((UINT8 *)bbb)[2], ((UINT8 *)bbb)[3]));
+
+ shuffler((UINT8*) im->image[tile_y + y] + x * im->pixelsize,
+ state->buffer + tile_y * row_byte_size,
+ current_tile_width
+ );
+ }
+ }
+ }
+ }
+
+ return 0;
+}
+
+int
+_decodeStrip(Imaging im, ImagingCodecState state, TIFF *tiff, int planes, ImagingShuffler *unpackers) {
+ INT32 strip_row = 0;
+ UINT8 *new_data;
+ UINT32 rows_per_strip;
+ int ret;
+ tsize_t strip_size, row_byte_size, unpacker_row_byte_size;
+
+ ret = TIFFGetField(tiff, TIFFTAG_ROWSPERSTRIP, &rows_per_strip);
+ if (ret != 1 || rows_per_strip==(UINT32)(-1)) {
+ rows_per_strip = state->ysize;
+ }
+
+ if (rows_per_strip > INT_MAX) {
+ state->errcode = IMAGING_CODEC_MEMORY;
+ return -1;
+ }
+
+ TRACE(("RowsPerStrip: %u\n", rows_per_strip));
+
+ strip_size = TIFFStripSize(tiff);
+ if (strip_size > INT_MAX - 1) {
+ state->errcode = IMAGING_CODEC_MEMORY;
+ return -1;
+ }
+
+ unpacker_row_byte_size = (state->xsize * state->bits / planes + 7) / 8;
+ if (strip_size > (unpacker_row_byte_size * rows_per_strip)) {
+ // If the strip size as expected by LibTiff isn't what we're expecting, abort.
+ // man: TIFFStripSize returns the equivalent size for a strip of data as it would be returned in a
+ // call to TIFFReadEncodedStrip ...
+ state->errcode = IMAGING_CODEC_BROKEN;
+ return -1;
+ }
+
+ state->bytes = strip_size;
+
+ TRACE(("StripSize: %d \n", state->bytes));
+
+ row_byte_size = TIFFScanlineSize(tiff);
+
+ // if the unpacker calculated row size is > row byte size, (at least) the last
+ // row of the strip will have a read buffer overflow.
+ if (row_byte_size == 0 || unpacker_row_byte_size > row_byte_size) {
+ state->errcode = IMAGING_CODEC_BROKEN;
+ return -1;
+ }
+
+ TRACE(("RowsByteSize: %u \n", row_byte_size));
+
+ /* realloc to fit whole strip */
+ /* malloc check above */
+ new_data = realloc(state->buffer, state->bytes);
+ if (!new_data) {
+ state->errcode = IMAGING_CODEC_MEMORY;
+ return -1;
+ }
+
+ state->buffer = new_data;
+
+ for (; state->y < state->ysize; state->y += rows_per_strip) {
+ int plane;
+ for (plane = 0; plane < planes; plane++) {
+ ImagingShuffler shuffler = unpackers[plane];
+ if (TIFFReadEncodedStrip(tiff, TIFFComputeStrip(tiff, state->y, plane), (tdata_t)state->buffer, strip_size) == -1) {
+ TRACE(("Decode Error, strip %d\n", TIFFComputeStrip(tiff, state->y, 0)));
+ state->errcode = IMAGING_CODEC_BROKEN;
+ return -1;
+ }
+
+ TRACE(("Decoded strip for row %d \n", state->y));
+
+ // iterate over each row in the strip and stuff data into image
+ for (strip_row = 0;
+ strip_row < min((INT32) rows_per_strip, state->ysize - state->y);
+ strip_row++) {
+ TRACE(("Writing data into line %d ; \n", state->y + strip_row));
+
+ // UINT8 * bbb = state->buffer + strip_row * (state->bytes / rows_per_strip);
+ // TRACE(("chars: %x %x %x %x\n", ((UINT8 *)bbb)[0], ((UINT8 *)bbb)[1], ((UINT8 *)bbb)[2], ((UINT8 *)bbb)[3]));
+
+ shuffler(
+ (UINT8*) im->image[state->y + state->yoff + strip_row] +
+ state->xoff * im->pixelsize,
+ state->buffer + strip_row * row_byte_size,
+ state->xsize);
+ }
+ }
+ }
+
+ return 0;
+}
+
+int
+ImagingLibTiffDecode(
+ Imaging im, ImagingCodecState state, UINT8 *buffer, Py_ssize_t bytes) {
+ TIFFSTATE *clientstate = (TIFFSTATE *)state->context;
+ char *filename = "tempfile.tif";
+ char *mode = "rC";
+ TIFF *tiff;
+ uint16_t photometric = 0; // init to not PHOTOMETRIC_YCBCR
+ uint16_t compression;
+ int readAsRGBA = 0;
+ uint16_t planarconfig = 0;
+ int planes = 1;
+ ImagingShuffler unpackers[4];
+ INT32 img_width, img_height;
+
+ memset(unpackers, 0, sizeof(ImagingShuffler) * 4);
+
+ /* buffer is the encoded file, bytes is the length of the encoded file */
+ /* it all ends up in state->buffer, which is a uint8* from Imaging.h */
+
+ TRACE(("in decoder: bytes %d\n", bytes));
+ TRACE(
+ ("State: count %d, state %d, x %d, y %d, ystep %d\n",
+ state->count,
+ state->state,
+ state->x,
+ state->y,
+ state->ystep));
+ TRACE(
+ ("State: xsize %d, ysize %d, xoff %d, yoff %d \n",
+ state->xsize,
+ state->ysize,
+ state->xoff,
+ state->yoff));
+ TRACE(("State: bits %d, bytes %d \n", state->bits, state->bytes));
+ TRACE(
+ ("Buffer: %p: %c%c%c%c\n",
+ buffer,
+ (char)buffer[0],
+ (char)buffer[1],
+ (char)buffer[2],
+ (char)buffer[3]));
+ TRACE(
+ ("State->Buffer: %c%c%c%c\n",
+ (char)state->buffer[0],
+ (char)state->buffer[1],
+ (char)state->buffer[2],
+ (char)state->buffer[3]));
+ TRACE(
+ ("Image: mode %s, type %d, bands: %d, xsize %d, ysize %d \n",
+ im->mode,
+ im->type,
+ im->bands,
+ im->xsize,
+ im->ysize));
+ TRACE(
+ ("Image: image8 %p, image32 %p, image %p, block %p \n",
+ im->image8,
+ im->image32,
+ im->image,
+ im->block));
+ TRACE(("Image: pixelsize: %d, linesize %d \n", im->pixelsize, im->linesize));
+
+ dump_state(clientstate);
+ clientstate->size = bytes;
+ clientstate->eof = clientstate->size;
+ clientstate->loc = 0;
+ clientstate->data = (tdata_t)buffer;
+ clientstate->flrealloc = 0;
+ dump_state(clientstate);
+
+ TIFFSetWarningHandler(NULL);
+ TIFFSetWarningHandlerExt(NULL);
+
+ if (clientstate->fp) {
+ TRACE(("Opening using fd: %d\n", clientstate->fp));
+ lseek(clientstate->fp, 0, SEEK_SET); // Sometimes, I get it set to the end.
+ tiff = TIFFFdOpen(fd_to_tiff_fd(clientstate->fp), filename, mode);
+ } else {
+ TRACE(("Opening from string\n"));
+ tiff = TIFFClientOpen(
+ filename,
+ mode,
+ (thandle_t)clientstate,
+ _tiffReadProc,
+ _tiffWriteProc,
+ _tiffSeekProc,
+ _tiffCloseProc,
+ _tiffSizeProc,
+ _tiffMapProc,
+ _tiffUnmapProc);
+ }
+
+ if (!tiff) {
+ TRACE(("Error, didn't get the tiff\n"));
+ state->errcode = IMAGING_CODEC_BROKEN;
+ return -1;
+ }
+
+ if (clientstate->ifd) {
+ int rv;
+ uint32_t ifdoffset = clientstate->ifd;
+ TRACE(("reading tiff ifd %u\n", ifdoffset));
+ rv = TIFFSetSubDirectory(tiff, ifdoffset);
+ if (!rv) {
+ TRACE(("error in TIFFSetSubDirectory"));
+ goto decode_err;
+ }
+ }
+
+ TIFFGetField(tiff, TIFFTAG_IMAGEWIDTH, &img_width);
+ TIFFGetField(tiff, TIFFTAG_IMAGELENGTH, &img_height);
+
+ if (state->xsize != img_width || state->ysize != img_height) {
+ TRACE(
+ ("Inconsistent Image Error: %d =? %d, %d =? %d",
+ state->xsize,
+ img_width,
+ state->ysize,
+ img_height));
+ state->errcode = IMAGING_CODEC_BROKEN;
+ goto decode_err;
+ }
+
+
+ TIFFGetField(tiff, TIFFTAG_PHOTOMETRIC, &photometric);
+ TIFFGetField(tiff, TIFFTAG_COMPRESSION, &compression);
+ TIFFGetFieldDefaulted(tiff, TIFFTAG_PLANARCONFIG, &planarconfig);
+
+ // Dealing with YCbCr images is complicated in case if subsampling
+ // Let LibTiff read them as RGBA
+ readAsRGBA = photometric == PHOTOMETRIC_YCBCR;
+
+ if (readAsRGBA && compression == COMPRESSION_JPEG && planarconfig == PLANARCONFIG_CONTIG) {
+ // If using new JPEG compression, let libjpeg do RGB conversion for performance reasons
+ TIFFSetField(tiff, TIFFTAG_JPEGCOLORMODE, JPEGCOLORMODE_RGB);
+ readAsRGBA = 0;
+ }
+
+ if (readAsRGBA) {
+ _decodeAsRGBA(im, state, tiff);
+ }
+ else {
+ planes = _pickUnpackers(im, state, tiff, planarconfig, unpackers);
+ if (planes <= 0) {
+ goto decode_err;
+ }
+
+ if (TIFFIsTiled(tiff)) {
+ _decodeTile(im, state, tiff, planes, unpackers);
+ }
+ else {
+ _decodeStrip(im, state, tiff, planes, unpackers);
+ }
+
+ if (!state->errcode) {
+ // Check if raw mode was RGBa and it was stored on separate planes
+ // so we have to convert it to RGBA
+ if (planes > 3 && strcmp(im->mode, "RGBA") == 0) {
+ uint16_t extrasamples;
+ uint16_t* sampleinfo;
+ ImagingShuffler shuffle;
+ INT32 y;
+
+ TIFFGetFieldDefaulted(tiff, TIFFTAG_EXTRASAMPLES, &extrasamples, &sampleinfo);
+
+ if (extrasamples >= 1 &&
+ (sampleinfo[0] == EXTRASAMPLE_UNSPECIFIED || sampleinfo[0] == EXTRASAMPLE_ASSOCALPHA)
+ ) {
+ shuffle = ImagingFindUnpacker("RGBA", "RGBa", NULL);
+
+ for (y = state->yoff; y < state->ysize; y++) {
+ UINT8* ptr = (UINT8*) im->image[y + state->yoff] +
+ state->xoff * im->pixelsize;
+ shuffle(ptr, ptr, state->xsize);
+ }
+ }
+ }
+ }
+ }
+
+ decode_err:
+ // TIFFClose in libtiff calls tif_closeproc and TIFFCleanup
+ if (clientstate->fp) {
+ // Pillow will manage the closing of the file rather than libtiff
+ // So only call TIFFCleanup
+ TIFFCleanup(tiff);
+ } else {
+ // When tif_closeproc refers to our custom _tiffCloseProc though,
+ // that is fine, as it does not close the file
+ TIFFClose(tiff);
+ }
+ TRACE(("Done Decoding, Returning \n"));
+ // Returning -1 here to force ImageFile.load to break, rather than
+ // even think about looping back around.
+ return -1;
+}
+
+int
+ImagingLibTiffEncodeInit(ImagingCodecState state, char *filename, int fp) {
+ // Open the FD or the pointer as a tiff file, for writing.
+ // We may have to do some monkeying around to make this really work.
+ // If we have a fp, then we're good.
+ // If we have a memory string, we're probably going to have to malloc, then
+ // shuffle bytes into the writescanline process.
+ // Going to have to deal with the directory as well.
+
+ TIFFSTATE *clientstate = (TIFFSTATE *)state->context;
+ int bufsize = 64 * 1024;
+ char *mode = "w";
+
+ TRACE(("initing libtiff\n"));
+ TRACE(("Filename %s, filepointer: %d \n", filename, fp));
+ TRACE(
+ ("State: count %d, state %d, x %d, y %d, ystep %d\n",
+ state->count,
+ state->state,
+ state->x,
+ state->y,
+ state->ystep));
+ TRACE(
+ ("State: xsize %d, ysize %d, xoff %d, yoff %d \n",
+ state->xsize,
+ state->ysize,
+ state->xoff,
+ state->yoff));
+ TRACE(("State: bits %d, bytes %d \n", state->bits, state->bytes));
+ TRACE(("State: context %p \n", state->context));
+
+ clientstate->loc = 0;
+ clientstate->size = 0;
+ clientstate->eof = 0;
+ clientstate->data = 0;
+ clientstate->flrealloc = 0;
+ clientstate->fp = fp;
+
+ state->state = 0;
+
+ if (fp) {
+ TRACE(("Opening using fd: %d for writing \n", clientstate->fp));
+ clientstate->tiff = TIFFFdOpen(fd_to_tiff_fd(clientstate->fp), filename, mode);
+ } else {
+ // calloc a buffer to write the tif, we're going to need to realloc or something
+ // if we need bigger.
+ TRACE(("Opening a buffer for writing \n"));
+ /* calloc check ok, small constant allocation */
+ clientstate->data = calloc(bufsize, 1);
+ clientstate->size = bufsize;
+ clientstate->flrealloc = 1;
+
+ if (!clientstate->data) {
+ TRACE(("Error, couldn't allocate a buffer of size %d\n", bufsize));
+ return 0;
+ }
+
+ clientstate->tiff = TIFFClientOpen(
+ filename,
+ mode,
+ (thandle_t)clientstate,
+ _tiffReadProc,
+ _tiffWriteProc,
+ _tiffSeekProc,
+ _tiffCloseProc,
+ _tiffSizeProc,
+ _tiffNullMapProc,
+ _tiffUnmapProc); /*force no mmap*/
+ }
+
+ if (!clientstate->tiff) {
+ TRACE(("Error, couldn't open tiff file\n"));
+ return 0;
+ }
+
+ return 1;
+}
+
+int
+ImagingLibTiffMergeFieldInfo(
+ ImagingCodecState state, TIFFDataType field_type, int key, int is_var_length) {
+ // Refer to libtiff docs (http://www.simplesystems.org/libtiff/addingtags.html)
+ TIFFSTATE *clientstate = (TIFFSTATE *)state->context;
+ uint32_t n;
+ int status = 0;
+
+ // custom fields added with ImagingLibTiffMergeFieldInfo are only used for
+ // decoding, ignore readcount;
+ int readcount = is_var_length ? TIFF_VARIABLE : 1;
+ // we support writing a single value, or a variable number of values
+ int writecount = is_var_length ? TIFF_VARIABLE : 1;
+ // whether the first value should encode the number of values.
+ int passcount = (is_var_length && field_type != TIFF_ASCII) ? 1 : 0;
+
+ TIFFFieldInfo info[] = {
+ {key,
+ readcount,
+ writecount,
+ field_type,
+ FIELD_CUSTOM,
+ 1,
+ passcount,
+ "CustomField"}};
+
+ n = sizeof(info) / sizeof(info[0]);
+
+ // Test for libtiff 4.0 or later, excluding libtiff 3.9.6 and 3.9.7
+#if TIFFLIB_VERSION >= 20111221 && TIFFLIB_VERSION != 20120218 && \
+ TIFFLIB_VERSION != 20120922
+ status = TIFFMergeFieldInfo(clientstate->tiff, info, n);
+#else
+ TIFFMergeFieldInfo(clientstate->tiff, info, n);
+#endif
+ return status;
+}
+
+int
+ImagingLibTiffSetField(ImagingCodecState state, ttag_t tag, ...) {
+ // after tif_dir.c->TIFFSetField.
+ TIFFSTATE *clientstate = (TIFFSTATE *)state->context;
+ va_list ap;
+ int status;
+
+ va_start(ap, tag);
+ status = TIFFVSetField(clientstate->tiff, tag, ap);
+ va_end(ap);
+ return status;
+}
+
+int
+ImagingLibTiffEncode(Imaging im, ImagingCodecState state, UINT8 *buffer, int bytes) {
+ /* One shot encoder. Encode everything to the tiff in the clientstate.
+ If we're running off of a FD, then run once, we're good, everything
+ ends up in the file, we close and we're done.
+
+ If we're going to memory, then we need to write the whole file into memory, then
+ parcel it back out to the pystring buffer bytes at a time.
+
+ */
+
+ TIFFSTATE *clientstate = (TIFFSTATE *)state->context;
+ TIFF *tiff = clientstate->tiff;
+
+ TRACE(("in encoder: bytes %d\n", bytes));
+ TRACE(
+ ("State: count %d, state %d, x %d, y %d, ystep %d\n",
+ state->count,
+ state->state,
+ state->x,
+ state->y,
+ state->ystep));
+ TRACE(
+ ("State: xsize %d, ysize %d, xoff %d, yoff %d \n",
+ state->xsize,
+ state->ysize,
+ state->xoff,
+ state->yoff));
+ TRACE(("State: bits %d, bytes %d \n", state->bits, state->bytes));
+ TRACE(
+ ("Buffer: %p: %c%c%c%c\n",
+ buffer,
+ (char)buffer[0],
+ (char)buffer[1],
+ (char)buffer[2],
+ (char)buffer[3]));
+ TRACE(
+ ("State->Buffer: %c%c%c%c\n",
+ (char)state->buffer[0],
+ (char)state->buffer[1],
+ (char)state->buffer[2],
+ (char)state->buffer[3]));
+ TRACE(
+ ("Image: mode %s, type %d, bands: %d, xsize %d, ysize %d \n",
+ im->mode,
+ im->type,
+ im->bands,
+ im->xsize,
+ im->ysize));
+ TRACE(
+ ("Image: image8 %p, image32 %p, image %p, block %p \n",
+ im->image8,
+ im->image32,
+ im->image,
+ im->block));
+ TRACE(("Image: pixelsize: %d, linesize %d \n", im->pixelsize, im->linesize));
+
+ dump_state(clientstate);
+
+ if (state->state == 0) {
+ TRACE(("Encoding line by line"));
+ while (state->y < state->ysize) {
+ state->shuffle(
+ state->buffer,
+ (UINT8 *)im->image[state->y + state->yoff] +
+ state->xoff * im->pixelsize,
+ state->xsize);
+
+ if (TIFFWriteScanline(
+ tiff, (tdata_t)(state->buffer), (uint32_t)state->y, 0) == -1) {
+ TRACE(("Encode Error, row %d\n", state->y));
+ state->errcode = IMAGING_CODEC_BROKEN;
+ TIFFClose(tiff);
+ if (!clientstate->fp) {
+ free(clientstate->data);
+ }
+ return -1;
+ }
+ state->y++;
+ }
+
+ if (state->y == state->ysize) {
+ state->state = 1;
+
+ TRACE(("Flushing \n"));
+ if (!TIFFFlush(tiff)) {
+ TRACE(("Error flushing the tiff"));
+ // likely reason is memory.
+ state->errcode = IMAGING_CODEC_MEMORY;
+ TIFFClose(tiff);
+ if (!clientstate->fp) {
+ free(clientstate->data);
+ }
+ return -1;
+ }
+ TRACE(("Closing \n"));
+ TIFFClose(tiff);
+ // reset the clientstate metadata to use it to read out the buffer.
+ clientstate->loc = 0;
+ clientstate->size = clientstate->eof; // redundant?
+ }
+ }
+
+ if (state->state == 1 && !clientstate->fp) {
+ int read = (int)_tiffReadProc(clientstate, (tdata_t)buffer, (tsize_t)bytes);
+ TRACE(
+ ("Buffer: %p: %c%c%c%c\n",
+ buffer,
+ (char)buffer[0],
+ (char)buffer[1],
+ (char)buffer[2],
+ (char)buffer[3]));
+ if (clientstate->loc == clientstate->eof) {
+ TRACE(("Hit EOF, calling an end, freeing data"));
+ state->errcode = IMAGING_CODEC_END;
+ free(clientstate->data);
+ }
+ return read;
+ }
+
+ state->errcode = IMAGING_CODEC_END;
+ return 0;
+}
+
+const char *
+ImagingTiffVersion(void) {
+ return TIFFGetVersion();
+}
+
+#endif
diff --git a/contrib/python/Pillow/py3/libImaging/TiffDecode.h b/contrib/python/Pillow/py3/libImaging/TiffDecode.h
new file mode 100644
index 00000000000..c7c7d48ed02
--- /dev/null
+++ b/contrib/python/Pillow/py3/libImaging/TiffDecode.h
@@ -0,0 +1,66 @@
+/*
+ * The Python Imaging Library.
+ * $Id: //modules/pil/libImaging/Tiff.h#1 $
+ *
+ * declarations for the LibTiff-based Group3 and Group4 decoder
+ *
+ */
+
+#ifndef _TIFFIO_
+#include <tiffio.h>
+#endif
+#ifndef _TIFF_
+#include <tiff.h>
+#endif
+
+/* UNDONE -- what are we using from this? */
+/*#ifndef _UNISTD_H
+ # include <unistd.h>
+ # endif
+*/
+
+#ifndef min
+#define min(x, y) ((x > y) ? y : x)
+#define max(x, y) ((x < y) ? y : x)
+#endif
+
+#ifndef _PIL_LIBTIFF_
+#define _PIL_LIBTIFF_
+
+typedef struct {
+ tdata_t data; /* tdata_t == void* */
+ toff_t loc; /* toff_t == uint32 */
+ tsize_t size; /* tsize_t == int32 */
+ int fp;
+ uint32_t ifd; /* offset of the ifd, used for multipage
+ * Should be uint32 for libtiff 3.9.x
+ * uint64 for libtiff 4.0.x
+ */
+ TIFF *tiff; /* Used in write */
+ toff_t eof;
+ int flrealloc; /* may we realloc */
+} TIFFSTATE;
+
+extern int
+ImagingLibTiffInit(ImagingCodecState state, int fp, uint32_t offset);
+extern int
+ImagingLibTiffEncodeInit(ImagingCodecState state, char *filename, int fp);
+extern int
+ImagingLibTiffMergeFieldInfo(
+ ImagingCodecState state, TIFFDataType field_type, int key, int is_var_length);
+extern int
+ImagingLibTiffSetField(ImagingCodecState state, ttag_t tag, ...);
+
+/*
+ Trace debugging
+ legacy, don't enable for Python 3.x, unicode issues.
+*/
+
+/*
+#define VA_ARGS(...) __VA_ARGS__
+#define TRACE(args) fprintf(stderr, VA_ARGS args)
+*/
+
+#define TRACE(args)
+
+#endif
diff --git a/contrib/python/Pillow/py3/libImaging/Unpack.c b/contrib/python/Pillow/py3/libImaging/Unpack.c
new file mode 100644
index 00000000000..279bdcdc8ad
--- /dev/null
+++ b/contrib/python/Pillow/py3/libImaging/Unpack.c
@@ -0,0 +1,1821 @@
+/*
+ * The Python Imaging Library.
+ * $Id$
+ *
+ * code to unpack raw data from various file formats
+ *
+ * history:
+ * 1996-03-07 fl Created (from various decoders)
+ * 1996-04-19 fl Added band unpackers
+ * 1996-05-12 fl Published RGB unpackers
+ * 1996-05-27 fl Added nibble unpacker
+ * 1996-12-10 fl Added complete set of PNG unpackers
+ * 1996-12-29 fl Set alpha byte in RGB unpackers
+ * 1997-01-05 fl Added remaining TGA unpackers
+ * 1997-01-18 fl Added inverting band unpackers
+ * 1997-01-25 fl Added FlashPix unpackers
+ * 1997-05-31 fl Added floating point unpackers
+ * 1998-02-08 fl Added I unpacker
+ * 1998-07-01 fl Added YCbCr unpacker
+ * 1998-07-02 fl Added full set of integer unpackers
+ * 1998-12-29 fl Added mode field, I;16 unpackers
+ * 1998-12-30 fl Added RGBX modes
+ * 1999-02-04 fl Fixed I;16 unpackers
+ * 2003-05-13 fl Added L/RGB reversed unpackers
+ * 2003-09-26 fl Added LA/PA and RGBa->RGB unpackers
+ *
+ * Copyright (c) 1997-2003 by Secret Labs AB.
+ * Copyright (c) 1996-1997 by Fredrik Lundh.
+ *
+ * See the README file for information on usage and redistribution.
+ */
+
+#include "Imaging.h"
+#include "Convert.h"
+
+#define R 0
+#define G 1
+#define B 2
+#define X 3
+
+#define A 3
+
+#define C 0
+#define M 1
+#define Y 2
+#define K 3
+
+/* byte-swapping macros */
+
+#define C16N (tmp[0] = in[0], tmp[1] = in[1]);
+#define C16S (tmp[1] = in[0], tmp[0] = in[1]);
+#define C32N (tmp[0] = in[0], tmp[1] = in[1], tmp[2] = in[2], tmp[3] = in[3]);
+#define C32S (tmp[3] = in[0], tmp[2] = in[1], tmp[1] = in[2], tmp[0] = in[3]);
+#define C64N \
+ (tmp[0] = in[0], \
+ tmp[1] = in[1], \
+ tmp[2] = in[2], \
+ tmp[3] = in[3], \
+ tmp[4] = in[4], \
+ tmp[5] = in[5], \
+ tmp[6] = in[6], \
+ tmp[7] = in[7]);
+#define C64S \
+ (tmp[7] = in[0], \
+ tmp[6] = in[1], \
+ tmp[5] = in[2], \
+ tmp[4] = in[3], \
+ tmp[3] = in[4], \
+ tmp[2] = in[5], \
+ tmp[1] = in[6], \
+ tmp[0] = in[7]);
+
+#ifdef WORDS_BIGENDIAN
+#define C16B C16N
+#define C16L C16S
+#define C32B C32N
+#define C32L C32S
+#define C64B C64N
+#define C64L C64S
+#else
+#define C16B C16S
+#define C16L C16N
+#define C32B C32S
+#define C32L C32N
+#define C64B C64S
+#define C64L C64N
+#endif
+
+/* bit-swapping */
+
+static UINT8 BITFLIP[] = {
+ 0, 128, 64, 192, 32, 160, 96, 224, 16, 144, 80, 208, 48, 176, 112, 240,
+ 8, 136, 72, 200, 40, 168, 104, 232, 24, 152, 88, 216, 56, 184, 120, 248,
+ 4, 132, 68, 196, 36, 164, 100, 228, 20, 148, 84, 212, 52, 180, 116, 244,
+ 12, 140, 76, 204, 44, 172, 108, 236, 28, 156, 92, 220, 60, 188, 124, 252,
+ 2, 130, 66, 194, 34, 162, 98, 226, 18, 146, 82, 210, 50, 178, 114, 242,
+ 10, 138, 74, 202, 42, 170, 106, 234, 26, 154, 90, 218, 58, 186, 122, 250,
+ 6, 134, 70, 198, 38, 166, 102, 230, 22, 150, 86, 214, 54, 182, 118, 246,
+ 14, 142, 78, 206, 46, 174, 110, 238, 30, 158, 94, 222, 62, 190, 126, 254,
+ 1, 129, 65, 193, 33, 161, 97, 225, 17, 145, 81, 209, 49, 177, 113, 241,
+ 9, 137, 73, 201, 41, 169, 105, 233, 25, 153, 89, 217, 57, 185, 121, 249,
+ 5, 133, 69, 197, 37, 165, 101, 229, 21, 149, 85, 213, 53, 181, 117, 245,
+ 13, 141, 77, 205, 45, 173, 109, 237, 29, 157, 93, 221, 61, 189, 125, 253,
+ 3, 131, 67, 195, 35, 163, 99, 227, 19, 147, 83, 211, 51, 179, 115, 243,
+ 11, 139, 75, 203, 43, 171, 107, 235, 27, 155, 91, 219, 59, 187, 123, 251,
+ 7, 135, 71, 199, 39, 167, 103, 231, 23, 151, 87, 215, 55, 183, 119, 247,
+ 15, 143, 79, 207, 47, 175, 111, 239, 31, 159, 95, 223, 63, 191, 127, 255};
+
+/* Unpack to "1" image */
+
+static void
+unpack1(UINT8 *out, const UINT8 *in, int pixels) {
+ /* bits (msb first, white is non-zero) */
+ while (pixels > 0) {
+ UINT8 byte = *in++;
+ switch (pixels) {
+ default:
+ *out++ = (byte & 128) ? 255 : 0;
+ byte <<= 1;
+ case 7:
+ *out++ = (byte & 128) ? 255 : 0;
+ byte <<= 1;
+ case 6:
+ *out++ = (byte & 128) ? 255 : 0;
+ byte <<= 1;
+ case 5:
+ *out++ = (byte & 128) ? 255 : 0;
+ byte <<= 1;
+ case 4:
+ *out++ = (byte & 128) ? 255 : 0;
+ byte <<= 1;
+ case 3:
+ *out++ = (byte & 128) ? 255 : 0;
+ byte <<= 1;
+ case 2:
+ *out++ = (byte & 128) ? 255 : 0;
+ byte <<= 1;
+ case 1:
+ *out++ = (byte & 128) ? 255 : 0;
+ }
+ pixels -= 8;
+ }
+}
+
+static void
+unpack1I(UINT8 *out, const UINT8 *in, int pixels) {
+ /* bits (msb first, white is zero) */
+ while (pixels > 0) {
+ UINT8 byte = *in++;
+ switch (pixels) {
+ default:
+ *out++ = (byte & 128) ? 0 : 255;
+ byte <<= 1;
+ case 7:
+ *out++ = (byte & 128) ? 0 : 255;
+ byte <<= 1;
+ case 6:
+ *out++ = (byte & 128) ? 0 : 255;
+ byte <<= 1;
+ case 5:
+ *out++ = (byte & 128) ? 0 : 255;
+ byte <<= 1;
+ case 4:
+ *out++ = (byte & 128) ? 0 : 255;
+ byte <<= 1;
+ case 3:
+ *out++ = (byte & 128) ? 0 : 255;
+ byte <<= 1;
+ case 2:
+ *out++ = (byte & 128) ? 0 : 255;
+ byte <<= 1;
+ case 1:
+ *out++ = (byte & 128) ? 0 : 255;
+ }
+ pixels -= 8;
+ }
+}
+
+static void
+unpack1R(UINT8 *out, const UINT8 *in, int pixels) {
+ /* bits (lsb first, white is non-zero) */
+ while (pixels > 0) {
+ UINT8 byte = *in++;
+ switch (pixels) {
+ default:
+ *out++ = (byte & 1) ? 255 : 0;
+ byte >>= 1;
+ case 7:
+ *out++ = (byte & 1) ? 255 : 0;
+ byte >>= 1;
+ case 6:
+ *out++ = (byte & 1) ? 255 : 0;
+ byte >>= 1;
+ case 5:
+ *out++ = (byte & 1) ? 255 : 0;
+ byte >>= 1;
+ case 4:
+ *out++ = (byte & 1) ? 255 : 0;
+ byte >>= 1;
+ case 3:
+ *out++ = (byte & 1) ? 255 : 0;
+ byte >>= 1;
+ case 2:
+ *out++ = (byte & 1) ? 255 : 0;
+ byte >>= 1;
+ case 1:
+ *out++ = (byte & 1) ? 255 : 0;
+ }
+ pixels -= 8;
+ }
+}
+
+static void
+unpack1IR(UINT8 *out, const UINT8 *in, int pixels) {
+ /* bits (lsb first, white is zero) */
+ while (pixels > 0) {
+ UINT8 byte = *in++;
+ switch (pixels) {
+ default:
+ *out++ = (byte & 1) ? 0 : 255;
+ byte >>= 1;
+ case 7:
+ *out++ = (byte & 1) ? 0 : 255;
+ byte >>= 1;
+ case 6:
+ *out++ = (byte & 1) ? 0 : 255;
+ byte >>= 1;
+ case 5:
+ *out++ = (byte & 1) ? 0 : 255;
+ byte >>= 1;
+ case 4:
+ *out++ = (byte & 1) ? 0 : 255;
+ byte >>= 1;
+ case 3:
+ *out++ = (byte & 1) ? 0 : 255;
+ byte >>= 1;
+ case 2:
+ *out++ = (byte & 1) ? 0 : 255;
+ byte >>= 1;
+ case 1:
+ *out++ = (byte & 1) ? 0 : 255;
+ }
+ pixels -= 8;
+ }
+}
+
+static void
+unpack18(UINT8 *out, const UINT8 *in, int pixels) {
+ /* Unpack a '|b1' image, which is a numpy boolean.
+ 1 == true, 0==false, in bytes */
+
+ int i;
+ for (i = 0; i < pixels; i++) {
+ out[i] = in[i] > 0 ? 255 : 0;
+ }
+}
+
+/* Unpack to "L" image */
+
+static void
+unpackL2(UINT8 *out, const UINT8 *in, int pixels) {
+ /* nibbles (msb first, white is non-zero) */
+ while (pixels > 0) {
+ UINT8 byte = *in++;
+ switch (pixels) {
+ default:
+ *out++ = ((byte >> 6) & 0x03U) * 0x55U;
+ byte <<= 2;
+ case 3:
+ *out++ = ((byte >> 6) & 0x03U) * 0x55U;
+ byte <<= 2;
+ case 2:
+ *out++ = ((byte >> 6) & 0x03U) * 0x55U;
+ byte <<= 2;
+ case 1:
+ *out++ = ((byte >> 6) & 0x03U) * 0x55U;
+ }
+ pixels -= 4;
+ }
+}
+
+static void
+unpackL2I(UINT8 *out, const UINT8 *in, int pixels) {
+ /* nibbles (msb first, white is zero) */
+ while (pixels > 0) {
+ UINT8 byte = *in++;
+ switch (pixels) {
+ default:
+ *out++ = 0xFFU - (UINT8)(((byte >> 6) & 0x03U) * 0x55U);
+ byte <<= 2;
+ case 3:
+ *out++ = 0xFFU - (UINT8)(((byte >> 6) & 0x03U) * 0x55U);
+ byte <<= 2;
+ case 2:
+ *out++ = 0xFFU - (UINT8)(((byte >> 6) & 0x03U) * 0x55U);
+ byte <<= 2;
+ case 1:
+ *out++ = 0xFFU - (UINT8)(((byte >> 6) & 0x03U) * 0x55U);
+ }
+ pixels -= 4;
+ }
+}
+
+static void
+unpackL2R(UINT8 *out, const UINT8 *in, int pixels) {
+ /* nibbles (bit order reversed, white is non-zero) */
+ while (pixels > 0) {
+ UINT8 byte = *in++;
+ byte = BITFLIP[byte];
+ switch (pixels) {
+ default:
+ *out++ = ((byte >> 6) & 0x03U) * 0x55U;
+ byte <<= 2;
+ case 3:
+ *out++ = ((byte >> 6) & 0x03U) * 0x55U;
+ byte <<= 2;
+ case 2:
+ *out++ = ((byte >> 6) & 0x03U) * 0x55U;
+ byte <<= 2;
+ case 1:
+ *out++ = ((byte >> 6) & 0x03U) * 0x55U;
+ }
+ pixels -= 4;
+ }
+}
+
+static void
+unpackL2IR(UINT8 *out, const UINT8 *in, int pixels) {
+ /* nibbles (bit order reversed, white is zero) */
+ while (pixels > 0) {
+ UINT8 byte = *in++;
+ byte = BITFLIP[byte];
+ switch (pixels) {
+ default:
+ *out++ = 0xFFU - (UINT8)(((byte >> 6) & 0x03U) * 0x55U);
+ byte <<= 2;
+ case 3:
+ *out++ = 0xFFU - (UINT8)(((byte >> 6) & 0x03U) * 0x55U);
+ byte <<= 2;
+ case 2:
+ *out++ = 0xFFU - (UINT8)(((byte >> 6) & 0x03U) * 0x55U);
+ byte <<= 2;
+ case 1:
+ *out++ = 0xFFU - (UINT8)(((byte >> 6) & 0x03U) * 0x55U);
+ }
+ pixels -= 4;
+ }
+}
+
+static void
+unpackL4(UINT8 *out, const UINT8 *in, int pixels) {
+ /* nibbles (msb first, white is non-zero) */
+ while (pixels > 0) {
+ UINT8 byte = *in++;
+ switch (pixels) {
+ default:
+ *out++ = ((byte >> 4) & 0x0FU) * 0x11U;
+ byte <<= 4;
+ case 1:
+ *out++ = ((byte >> 4) & 0x0FU) * 0x11U;
+ }
+ pixels -= 2;
+ }
+}
+
+static void
+unpackL4I(UINT8 *out, const UINT8 *in, int pixels) {
+ /* nibbles (msb first, white is zero) */
+ while (pixels > 0) {
+ UINT8 byte = *in++;
+ switch (pixels) {
+ default:
+ *out++ = 0xFFU - (UINT8)(((byte >> 4) & 0x0FU) * 0x11U);
+ byte <<= 4;
+ case 1:
+ *out++ = 0xFFU - (UINT8)(((byte >> 4) & 0x0FU) * 0x11U);
+ }
+ pixels -= 2;
+ }
+}
+
+static void
+unpackL4R(UINT8 *out, const UINT8 *in, int pixels) {
+ /* nibbles (bit order reversed, white is non-zero) */
+ while (pixels > 0) {
+ UINT8 byte = *in++;
+ byte = BITFLIP[byte];
+ switch (pixels) {
+ default:
+ *out++ = ((byte >> 4) & 0x0FU) * 0x11U;
+ byte <<= 4;
+ case 1:
+ *out++ = ((byte >> 4) & 0x0FU) * 0x11U;
+ }
+ pixels -= 2;
+ }
+}
+
+static void
+unpackL4IR(UINT8 *out, const UINT8 *in, int pixels) {
+ /* nibbles (bit order reversed, white is zero) */
+ while (pixels > 0) {
+ UINT8 byte = *in++;
+ byte = BITFLIP[byte];
+ switch (pixels) {
+ default:
+ *out++ = 0xFFU - (UINT8)(((byte >> 4) & 0x0FU) * 0x11U);
+ byte <<= 4;
+ case 1:
+ *out++ = 0xFFU - (UINT8)(((byte >> 4) & 0x0FU) * 0x11U);
+ }
+ pixels -= 2;
+ }
+}
+
+static void
+unpackLA(UINT8 *_out, const UINT8 *in, int pixels) {
+ int i;
+ /* LA, pixel interleaved */
+ for (i = 0; i < pixels; i++) {
+ UINT32 iv = MAKE_UINT32(in[0], in[0], in[0], in[1]);
+ memcpy(_out, &iv, sizeof(iv));
+ in += 2;
+ _out += 4;
+ }
+}
+
+static void
+unpackLAL(UINT8 *_out, const UINT8 *in, int pixels) {
+ int i;
+ /* LA, line interleaved */
+ for (i = 0; i < pixels; i++, _out += 4) {
+ UINT32 iv = MAKE_UINT32(in[i], in[i], in[i], in[i + pixels]);
+ memcpy(_out, &iv, sizeof(iv));
+ }
+}
+
+static void
+unpackLI(UINT8 *out, const UINT8 *in, int pixels) {
+ /* negative */
+ int i;
+ for (i = 0; i < pixels; i++) {
+ out[i] = ~in[i];
+ }
+}
+
+static void
+unpackLR(UINT8 *out, const UINT8 *in, int pixels) {
+ int i;
+ /* RGB, bit reversed */
+ for (i = 0; i < pixels; i++) {
+ out[i] = BITFLIP[in[i]];
+ }
+}
+
+static void
+unpackL16(UINT8 *out, const UINT8 *in, int pixels) {
+ /* int16 (upper byte, little endian) */
+ int i;
+ for (i = 0; i < pixels; i++) {
+ out[i] = in[1];
+ in += 2;
+ }
+}
+
+static void
+unpackL16B(UINT8 *out, const UINT8 *in, int pixels) {
+ int i;
+ /* int16 (upper byte, big endian) */
+ for (i = 0; i < pixels; i++) {
+ out[i] = in[0];
+ in += 2;
+ }
+}
+
+/* Unpack to "P" image */
+
+static void
+unpackP1(UINT8 *out, const UINT8 *in, int pixels) {
+ /* bits */
+ while (pixels > 0) {
+ UINT8 byte = *in++;
+ switch (pixels) {
+ default:
+ *out++ = (byte >> 7) & 1;
+ byte <<= 1;
+ case 7:
+ *out++ = (byte >> 7) & 1;
+ byte <<= 1;
+ case 6:
+ *out++ = (byte >> 7) & 1;
+ byte <<= 1;
+ case 5:
+ *out++ = (byte >> 7) & 1;
+ byte <<= 1;
+ case 4:
+ *out++ = (byte >> 7) & 1;
+ byte <<= 1;
+ case 3:
+ *out++ = (byte >> 7) & 1;
+ byte <<= 1;
+ case 2:
+ *out++ = (byte >> 7) & 1;
+ byte <<= 1;
+ case 1:
+ *out++ = (byte >> 7) & 1;
+ }
+ pixels -= 8;
+ }
+}
+
+static void
+unpackP2(UINT8 *out, const UINT8 *in, int pixels) {
+ /* bit pairs */
+ while (pixels > 0) {
+ UINT8 byte = *in++;
+ switch (pixels) {
+ default:
+ *out++ = (byte >> 6) & 3;
+ byte <<= 2;
+ case 3:
+ *out++ = (byte >> 6) & 3;
+ byte <<= 2;
+ case 2:
+ *out++ = (byte >> 6) & 3;
+ byte <<= 2;
+ case 1:
+ *out++ = (byte >> 6) & 3;
+ }
+ pixels -= 4;
+ }
+}
+
+static void
+unpackP4(UINT8 *out, const UINT8 *in, int pixels) {
+ /* nibbles */
+ while (pixels > 0) {
+ UINT8 byte = *in++;
+ switch (pixels) {
+ default:
+ *out++ = (byte >> 4) & 15;
+ byte <<= 4;
+ case 1:
+ *out++ = (byte >> 4) & 15;
+ }
+ pixels -= 2;
+ }
+}
+
+static void
+unpackP2L(UINT8 *out, const UINT8 *in, int pixels) {
+ int i, j, m, s;
+ /* bit layers */
+ m = 128;
+ s = (pixels + 7) / 8;
+ for (i = j = 0; i < pixels; i++) {
+ out[i] = ((in[j] & m) ? 1 : 0) + ((in[j + s] & m) ? 2 : 0);
+ if ((m >>= 1) == 0) {
+ m = 128;
+ j++;
+ }
+ }
+}
+
+static void
+unpackP4L(UINT8 *out, const UINT8 *in, int pixels) {
+ int i, j, m, s;
+ /* bit layers (trust the optimizer ;-) */
+ m = 128;
+ s = (pixels + 7) / 8;
+ for (i = j = 0; i < pixels; i++) {
+ out[i] = ((in[j] & m) ? 1 : 0) + ((in[j + s] & m) ? 2 : 0) +
+ ((in[j + 2 * s] & m) ? 4 : 0) + ((in[j + 3 * s] & m) ? 8 : 0);
+ if ((m >>= 1) == 0) {
+ m = 128;
+ j++;
+ }
+ }
+}
+
+/* Unpack to "RGB" image */
+
+void
+ImagingUnpackRGB(UINT8 *_out, const UINT8 *in, int pixels) {
+ int i = 0;
+ /* RGB triplets */
+ for (; i < pixels - 1; i++) {
+ UINT32 iv;
+ memcpy(&iv, in, sizeof(iv));
+ iv |= MASK_UINT32_CHANNEL_3;
+ memcpy(_out, &iv, sizeof(iv));
+ in += 3;
+ _out += 4;
+ }
+ for (; i < pixels; i++) {
+ UINT32 iv = MAKE_UINT32(in[0], in[1], in[2], 255);
+ memcpy(_out, &iv, sizeof(iv));
+ in += 3;
+ _out += 4;
+ }
+}
+
+void
+unpackRGB16L(UINT8 *_out, const UINT8 *in, int pixels) {
+ int i;
+ /* 16-bit RGB triplets, little-endian order */
+ for (i = 0; i < pixels; i++) {
+ UINT32 iv = MAKE_UINT32(in[1], in[3], in[5], 255);
+ memcpy(_out, &iv, sizeof(iv));
+ in += 6;
+ _out += 4;
+ }
+}
+
+void
+unpackRGB16B(UINT8 *_out, const UINT8 *in, int pixels) {
+ int i;
+ /* 16-bit RGB triplets, big-endian order */
+ for (i = 0; i < pixels; i++) {
+ UINT32 iv = MAKE_UINT32(in[0], in[2], in[4], 255);
+ memcpy(_out, &iv, sizeof(iv));
+ in += 6;
+ _out += 4;
+ }
+}
+
+static void
+unpackRGBL(UINT8 *_out, const UINT8 *in, int pixels) {
+ int i;
+ /* RGB, line interleaved */
+ for (i = 0; i < pixels; i++, _out += 4) {
+ UINT32 iv = MAKE_UINT32(in[i], in[i + pixels], in[i + pixels + pixels], 255);
+ memcpy(_out, &iv, sizeof(iv));
+ }
+}
+
+static void
+unpackRGBR(UINT8 *_out, const UINT8 *in, int pixels) {
+ int i;
+ /* RGB, bit reversed */
+ for (i = 0; i < pixels; i++) {
+ UINT32 iv = MAKE_UINT32(BITFLIP[in[0]], BITFLIP[in[1]], BITFLIP[in[2]], 255);
+ memcpy(_out, &iv, sizeof(iv));
+ in += 3;
+ _out += 4;
+ }
+}
+
+void
+ImagingUnpackBGR(UINT8 *_out, const UINT8 *in, int pixels) {
+ int i;
+ /* RGB, reversed bytes */
+ for (i = 0; i < pixels; i++) {
+ UINT32 iv = MAKE_UINT32(in[2], in[1], in[0], 255);
+ memcpy(_out, &iv, sizeof(iv));
+ in += 3;
+ _out += 4;
+ }
+}
+
+void
+ImagingUnpackRGB15(UINT8 *out, const UINT8 *in, int pixels) {
+ int i, pixel;
+ /* RGB, 5 bits per pixel */
+ for (i = 0; i < pixels; i++) {
+ pixel = in[0] + (in[1] << 8);
+ out[R] = (pixel & 31) * 255 / 31;
+ out[G] = ((pixel >> 5) & 31) * 255 / 31;
+ out[B] = ((pixel >> 10) & 31) * 255 / 31;
+ out[A] = 255;
+ out += 4;
+ in += 2;
+ }
+}
+
+void
+ImagingUnpackRGBA15(UINT8 *out, const UINT8 *in, int pixels) {
+ int i, pixel;
+ /* RGB, 5/5/5/1 bits per pixel */
+ for (i = 0; i < pixels; i++) {
+ pixel = in[0] + (in[1] << 8);
+ out[R] = (pixel & 31) * 255 / 31;
+ out[G] = ((pixel >> 5) & 31) * 255 / 31;
+ out[B] = ((pixel >> 10) & 31) * 255 / 31;
+ out[A] = (pixel >> 15) * 255;
+ out += 4;
+ in += 2;
+ }
+}
+
+void
+ImagingUnpackBGR15(UINT8 *out, const UINT8 *in, int pixels) {
+ int i, pixel;
+ /* RGB, reversed bytes, 5 bits per pixel */
+ for (i = 0; i < pixels; i++) {
+ pixel = in[0] + (in[1] << 8);
+ out[B] = (pixel & 31) * 255 / 31;
+ out[G] = ((pixel >> 5) & 31) * 255 / 31;
+ out[R] = ((pixel >> 10) & 31) * 255 / 31;
+ out[A] = 255;
+ out += 4;
+ in += 2;
+ }
+}
+
+void
+ImagingUnpackBGRA15(UINT8 *out, const UINT8 *in, int pixels) {
+ int i, pixel;
+ /* RGB, rearranged channels, 5/5/5/1 bits per pixel */
+ for (i = 0; i < pixels; i++) {
+ pixel = in[0] + (in[1] << 8);
+ out[B] = (pixel & 31) * 255 / 31;
+ out[G] = ((pixel >> 5) & 31) * 255 / 31;
+ out[R] = ((pixel >> 10) & 31) * 255 / 31;
+ out[A] = (pixel >> 15) * 255;
+ out += 4;
+ in += 2;
+ }
+}
+
+void
+ImagingUnpackRGB16(UINT8 *out, const UINT8 *in, int pixels) {
+ int i, pixel;
+ /* RGB, 5/6/5 bits per pixel */
+ for (i = 0; i < pixels; i++) {
+ pixel = in[0] + (in[1] << 8);
+ out[R] = (pixel & 31) * 255 / 31;
+ out[G] = ((pixel >> 5) & 63) * 255 / 63;
+ out[B] = ((pixel >> 11) & 31) * 255 / 31;
+ out[A] = 255;
+ out += 4;
+ in += 2;
+ }
+}
+
+void
+ImagingUnpackBGR16(UINT8 *out, const UINT8 *in, int pixels) {
+ int i, pixel;
+ /* RGB, reversed bytes, 5/6/5 bits per pixel */
+ for (i = 0; i < pixels; i++) {
+ pixel = in[0] + (in[1] << 8);
+ out[B] = (pixel & 31) * 255 / 31;
+ out[G] = ((pixel >> 5) & 63) * 255 / 63;
+ out[R] = ((pixel >> 11) & 31) * 255 / 31;
+ out[A] = 255;
+ out += 4;
+ in += 2;
+ }
+}
+
+void
+ImagingUnpackRGB4B(UINT8 *out, const UINT8 *in, int pixels) {
+ int i, pixel;
+ /* RGB, 4 bits per pixel */
+ for (i = 0; i < pixels; i++) {
+ pixel = in[0] + (in[1] << 8);
+ out[R] = (pixel & 15) * 17;
+ out[G] = ((pixel >> 4) & 15) * 17;
+ out[B] = ((pixel >> 8) & 15) * 17;
+ out[A] = 255;
+ out += 4;
+ in += 2;
+ }
+}
+
+void
+ImagingUnpackRGBA4B(UINT8 *out, const UINT8 *in, int pixels) {
+ int i, pixel;
+ /* RGBA, 4 bits per pixel */
+ for (i = 0; i < pixels; i++) {
+ pixel = in[0] + (in[1] << 8);
+ out[R] = (pixel & 15) * 17;
+ out[G] = ((pixel >> 4) & 15) * 17;
+ out[B] = ((pixel >> 8) & 15) * 17;
+ out[A] = ((pixel >> 12) & 15) * 17;
+ out += 4;
+ in += 2;
+ }
+}
+
+static void
+ImagingUnpackBGRX(UINT8 *_out, const UINT8 *in, int pixels) {
+ int i;
+ /* RGB, reversed bytes with padding */
+ for (i = 0; i < pixels; i++) {
+ UINT32 iv = MAKE_UINT32(in[2], in[1], in[0], 255);
+ memcpy(_out, &iv, sizeof(iv));
+ in += 4;
+ _out += 4;
+ }
+}
+
+static void
+ImagingUnpackXRGB(UINT8 *_out, const UINT8 *in, int pixels) {
+ int i;
+ /* RGB, leading pad */
+ for (i = 0; i < pixels; i++) {
+ UINT32 iv = MAKE_UINT32(in[1], in[2], in[3], 255);
+ memcpy(_out, &iv, sizeof(iv));
+ in += 4;
+ _out += 4;
+ }
+}
+
+static void
+ImagingUnpackXBGR(UINT8 *_out, const UINT8 *in, int pixels) {
+ int i;
+ /* RGB, reversed bytes, leading pad */
+ for (i = 0; i < pixels; i++) {
+ UINT32 iv = MAKE_UINT32(in[3], in[2], in[1], 255);
+ memcpy(_out, &iv, sizeof(iv));
+ in += 4;
+ _out += 4;
+ }
+}
+
+/* Unpack to "RGBA" image */
+
+static void
+unpackRGBALA(UINT8 *_out, const UINT8 *in, int pixels) {
+ int i;
+ /* greyscale with alpha */
+ for (i = 0; i < pixels; i++) {
+ UINT32 iv = MAKE_UINT32(in[0], in[0], in[0], in[1]);
+ memcpy(_out, &iv, sizeof(iv));
+ in += 2;
+ _out += 4;
+ }
+}
+
+static void
+unpackRGBALA16B(UINT8 *_out, const UINT8 *in, int pixels) {
+ int i;
+ /* 16-bit greyscale with alpha, big-endian */
+ for (i = 0; i < pixels; i++) {
+ UINT32 iv = MAKE_UINT32(in[0], in[0], in[0], in[2]);
+ memcpy(_out, &iv, sizeof(iv));
+ in += 4;
+ _out += 4;
+ }
+}
+
+static void
+unpackRGBa16L(UINT8 *_out, const UINT8 *in, int pixels) {
+ int i;
+ /* premultiplied 16-bit RGBA, little-endian */
+ for (i = 0; i < pixels; i++) {
+ int a = in[7];
+ UINT32 iv;
+ if (!a) {
+ iv = 0;
+ } else if (a == 255) {
+ iv = MAKE_UINT32(in[1], in[3], in[5], a);
+ } else {
+ iv = MAKE_UINT32(
+ CLIP8(in[1] * 255 / a),
+ CLIP8(in[3] * 255 / a),
+ CLIP8(in[5] * 255 / a),
+ a);
+ }
+ memcpy(_out, &iv, sizeof(iv));
+ in += 8;
+ _out += 4;
+ }
+}
+
+static void
+unpackRGBa16B(UINT8 *_out, const UINT8 *in, int pixels) {
+ int i;
+ /* premultiplied 16-bit RGBA, big-endian */
+ for (i = 0; i < pixels; i++) {
+ int a = in[6];
+ UINT32 iv;
+ if (!a) {
+ iv = 0;
+ } else if (a == 255) {
+ iv = MAKE_UINT32(in[0], in[2], in[4], a);
+ } else {
+ iv = MAKE_UINT32(
+ CLIP8(in[0] * 255 / a),
+ CLIP8(in[2] * 255 / a),
+ CLIP8(in[4] * 255 / a),
+ a);
+ }
+ memcpy(_out, &iv, sizeof(iv));
+ in += 8;
+ _out += 4;
+ }
+}
+
+static void
+unpackRGBa(UINT8 *_out, const UINT8 *in, int pixels) {
+ int i;
+ /* premultiplied RGBA */
+ for (i = 0; i < pixels; i++) {
+ int a = in[3];
+ UINT32 iv;
+ if (!a) {
+ iv = 0;
+ } else if (a == 255) {
+ iv = MAKE_UINT32(in[0], in[1], in[2], a);
+ } else {
+ iv = MAKE_UINT32(
+ CLIP8(in[0] * 255 / a),
+ CLIP8(in[1] * 255 / a),
+ CLIP8(in[2] * 255 / a),
+ a);
+ }
+ memcpy(_out, &iv, sizeof(iv));
+ in += 4;
+ _out += 4;
+ }
+}
+
+static void
+unpackRGBaskip1(UINT8 *_out, const UINT8 *in, int pixels) {
+ int i;
+ UINT32 *out = (UINT32 *)_out;
+ /* premultiplied RGBA */
+ for (i = 0; i < pixels; i++) {
+ int a = in[3];
+ if (!a) {
+ out[i] = 0;
+ } else if (a == 255) {
+ out[i] = MAKE_UINT32(in[0], in[1], in[2], a);
+ } else {
+ out[i] = MAKE_UINT32(
+ CLIP8(in[0] * 255 / a),
+ CLIP8(in[1] * 255 / a),
+ CLIP8(in[2] * 255 / a),
+ a);
+ }
+ in += 5;
+ }
+}
+
+static void
+unpackRGBaskip2(UINT8 *_out, const UINT8 *in, int pixels) {
+ int i;
+ UINT32 *out = (UINT32 *)_out;
+ /* premultiplied RGBA */
+ for (i = 0; i < pixels; i++) {
+ int a = in[3];
+ if (!a) {
+ out[i] = 0;
+ } else if (a == 255) {
+ out[i] = MAKE_UINT32(in[0], in[1], in[2], a);
+ } else {
+ out[i] = MAKE_UINT32(
+ CLIP8(in[0] * 255 / a),
+ CLIP8(in[1] * 255 / a),
+ CLIP8(in[2] * 255 / a),
+ a);
+ }
+ in += 6;
+ }
+}
+
+static void
+unpackBGRa(UINT8 *_out, const UINT8 *in, int pixels) {
+ int i;
+ /* premultiplied BGRA */
+ for (i = 0; i < pixels; i++) {
+ int a = in[3];
+ UINT32 iv;
+ if (!a) {
+ iv = 0;
+ } else if (a == 255) {
+ iv = MAKE_UINT32(in[2], in[1], in[0], a);
+ } else {
+ iv = MAKE_UINT32(
+ CLIP8(in[2] * 255 / a),
+ CLIP8(in[1] * 255 / a),
+ CLIP8(in[0] * 255 / a),
+ a);
+ }
+ memcpy(_out, &iv, sizeof(iv));
+ in += 4;
+ _out += 4;
+ }
+}
+
+static void
+unpackRGBAI(UINT8 *out, const UINT8 *in, int pixels) {
+ int i;
+ /* RGBA, inverted RGB bytes (FlashPix) */
+ for (i = 0; i < pixels; i++) {
+ out[R] = ~in[0];
+ out[G] = ~in[1];
+ out[B] = ~in[2];
+ out[A] = in[3];
+ out += 4;
+ in += 4;
+ }
+}
+
+static void
+unpackRGBAL(UINT8 *_out, const UINT8 *in, int pixels) {
+ int i;
+ /* RGBA, line interleaved */
+ for (i = 0; i < pixels; i++, _out += 4) {
+ UINT32 iv = MAKE_UINT32(
+ in[i],
+ in[i + pixels],
+ in[i + pixels + pixels],
+ in[i + pixels + pixels + pixels]);
+ memcpy(_out, &iv, sizeof(iv));
+ }
+}
+
+void
+unpackRGBA16L(UINT8 *_out, const UINT8 *in, int pixels) {
+ int i;
+ /* 16-bit RGBA, little-endian order */
+ for (i = 0; i < pixels; i++, _out += 4) {
+ UINT32 iv = MAKE_UINT32(in[1], in[3], in[5], in[7]);
+ memcpy(_out, &iv, sizeof(iv));
+ in += 8;
+ }
+}
+
+void
+unpackRGBA16B(UINT8 *_out, const UINT8 *in, int pixels) {
+ int i;
+ /* 16-bit RGBA, big-endian order */
+ for (i = 0; i < pixels; i++, _out += 4) {
+ UINT32 iv = MAKE_UINT32(in[0], in[2], in[4], in[6]);
+ memcpy(_out, &iv, sizeof(iv));
+ in += 8;
+ }
+}
+
+static void
+unpackARGB(UINT8 *_out, const UINT8 *in, int pixels) {
+ int i;
+ /* RGBA, leading pad */
+ for (i = 0; i < pixels; i++) {
+ UINT32 iv = MAKE_UINT32(in[1], in[2], in[3], in[0]);
+ memcpy(_out, &iv, sizeof(iv));
+ in += 4;
+ _out += 4;
+ }
+}
+
+static void
+unpackABGR(UINT8 *_out, const UINT8 *in, int pixels) {
+ int i;
+ /* RGBA, reversed bytes */
+ for (i = 0; i < pixels; i++) {
+ UINT32 iv = MAKE_UINT32(in[3], in[2], in[1], in[0]);
+ memcpy(_out, &iv, sizeof(iv));
+ in += 4;
+ _out += 4;
+ }
+}
+
+static void
+unpackBGRA(UINT8 *_out, const UINT8 *in, int pixels) {
+ int i;
+ /* RGBA, rearranged channels */
+ for (i = 0; i < pixels; i++) {
+ UINT32 iv = MAKE_UINT32(in[2], in[1], in[0], in[3]);
+ memcpy(_out, &iv, sizeof(iv));
+ in += 4;
+ _out += 4;
+ }
+}
+
+static void
+unpackBGRA16L(UINT8 *_out, const UINT8 *in, int pixels) {
+ int i;
+ /* 16-bit RGBA, little-endian order, rearranged channels */
+ for (i = 0; i < pixels; i++) {
+ UINT32 iv = MAKE_UINT32(in[5], in[3], in[1], in[7]);
+ memcpy(_out, &iv, sizeof(iv));
+ in += 8;
+ _out += 4;
+ }
+}
+
+static void
+unpackBGRA16B(UINT8 *_out, const UINT8 *in, int pixels) {
+ int i;
+ /* 16-bit RGBA, big-endian order, rearranged channels */
+ for (i = 0; i < pixels; i++) {
+ UINT32 iv = MAKE_UINT32(in[4], in[2], in[0], in[6]);
+ memcpy(_out, &iv, sizeof(iv));
+ in += 8;
+ _out += 4;
+ }
+}
+
+/* Unpack to "CMYK" image */
+
+static void
+unpackCMYKI(UINT8 *_out, const UINT8 *in, int pixels) {
+ int i;
+ /* CMYK, inverted bytes (Photoshop 2.5) */
+ for (i = 0; i < pixels; i++) {
+ UINT32 iv = ~MAKE_UINT32(in[0], in[1], in[2], in[3]);
+ memcpy(_out, &iv, sizeof(iv));
+ in += 4;
+ _out += 4;
+ }
+}
+
+/* Unpack to "LAB" image */
+/* There are two representations of LAB images for whatever precision:
+ L: Uint (in PS, it's 0-100)
+ A: Int (in ps, -128 .. 128, or elsewhere 0..255, with 128 as middle.
+ Channels in PS display a 0 value as middle grey,
+ LCMS appears to use 128 as the 0 value for these channels)
+ B: Int (as above)
+
+ Since we don't have any signed ints, we're going with the shifted versions
+ internally, and we'll unshift for saving and whatnot.
+*/
+void
+ImagingUnpackLAB(UINT8 *out, const UINT8 *in, int pixels) {
+ int i;
+ /* LAB triplets */
+ for (i = 0; i < pixels; i++) {
+ out[0] = in[0];
+ out[1] = in[1] ^ 128; /* signed in outside world */
+ out[2] = in[2] ^ 128;
+ out[3] = 255;
+ out += 4;
+ in += 3;
+ }
+}
+
+static void
+unpackI16N_I16B(UINT8 *out, const UINT8 *in, int pixels) {
+ int i;
+ UINT8 *tmp = (UINT8 *)out;
+ for (i = 0; i < pixels; i++) {
+ C16B;
+ in += 2;
+ tmp += 2;
+ }
+}
+static void
+unpackI16N_I16(UINT8 *out, const UINT8 *in, int pixels) {
+ int i;
+ UINT8 *tmp = (UINT8 *)out;
+ for (i = 0; i < pixels; i++) {
+ C16L;
+ in += 2;
+ tmp += 2;
+ }
+}
+static void
+unpackI16B_I16(UINT8 *out, const UINT8 *in, int pixels) {
+ int i;
+ for (i = 0; i < pixels; i++) {
+ out[0] = in[1];
+ out[1] = in[0];
+ in += 2;
+ out += 2;
+ }
+}
+static void
+unpackI16R_I16(UINT8 *out, const UINT8 *in, int pixels) {
+ int i;
+ for (i = 0; i < pixels; i++) {
+ out[0] = BITFLIP[in[0]];
+ out[1] = BITFLIP[in[1]];
+ in += 2;
+ out += 2;
+ }
+}
+
+static void
+unpackI12_I16(UINT8 *out, const UINT8 *in, int pixels) {
+ /* Fillorder 1/MSB -> LittleEndian, for 12bit integer greyscale tiffs.
+
+ According to the TIFF spec:
+
+ FillOrder = 2 should be used only when BitsPerSample = 1 and
+ the data is either uncompressed or compressed using CCITT 1D
+ or 2D compression, to avoid potentially ambiguous situations.
+
+ Yeah. I thought so. We'll see how well people read the spec.
+ We've got several fillorder=2 modes in TiffImagePlugin.py
+
+ There's no spec I can find. It appears that the in storage
+ layout is: 00 80 00 ... -> (128 , 0 ...). The samples are
+ stored in a single big bitian 12bit block, but need to be
+ pulled out to little endian format to be stored in a 2 byte
+ int.
+ */
+
+ int i;
+ UINT16 pixel;
+#ifdef WORDS_BIGENDIAN
+ UINT8 *tmp = (UINT8 *)&pixel;
+#endif
+ for (i = 0; i < pixels - 1; i += 2) {
+ pixel = (((UINT16)in[0]) << 4) + (in[1] >> 4);
+#ifdef WORDS_BIGENDIAN
+ out[0] = tmp[1];
+ out[1] = tmp[0];
+#else
+ memcpy(out, &pixel, sizeof(pixel));
+#endif
+
+ out += 2;
+ pixel = (((UINT16)(in[1] & 0x0F)) << 8) + in[2];
+#ifdef WORDS_BIGENDIAN
+ out[0] = tmp[1];
+ out[1] = tmp[0];
+#else
+ memcpy(out, &pixel, sizeof(pixel));
+#endif
+
+ in += 3;
+ out += 2;
+ }
+ if (i == pixels - 1) {
+ pixel = (((UINT16)in[0]) << 4) + (in[1] >> 4);
+#ifdef WORDS_BIGENDIAN
+ out[0] = tmp[1];
+ out[1] = tmp[0];
+#else
+ memcpy(out, &pixel, sizeof(pixel));
+#endif
+ }
+}
+
+static void
+copy1(UINT8 *out, const UINT8 *in, int pixels) {
+ /* L, P */
+ memcpy(out, in, pixels);
+}
+
+static void
+copy2(UINT8 *out, const UINT8 *in, int pixels) {
+ /* I;16 */
+ memcpy(out, in, pixels * 2);
+}
+
+static void
+copy3(UINT8 *out, const UINT8 *in, int pixels) {
+ /* BGR;24 */
+ memcpy(out, in, pixels * 3);
+}
+
+static void
+copy4(UINT8 *out, const UINT8 *in, int pixels) {
+ /* RGBA, CMYK quadruples */
+ memcpy(out, in, 4 * pixels);
+}
+
+static void
+copy4skip1(UINT8 *_out, const UINT8 *in, int pixels) {
+ int i;
+ for (i = 0; i < pixels; i++) {
+ memcpy(_out, in, 4);
+ in += 5;
+ _out += 4;
+ }
+}
+
+static void
+copy4skip2(UINT8 *_out, const UINT8 *in, int pixels) {
+ int i;
+ for (i = 0; i < pixels; i++) {
+ memcpy(_out, in, 4);
+ in += 6;
+ _out += 4;
+ }
+}
+
+/* Unpack to "I" and "F" images */
+
+#define UNPACK_RAW(NAME, GET, INTYPE, OUTTYPE) \
+ static void NAME(UINT8 *out_, const UINT8 *in, int pixels) { \
+ int i; \
+ OUTTYPE *out = (OUTTYPE *)out_; \
+ for (i = 0; i < pixels; i++, in += sizeof(INTYPE)) { \
+ out[i] = (OUTTYPE)((INTYPE)GET); \
+ } \
+ }
+
+#define UNPACK(NAME, COPY, INTYPE, OUTTYPE) \
+ static void NAME(UINT8 *out_, const UINT8 *in, int pixels) { \
+ int i; \
+ OUTTYPE *out = (OUTTYPE *)out_; \
+ INTYPE tmp_; \
+ UINT8 *tmp = (UINT8 *)&tmp_; \
+ for (i = 0; i < pixels; i++, in += sizeof(INTYPE)) { \
+ COPY; \
+ out[i] = (OUTTYPE)tmp_; \
+ } \
+ }
+
+UNPACK_RAW(unpackI8, in[0], UINT8, INT32)
+UNPACK_RAW(unpackI8S, in[0], INT8, INT32)
+UNPACK(unpackI16, C16L, UINT16, INT32)
+UNPACK(unpackI16S, C16L, INT16, INT32)
+UNPACK(unpackI16B, C16B, UINT16, INT32)
+UNPACK(unpackI16BS, C16B, INT16, INT32)
+UNPACK(unpackI16N, C16N, UINT16, INT32)
+UNPACK(unpackI16NS, C16N, INT16, INT32)
+UNPACK(unpackI32, C32L, UINT32, INT32)
+UNPACK(unpackI32S, C32L, INT32, INT32)
+UNPACK(unpackI32B, C32B, UINT32, INT32)
+UNPACK(unpackI32BS, C32B, INT32, INT32)
+UNPACK(unpackI32N, C32N, UINT32, INT32)
+UNPACK(unpackI32NS, C32N, INT32, INT32)
+
+UNPACK_RAW(unpackF8, in[0], UINT8, FLOAT32)
+UNPACK_RAW(unpackF8S, in[0], INT8, FLOAT32)
+UNPACK(unpackF16, C16L, UINT16, FLOAT32)
+UNPACK(unpackF16S, C16L, INT16, FLOAT32)
+UNPACK(unpackF16B, C16B, UINT16, FLOAT32)
+UNPACK(unpackF16BS, C16B, INT16, FLOAT32)
+UNPACK(unpackF16N, C16N, UINT16, FLOAT32)
+UNPACK(unpackF16NS, C16N, INT16, FLOAT32)
+UNPACK(unpackF32, C32L, UINT32, FLOAT32)
+UNPACK(unpackF32S, C32L, INT32, FLOAT32)
+UNPACK(unpackF32B, C32B, UINT32, FLOAT32)
+UNPACK(unpackF32BS, C32B, INT32, FLOAT32)
+UNPACK(unpackF32N, C32N, UINT32, FLOAT32)
+UNPACK(unpackF32NS, C32N, INT32, FLOAT32)
+UNPACK(unpackF32F, C32L, FLOAT32, FLOAT32)
+UNPACK(unpackF32BF, C32B, FLOAT32, FLOAT32)
+UNPACK(unpackF32NF, C32N, FLOAT32, FLOAT32)
+#ifdef FLOAT64
+UNPACK(unpackF64F, C64L, FLOAT64, FLOAT32)
+UNPACK(unpackF64BF, C64B, FLOAT64, FLOAT32)
+UNPACK(unpackF64NF, C64N, FLOAT64, FLOAT32)
+#endif
+
+/* Misc. unpackers */
+
+static void
+band0(UINT8 *out, const UINT8 *in, int pixels) {
+ int i;
+ /* band 0 only */
+ for (i = 0; i < pixels; i++) {
+ out[0] = in[i];
+ out += 4;
+ }
+}
+
+static void
+band1(UINT8 *out, const UINT8 *in, int pixels) {
+ int i;
+ /* band 1 only */
+ for (i = 0; i < pixels; i++) {
+ out[1] = in[i];
+ out += 4;
+ }
+}
+
+static void
+band2(UINT8 *out, const UINT8 *in, int pixels) {
+ int i;
+ /* band 2 only */
+ for (i = 0; i < pixels; i++) {
+ out[2] = in[i];
+ out += 4;
+ }
+}
+
+static void
+band3(UINT8 *out, const UINT8 *in, int pixels) {
+ /* band 3 only */
+ int i;
+ for (i = 0; i < pixels; i++) {
+ out[3] = in[i];
+ out += 4;
+ }
+}
+
+static void
+band0I(UINT8 *out, const UINT8 *in, int pixels) {
+ int i;
+ /* band 0 only */
+ for (i = 0; i < pixels; i++) {
+ out[0] = ~in[i];
+ out += 4;
+ }
+}
+
+static void
+band1I(UINT8 *out, const UINT8 *in, int pixels) {
+ int i;
+ /* band 1 only */
+ for (i = 0; i < pixels; i++) {
+ out[1] = ~in[i];
+ out += 4;
+ }
+}
+
+static void
+band2I(UINT8 *out, const UINT8 *in, int pixels) {
+ int i;
+ /* band 2 only */
+ for (i = 0; i < pixels; i++) {
+ out[2] = ~in[i];
+ out += 4;
+ }
+}
+
+static void
+band3I(UINT8 *out, const UINT8 *in, int pixels) {
+ /* band 3 only */
+ int i;
+ for (i = 0; i < pixels; i++) {
+ out[3] = ~in[i];
+ out += 4;
+ }
+}
+
+static void
+band016B(UINT8* out, const UINT8* in, int pixels)
+{
+ int i;
+ /* band 0 only, big endian */
+ for (i = 0; i < pixels; i++) {
+ out[0] = in[0];
+ out += 4; in += 2;
+ }
+}
+
+static void
+band116B(UINT8* out, const UINT8* in, int pixels)
+{
+ int i;
+ /* band 1 only, big endian */
+ for (i = 0; i < pixels; i++) {
+ out[1] = in[0];
+ out += 4; in += 2;
+ }
+}
+
+static void
+band216B(UINT8* out, const UINT8* in, int pixels)
+{
+ int i;
+ /* band 2 only, big endian */
+ for (i = 0; i < pixels; i++) {
+ out[2] = in[0];
+ out += 4; in += 2;
+ }
+}
+
+static void
+band316B(UINT8* out, const UINT8* in, int pixels)
+{
+ int i;
+ /* band 3 only, big endian */
+ for (i = 0; i < pixels; i++) {
+ out[3] = in[0];
+ out += 4; in += 2;
+ }
+}
+
+static void
+band016L(UINT8* out, const UINT8* in, int pixels)
+{
+ int i;
+ /* band 0 only, little endian */
+ for (i = 0; i < pixels; i++) {
+ out[0] = in[1];
+ out += 4; in += 2;
+ }
+}
+
+static void
+band116L(UINT8* out, const UINT8* in, int pixels)
+{
+ int i;
+ /* band 1 only, little endian */
+ for (i = 0; i < pixels; i++) {
+ out[1] = in[1];
+ out += 4; in += 2;
+ }
+}
+
+static void
+band216L(UINT8* out, const UINT8* in, int pixels)
+{
+ int i;
+ /* band 2 only, little endian */
+ for (i = 0; i < pixels; i++) {
+ out[2] = in[1];
+ out += 4; in += 2;
+ }
+}
+
+static void
+band316L(UINT8* out, const UINT8* in, int pixels)
+{
+ int i;
+ /* band 3 only, little endian */
+ for (i = 0; i < pixels; i++) {
+ out[3] = in[1];
+ out += 4; in += 2;
+ }
+}
+
+static struct {
+ const char *mode;
+ const char *rawmode;
+ int bits;
+ ImagingShuffler unpack;
+} unpackers[] = {
+
+ /* raw mode syntax is "<mode>;<bits><flags>" where "bits" defaults
+ depending on mode (1 for "1", 8 for "P" and "L", etc), and
+ "flags" should be given in alphabetical order. if both bits
+ and flags have their default values, the ; should be left out */
+
+ /* flags: "I" inverted data; "R" reversed bit order; "B" big
+ endian byte order (default is little endian); "L" line
+ interleave, "S" signed, "F" floating point */
+
+ /* exception: rawmodes "I" and "F" are always native endian byte order */
+
+ /* bilevel */
+ {"1", "1", 1, unpack1},
+ {"1", "1;I", 1, unpack1I},
+ {"1", "1;R", 1, unpack1R},
+ {"1", "1;IR", 1, unpack1IR},
+ {"1", "1;8", 8, unpack18},
+
+ /* greyscale */
+ {"L", "L;2", 2, unpackL2},
+ {"L", "L;2I", 2, unpackL2I},
+ {"L", "L;2R", 2, unpackL2R},
+ {"L", "L;2IR", 2, unpackL2IR},
+
+ {"L", "L;4", 4, unpackL4},
+ {"L", "L;4I", 4, unpackL4I},
+ {"L", "L;4R", 4, unpackL4R},
+ {"L", "L;4IR", 4, unpackL4IR},
+
+ {"L", "L", 8, copy1},
+ {"L", "L;I", 8, unpackLI},
+ {"L", "L;R", 8, unpackLR},
+ {"L", "L;16", 16, unpackL16},
+ {"L", "L;16B", 16, unpackL16B},
+
+ /* greyscale w. alpha */
+ {"LA", "LA", 16, unpackLA},
+ {"LA", "LA;L", 16, unpackLAL},
+
+ /* greyscale w. alpha premultiplied */
+ {"La", "La", 16, unpackLA},
+
+ /* palette */
+ {"P", "P;1", 1, unpackP1},
+ {"P", "P;2", 2, unpackP2},
+ {"P", "P;2L", 2, unpackP2L},
+ {"P", "P;4", 4, unpackP4},
+ {"P", "P;4L", 4, unpackP4L},
+ {"P", "P", 8, copy1},
+ {"P", "P;R", 8, unpackLR},
+ {"P", "L", 8, copy1},
+
+ /* palette w. alpha */
+ {"PA", "PA", 16, unpackLA},
+ {"PA", "PA;L", 16, unpackLAL},
+ {"PA", "LA", 16, unpackLA},
+
+ /* true colour */
+ {"RGB", "RGB", 24, ImagingUnpackRGB},
+ {"RGB", "RGB;L", 24, unpackRGBL},
+ {"RGB", "RGB;R", 24, unpackRGBR},
+ {"RGB", "RGB;16L", 48, unpackRGB16L},
+ {"RGB", "RGB;16B", 48, unpackRGB16B},
+ {"RGB", "BGR", 24, ImagingUnpackBGR},
+ {"RGB", "RGB;15", 16, ImagingUnpackRGB15},
+ {"RGB", "BGR;15", 16, ImagingUnpackBGR15},
+ {"RGB", "RGB;16", 16, ImagingUnpackRGB16},
+ {"RGB", "BGR;16", 16, ImagingUnpackBGR16},
+ {"RGB", "RGB;4B", 16, ImagingUnpackRGB4B},
+ {"RGB", "BGR;5", 16, ImagingUnpackBGR15}, /* compat */
+ {"RGB", "RGBX", 32, copy4},
+ {"RGB", "RGBX;L", 32, unpackRGBAL},
+ {"RGB", "RGBA;L", 32, unpackRGBAL},
+ {"RGB", "RGBA;15", 16, ImagingUnpackRGBA15},
+ {"RGB", "BGRX", 32, ImagingUnpackBGRX},
+ {"RGB", "XRGB", 32, ImagingUnpackXRGB},
+ {"RGB", "XBGR", 32, ImagingUnpackXBGR},
+ {"RGB", "YCC;P", 24, ImagingUnpackYCC},
+ {"RGB", "R", 8, band0},
+ {"RGB", "G", 8, band1},
+ {"RGB", "B", 8, band2},
+ {"RGB", "R;16L", 16, band016L},
+ {"RGB", "G;16L", 16, band116L},
+ {"RGB", "B;16L", 16, band216L},
+ {"RGB", "R;16B", 16, band016B},
+ {"RGB", "G;16B", 16, band116B},
+ {"RGB", "B;16B", 16, band216B},
+ {"RGB", "CMYK", 32, cmyk2rgb},
+
+ {"BGR;15", "BGR;15", 16, copy2},
+ {"BGR;16", "BGR;16", 16, copy2},
+ {"BGR;24", "BGR;24", 24, copy3},
+
+ /* true colour w. alpha */
+ {"RGBA", "LA", 16, unpackRGBALA},
+ {"RGBA", "LA;16B", 32, unpackRGBALA16B},
+ {"RGBA", "RGBA", 32, copy4},
+ {"RGBA", "RGBAX", 40, copy4skip1},
+ {"RGBA", "RGBAXX", 48, copy4skip2},
+ {"RGBA", "RGBa", 32, unpackRGBa},
+ {"RGBA", "RGBaX", 40, unpackRGBaskip1},
+ {"RGBA", "RGBaXX", 48, unpackRGBaskip2},
+ {"RGBA", "RGBa;16L", 64, unpackRGBa16L},
+ {"RGBA", "RGBa;16B", 64, unpackRGBa16B},
+ {"RGBA", "BGRa", 32, unpackBGRa},
+ {"RGBA", "RGBA;I", 32, unpackRGBAI},
+ {"RGBA", "RGBA;L", 32, unpackRGBAL},
+ {"RGBA", "RGBA;15", 16, ImagingUnpackRGBA15},
+ {"RGBA", "BGRA;15", 16, ImagingUnpackBGRA15},
+ {"RGBA", "RGBA;4B", 16, ImagingUnpackRGBA4B},
+ {"RGBA", "RGBA;16L", 64, unpackRGBA16L},
+ {"RGBA", "RGBA;16B", 64, unpackRGBA16B},
+ {"RGBA", "BGRA", 32, unpackBGRA},
+ {"RGBA", "BGRA;16L", 64, unpackBGRA16L},
+ {"RGBA", "BGRA;16B", 64, unpackBGRA16B},
+ {"RGBA", "ARGB", 32, unpackARGB},
+ {"RGBA", "ABGR", 32, unpackABGR},
+ {"RGBA", "YCCA;P", 32, ImagingUnpackYCCA},
+ {"RGBA", "R", 8, band0},
+ {"RGBA", "G", 8, band1},
+ {"RGBA", "B", 8, band2},
+ {"RGBA", "A", 8, band3},
+ {"RGBA", "R;16L", 16, band016L},
+ {"RGBA", "G;16L", 16, band116L},
+ {"RGBA", "B;16L", 16, band216L},
+ {"RGBA", "A;16L", 16, band316L},
+ {"RGBA", "R;16B", 16, band016B},
+ {"RGBA", "G;16B", 16, band116B},
+ {"RGBA", "B;16B", 16, band216B},
+ {"RGBA", "A;16B", 16, band316B},
+
+#ifdef WORDS_BIGENDIAN
+ {"RGB", "RGB;16N", 48, unpackRGB16B},
+ {"RGBA", "RGBa;16N", 64, unpackRGBa16B},
+ {"RGBA", "RGBA;16N", 64, unpackRGBA16B},
+ {"RGBX", "RGBX;16N", 64, unpackRGBA16B},
+ {"RGB", "R;16N", 16, band016B},
+ {"RGB", "G;16N", 16, band116B},
+ {"RGB", "B;16N", 16, band216B},
+
+ {"RGBA", "R;16N", 16, band016B},
+ {"RGBA", "G;16N", 16, band116B},
+ {"RGBA", "B;16N", 16, band216B},
+ {"RGBA", "A;16N", 16, band316B},
+#else
+ {"RGB", "RGB;16N", 48, unpackRGB16L},
+ {"RGBA", "RGBa;16N", 64, unpackRGBa16L},
+ {"RGBA", "RGBA;16N", 64, unpackRGBA16L},
+ {"RGBX", "RGBX;16N", 64, unpackRGBA16L},
+ {"RGB", "R;16N", 16, band016L},
+ {"RGB", "G;16N", 16, band116L},
+ {"RGB", "B;16N", 16, band216L},
+
+
+ {"RGBA", "R;16N", 16, band016L},
+ {"RGBA", "G;16N", 16, band116L},
+ {"RGBA", "B;16N", 16, band216L},
+ {"RGBA", "A;16N", 16, band316L},
+#endif
+
+ /* true colour w. alpha premultiplied */
+ {"RGBa", "RGBa", 32, copy4},
+ {"RGBa", "BGRa", 32, unpackBGRA},
+ {"RGBa", "aRGB", 32, unpackARGB},
+ {"RGBa", "aBGR", 32, unpackABGR},
+
+ /* true colour w. padding */
+ {"RGBX", "RGB", 24, ImagingUnpackRGB},
+ {"RGBX", "RGB;L", 24, unpackRGBL},
+ {"RGBX", "RGB;16B", 48, unpackRGB16B},
+ {"RGBX", "BGR", 24, ImagingUnpackBGR},
+ {"RGBX", "RGB;15", 16, ImagingUnpackRGB15},
+ {"RGBX", "BGR;15", 16, ImagingUnpackBGR15},
+ {"RGBX", "RGB;4B", 16, ImagingUnpackRGB4B},
+ {"RGBX", "BGR;5", 16, ImagingUnpackBGR15}, /* compat */
+ {"RGBX", "RGBX", 32, copy4},
+ {"RGBX", "RGBXX", 40, copy4skip1},
+ {"RGBX", "RGBXXX", 48, copy4skip2},
+ {"RGBX", "RGBX;L", 32, unpackRGBAL},
+ {"RGBX", "RGBX;16L", 64, unpackRGBA16L},
+ {"RGBX", "RGBX;16B", 64, unpackRGBA16B},
+ {"RGBX", "BGRX", 32, ImagingUnpackBGRX},
+ {"RGBX", "XRGB", 32, ImagingUnpackXRGB},
+ {"RGBX", "XBGR", 32, ImagingUnpackXBGR},
+ {"RGBX", "YCC;P", 24, ImagingUnpackYCC},
+ {"RGBX", "R", 8, band0},
+ {"RGBX", "G", 8, band1},
+ {"RGBX", "B", 8, band2},
+ {"RGBX", "X", 8, band3},
+
+ /* colour separation */
+ {"CMYK", "CMYK", 32, copy4},
+ {"CMYK", "CMYKX", 40, copy4skip1},
+ {"CMYK", "CMYKXX", 48, copy4skip2},
+ {"CMYK", "CMYK;I", 32, unpackCMYKI},
+ {"CMYK", "CMYK;L", 32, unpackRGBAL},
+ {"CMYK", "CMYK;16L", 64, unpackRGBA16L},
+ {"CMYK", "CMYK;16B", 64, unpackRGBA16B},
+ {"CMYK", "C", 8, band0},
+ {"CMYK", "M", 8, band1},
+ {"CMYK", "Y", 8, band2},
+ {"CMYK", "K", 8, band3},
+ {"CMYK", "C;I", 8, band0I},
+ {"CMYK", "M;I", 8, band1I},
+ {"CMYK", "Y;I", 8, band2I},
+ {"CMYK", "K;I", 8, band3I},
+
+#ifdef WORDS_BIGENDIAN
+ {"CMYK", "CMYK;16N", 64, unpackRGBA16B},
+#else
+ {"CMYK", "CMYK;16N", 64, unpackRGBA16L},
+#endif
+
+ /* video (YCbCr) */
+ {"YCbCr", "YCbCr", 24, ImagingUnpackRGB},
+ {"YCbCr", "YCbCr;L", 24, unpackRGBL},
+ {"YCbCr", "YCbCrX", 32, copy4},
+ {"YCbCr", "YCbCrK", 32, copy4},
+
+ /* LAB Color */
+ {"LAB", "LAB", 24, ImagingUnpackLAB},
+ {"LAB", "L", 8, band0},
+ {"LAB", "A", 8, band1},
+ {"LAB", "B", 8, band2},
+
+ /* HSV Color */
+ {"HSV", "HSV", 24, ImagingUnpackRGB},
+ {"HSV", "H", 8, band0},
+ {"HSV", "S", 8, band1},
+ {"HSV", "V", 8, band2},
+
+ /* integer variations */
+ {"I", "I", 32, copy4},
+ {"I", "I;8", 8, unpackI8},
+ {"I", "I;8S", 8, unpackI8S},
+ {"I", "I;16", 16, unpackI16},
+ {"I", "I;16S", 16, unpackI16S},
+ {"I", "I;16B", 16, unpackI16B},
+ {"I", "I;16BS", 16, unpackI16BS},
+ {"I", "I;16N", 16, unpackI16N},
+ {"I", "I;16NS", 16, unpackI16NS},
+ {"I", "I;32", 32, unpackI32},
+ {"I", "I;32S", 32, unpackI32S},
+ {"I", "I;32B", 32, unpackI32B},
+ {"I", "I;32BS", 32, unpackI32BS},
+ {"I", "I;32N", 32, unpackI32N},
+ {"I", "I;32NS", 32, unpackI32NS},
+
+ /* floating point variations */
+ {"F", "F", 32, copy4},
+ {"F", "F;8", 8, unpackF8},
+ {"F", "F;8S", 8, unpackF8S},
+ {"F", "F;16", 16, unpackF16},
+ {"F", "F;16S", 16, unpackF16S},
+ {"F", "F;16B", 16, unpackF16B},
+ {"F", "F;16BS", 16, unpackF16BS},
+ {"F", "F;16N", 16, unpackF16N},
+ {"F", "F;16NS", 16, unpackF16NS},
+ {"F", "F;32", 32, unpackF32},
+ {"F", "F;32S", 32, unpackF32S},
+ {"F", "F;32B", 32, unpackF32B},
+ {"F", "F;32BS", 32, unpackF32BS},
+ {"F", "F;32N", 32, unpackF32N},
+ {"F", "F;32NS", 32, unpackF32NS},
+ {"F", "F;32F", 32, unpackF32F},
+ {"F", "F;32BF", 32, unpackF32BF},
+ {"F", "F;32NF", 32, unpackF32NF},
+#ifdef FLOAT64
+ {"F", "F;64F", 64, unpackF64F},
+ {"F", "F;64BF", 64, unpackF64BF},
+ {"F", "F;64NF", 64, unpackF64NF},
+#endif
+
+ /* storage modes */
+ {"I;16", "I;16", 16, copy2},
+ {"I;16B", "I;16B", 16, copy2},
+ {"I;16L", "I;16L", 16, copy2},
+ {"I;16N", "I;16N", 16, copy2},
+
+ {"I;16", "I;16B", 16, unpackI16B_I16},
+ {"I;16", "I;16N", 16, unpackI16N_I16}, // LibTiff native->image endian.
+ {"I;16L", "I;16N", 16, unpackI16N_I16}, // LibTiff native->image endian.
+ {"I;16B", "I;16N", 16, unpackI16N_I16B},
+
+ {"I;16", "I;16R", 16, unpackI16R_I16},
+
+ {"I;16", "I;12", 12, unpackI12_I16}, // 12 bit Tiffs stored in 16bits.
+
+ {NULL} /* sentinel */
+};
+
+ImagingShuffler
+ImagingFindUnpacker(const char *mode, const char *rawmode, int *bits_out) {
+ int i;
+
+ /* find a suitable pixel unpacker */
+ for (i = 0; unpackers[i].rawmode; i++) {
+ if (strcmp(unpackers[i].mode, mode) == 0 &&
+ strcmp(unpackers[i].rawmode, rawmode) == 0) {
+ if (bits_out) {
+ *bits_out = unpackers[i].bits;
+ }
+ return unpackers[i].unpack;
+ }
+ }
+
+ /* FIXME: configure a general unpacker based on the type codes... */
+
+ return NULL;
+}
diff --git a/contrib/python/Pillow/py3/libImaging/UnpackYCC.c b/contrib/python/Pillow/py3/libImaging/UnpackYCC.c
new file mode 100644
index 00000000000..0b177bdd4f5
--- /dev/null
+++ b/contrib/python/Pillow/py3/libImaging/UnpackYCC.c
@@ -0,0 +1,163 @@
+/*
+ * The Python Imaging Library.
+ * $Id$
+ *
+ * code to convert and unpack PhotoYCC data
+ *
+ * history:
+ * 97-01-25 fl Moved from PcdDecode.c
+ *
+ * Copyright (c) Fredrik Lundh 1996-97.
+ * Copyright (c) Secret Labs AB 1997.
+ *
+ * See the README file for information on usage and redistribution.
+ */
+
+#include "Imaging.h"
+
+/* Tables generated by pcdtables.py, based on transforms taken from
+ the "Colour Space Conversions FAQ" by Roberts/Ford. */
+
+static INT16 L[] = {
+ 0, 1, 3, 4, 5, 7, 8, 10, 11, 12, 14, 15, 16, 18, 19, 20,
+ 22, 23, 24, 26, 27, 29, 30, 31, 33, 34, 35, 37, 38, 39, 41, 42,
+ 43, 45, 46, 48, 49, 50, 52, 53, 54, 56, 57, 58, 60, 61, 62, 64,
+ 65, 67, 68, 69, 71, 72, 73, 75, 76, 77, 79, 80, 82, 83, 84, 86,
+ 87, 88, 90, 91, 92, 94, 95, 96, 98, 99, 101, 102, 103, 105, 106, 107,
+ 109, 110, 111, 113, 114, 115, 117, 118, 120, 121, 122, 124, 125, 126, 128, 129,
+ 130, 132, 133, 134, 136, 137, 139, 140, 141, 143, 144, 145, 147, 148, 149, 151,
+ 152, 153, 155, 156, 158, 159, 160, 162, 163, 164, 166, 167, 168, 170, 171, 173,
+ 174, 175, 177, 178, 179, 181, 182, 183, 185, 186, 187, 189, 190, 192, 193, 194,
+ 196, 197, 198, 200, 201, 202, 204, 205, 206, 208, 209, 211, 212, 213, 215, 216,
+ 217, 219, 220, 221, 223, 224, 225, 227, 228, 230, 231, 232, 234, 235, 236, 238,
+ 239, 240, 242, 243, 245, 246, 247, 249, 250, 251, 253, 254, 255, 257, 258, 259,
+ 261, 262, 264, 265, 266, 268, 269, 270, 272, 273, 274, 276, 277, 278, 280, 281,
+ 283, 284, 285, 287, 288, 289, 291, 292, 293, 295, 296, 297, 299, 300, 302, 303,
+ 304, 306, 307, 308, 310, 311, 312, 314, 315, 317, 318, 319, 321, 322, 323, 325,
+ 326, 327, 329, 330, 331, 333, 334, 336, 337, 338, 340, 341, 342, 344, 345, 346};
+
+static INT16 CB[] = {
+ -345, -343, -341, -338, -336, -334, -332, -329, -327, -325, -323, -321, -318, -316,
+ -314, -312, -310, -307, -305, -303, -301, -298, -296, -294, -292, -290, -287, -285,
+ -283, -281, -278, -276, -274, -272, -270, -267, -265, -263, -261, -258, -256, -254,
+ -252, -250, -247, -245, -243, -241, -239, -236, -234, -232, -230, -227, -225, -223,
+ -221, -219, -216, -214, -212, -210, -207, -205, -203, -201, -199, -196, -194, -192,
+ -190, -188, -185, -183, -181, -179, -176, -174, -172, -170, -168, -165, -163, -161,
+ -159, -156, -154, -152, -150, -148, -145, -143, -141, -139, -137, -134, -132, -130,
+ -128, -125, -123, -121, -119, -117, -114, -112, -110, -108, -105, -103, -101, -99,
+ -97, -94, -92, -90, -88, -85, -83, -81, -79, -77, -74, -72, -70, -68,
+ -66, -63, -61, -59, -57, -54, -52, -50, -48, -46, -43, -41, -39, -37,
+ -34, -32, -30, -28, -26, -23, -21, -19, -17, -15, -12, -10, -8, -6,
+ -3, -1, 0, 2, 4, 7, 9, 11, 13, 16, 18, 20, 22, 24,
+ 27, 29, 31, 33, 35, 38, 40, 42, 44, 47, 49, 51, 53, 55,
+ 58, 60, 62, 64, 67, 69, 71, 73, 75, 78, 80, 82, 84, 86,
+ 89, 91, 93, 95, 98, 100, 102, 104, 106, 109, 111, 113, 115, 118,
+ 120, 122, 124, 126, 129, 131, 133, 135, 138, 140, 142, 144, 146, 149,
+ 151, 153, 155, 157, 160, 162, 164, 166, 169, 171, 173, 175, 177, 180,
+ 182, 184, 186, 189, 191, 193, 195, 197, 200, 202, 204, 206, 208, 211,
+ 213, 215, 217, 220};
+
+static INT16 GB[] = {
+ 67, 67, 66, 66, 65, 65, 65, 64, 64, 63, 63, 62, 62, 62, 61, 61,
+ 60, 60, 59, 59, 59, 58, 58, 57, 57, 56, 56, 56, 55, 55, 54, 54,
+ 53, 53, 52, 52, 52, 51, 51, 50, 50, 49, 49, 49, 48, 48, 47, 47,
+ 46, 46, 46, 45, 45, 44, 44, 43, 43, 43, 42, 42, 41, 41, 40, 40,
+ 40, 39, 39, 38, 38, 37, 37, 37, 36, 36, 35, 35, 34, 34, 34, 33,
+ 33, 32, 32, 31, 31, 31, 30, 30, 29, 29, 28, 28, 28, 27, 27, 26,
+ 26, 25, 25, 25, 24, 24, 23, 23, 22, 22, 22, 21, 21, 20, 20, 19,
+ 19, 19, 18, 18, 17, 17, 16, 16, 15, 15, 15, 14, 14, 13, 13, 12,
+ 12, 12, 11, 11, 10, 10, 9, 9, 9, 8, 8, 7, 7, 6, 6, 6,
+ 5, 5, 4, 4, 3, 3, 3, 2, 2, 1, 1, 0, 0, 0, 0, 0,
+ -1, -1, -2, -2, -2, -3, -3, -4, -4, -5, -5, -5, -6, -6, -7, -7,
+ -8, -8, -8, -9, -9, -10, -10, -11, -11, -11, -12, -12, -13, -13, -14, -14,
+ -14, -15, -15, -16, -16, -17, -17, -18, -18, -18, -19, -19, -20, -20, -21, -21,
+ -21, -22, -22, -23, -23, -24, -24, -24, -25, -25, -26, -26, -27, -27, -27, -28,
+ -28, -29, -29, -30, -30, -30, -31, -31, -32, -32, -33, -33, -33, -34, -34, -35,
+ -35, -36, -36, -36, -37, -37, -38, -38, -39, -39, -39, -40, -40, -41, -41, -42};
+
+static INT16 CR[] = {
+ -249, -247, -245, -243, -241, -239, -238, -236, -234, -232, -230, -229, -227, -225,
+ -223, -221, -219, -218, -216, -214, -212, -210, -208, -207, -205, -203, -201, -199,
+ -198, -196, -194, -192, -190, -188, -187, -185, -183, -181, -179, -178, -176, -174,
+ -172, -170, -168, -167, -165, -163, -161, -159, -157, -156, -154, -152, -150, -148,
+ -147, -145, -143, -141, -139, -137, -136, -134, -132, -130, -128, -127, -125, -123,
+ -121, -119, -117, -116, -114, -112, -110, -108, -106, -105, -103, -101, -99, -97,
+ -96, -94, -92, -90, -88, -86, -85, -83, -81, -79, -77, -76, -74, -72,
+ -70, -68, -66, -65, -63, -61, -59, -57, -55, -54, -52, -50, -48, -46,
+ -45, -43, -41, -39, -37, -35, -34, -32, -30, -28, -26, -25, -23, -21,
+ -19, -17, -15, -14, -12, -10, -8, -6, -4, -3, -1, 0, 2, 4,
+ 5, 7, 9, 11, 13, 15, 16, 18, 20, 22, 24, 26, 27, 29,
+ 31, 33, 35, 36, 38, 40, 42, 44, 46, 47, 49, 51, 53, 55,
+ 56, 58, 60, 62, 64, 66, 67, 69, 71, 73, 75, 77, 78, 80,
+ 82, 84, 86, 87, 89, 91, 93, 95, 97, 98, 100, 102, 104, 106,
+ 107, 109, 111, 113, 115, 117, 118, 120, 122, 124, 126, 128, 129, 131,
+ 133, 135, 137, 138, 140, 142, 144, 146, 148, 149, 151, 153, 155, 157,
+ 158, 160, 162, 164, 166, 168, 169, 171, 173, 175, 177, 179, 180, 182,
+ 184, 186, 188, 189, 191, 193, 195, 197, 199, 200, 202, 204, 206, 208,
+ 209, 211, 213, 215};
+
+static INT16 GR[] = {
+ 127, 126, 125, 124, 123, 122, 121, 121, 120, 119, 118, 117, 116, 115, 114,
+ 113, 112, 111, 110, 109, 108, 108, 107, 106, 105, 104, 103, 102, 101, 100,
+ 99, 98, 97, 96, 95, 95, 94, 93, 92, 91, 90, 89, 88, 87, 86,
+ 85, 84, 83, 83, 82, 81, 80, 79, 78, 77, 76, 75, 74, 73, 72,
+ 71, 70, 70, 69, 68, 67, 66, 65, 64, 63, 62, 61, 60, 59, 58,
+ 57, 57, 56, 55, 54, 53, 52, 51, 50, 49, 48, 47, 46, 45, 45,
+ 44, 43, 42, 41, 40, 39, 38, 37, 36, 35, 34, 33, 32, 32, 31,
+ 30, 29, 28, 27, 26, 25, 24, 23, 22, 21, 20, 19, 19, 18, 17,
+ 16, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 6, 5, 4, 3,
+ 2, 1, 0, 0, -1, -2, -3, -4, -5, -5, -6, -7, -8, -9, -10,
+ -11, -12, -13, -14, -15, -16, -17, -18, -18, -19, -20, -21, -22, -23, -24,
+ -25, -26, -27, -28, -29, -30, -31, -31, -32, -33, -34, -35, -36, -37, -38,
+ -39, -40, -41, -42, -43, -44, -44, -45, -46, -47, -48, -49, -50, -51, -52,
+ -53, -54, -55, -56, -56, -57, -58, -59, -60, -61, -62, -63, -64, -65, -66,
+ -67, -68, -69, -69, -70, -71, -72, -73, -74, -75, -76, -77, -78, -79, -80,
+ -81, -82, -82, -83, -84, -85, -86, -87, -88, -89, -90, -91, -92, -93, -94,
+ -94, -95, -96, -97, -98, -99, -100, -101, -102, -103, -104, -105, -106, -107, -107,
+ -108};
+
+#define R 0
+#define G 1
+#define B 2
+#define A 3
+
+#define YCC2RGB(rgb, y, cb, cr) \
+ { \
+ int l = L[y]; \
+ int r = l + CR[cr]; \
+ int g = l + GR[cr] + GB[cb]; \
+ int b = l + CB[cb]; \
+ rgb[0] = (r <= 0) ? 0 : (r >= 255) ? 255 : r; \
+ rgb[1] = (g <= 0) ? 0 : (g >= 255) ? 255 : g; \
+ rgb[2] = (b <= 0) ? 0 : (b >= 255) ? 255 : b; \
+ }
+
+void
+ImagingUnpackYCC(UINT8 *out, const UINT8 *in, int pixels) {
+ int i;
+ /* PhotoYCC triplets */
+ for (i = 0; i < pixels; i++) {
+ YCC2RGB(out, in[0], in[1], in[2]);
+ out[A] = 255;
+ out += 4;
+ in += 3;
+ }
+}
+
+void
+ImagingUnpackYCCA(UINT8 *out, const UINT8 *in, int pixels) {
+ int i;
+ /* PhotoYCC triplets plus premultiplied alpha */
+ for (i = 0; i < pixels; i++) {
+ /* Divide by alpha */
+ UINT8 rgb[3];
+ rgb[0] = (in[3] == 0) ? 0 : (((int)in[0] * 255) / in[3]);
+ rgb[1] = (in[3] == 0) ? 0 : (((int)in[1] * 255) / in[3]);
+ rgb[2] = (in[3] == 0) ? 0 : (((int)in[2] * 255) / in[3]);
+ /* Convert non-multiplied data to RGB */
+ YCC2RGB(out, rgb[0], rgb[1], rgb[2]);
+ out[A] = in[3];
+ out += 4;
+ in += 4;
+ }
+}
diff --git a/contrib/python/Pillow/py3/libImaging/UnsharpMask.c b/contrib/python/Pillow/py3/libImaging/UnsharpMask.c
new file mode 100644
index 00000000000..2853ce903fc
--- /dev/null
+++ b/contrib/python/Pillow/py3/libImaging/UnsharpMask.c
@@ -0,0 +1,97 @@
+/* PILusm, a gaussian blur and unsharp masking library for PIL
+ By Kevin Cazabon, copyright 2003
+
+/* Originally released under LGPL. Graciously donated to PIL
+ for distribution under the standard PIL license in 2009." */
+
+#include "Imaging.h"
+
+typedef UINT8 pixel[4];
+
+static inline UINT8
+clip8(int in) {
+ if (in >= 255) {
+ return 255;
+ }
+ if (in <= 0) {
+ return 0;
+ }
+ return (UINT8)in;
+}
+
+Imaging
+ImagingUnsharpMask(
+ Imaging imOut, Imaging imIn, float radius, int percent, int threshold) {
+ ImagingSectionCookie cookie;
+ Imaging result;
+
+ int x, y, diff;
+
+ pixel *lineIn = NULL;
+ pixel *lineOut = NULL;
+ UINT8 *lineIn8 = NULL;
+ UINT8 *lineOut8 = NULL;
+
+ /* First, do a gaussian blur on the image, putting results in imOut
+ temporarily. All format checks are in gaussian blur. */
+ result = ImagingGaussianBlur(imOut, imIn, radius, radius, 3);
+ if (!result) {
+ return NULL;
+ }
+
+ /* Now, go through each pixel, compare "normal" pixel to blurred
+ pixel. If the difference is more than threshold values, apply
+ the OPPOSITE correction to the amount of blur, multiplied by
+ percent. */
+
+ ImagingSectionEnter(&cookie);
+
+ for (y = 0; y < imIn->ysize; y++) {
+ if (imIn->image8) {
+ lineIn8 = imIn->image8[y];
+ lineOut8 = imOut->image8[y];
+ for (x = 0; x < imIn->xsize; x++) {
+ /* compare in/out pixels, apply sharpening */
+ diff = lineIn8[x] - lineOut8[x];
+ if (abs(diff) > threshold) {
+ /* add the diff*percent to the original pixel */
+ lineOut8[x] = clip8(lineIn8[x] + diff * percent / 100);
+ } else {
+ /* new pixel is the same as imIn */
+ lineOut8[x] = lineIn8[x];
+ }
+ }
+ } else {
+ lineIn = (pixel *)imIn->image32[y];
+ lineOut = (pixel *)imOut->image32[y];
+ for (x = 0; x < imIn->xsize; x++) {
+ /* compare in/out pixels, apply sharpening */
+ diff = lineIn[x][0] - lineOut[x][0];
+ lineOut[x][0] = abs(diff) > threshold
+ ? clip8(lineIn[x][0] + diff * percent / 100)
+ : lineIn[x][0];
+
+ diff = lineIn[x][1] - lineOut[x][1];
+ lineOut[x][1] = abs(diff) > threshold
+ ? clip8(lineIn[x][1] + diff * percent / 100)
+ : lineIn[x][1];
+
+ diff = lineIn[x][2] - lineOut[x][2];
+ lineOut[x][2] = abs(diff) > threshold
+ ? clip8(lineIn[x][2] + diff * percent / 100)
+ : lineIn[x][2];
+
+ diff = lineIn[x][3] - lineOut[x][3];
+ lineOut[x][3] = abs(diff) > threshold
+ ? clip8(lineIn[x][3] + diff * percent / 100)
+ : lineIn[x][3];
+ }
+ }
+ }
+
+ ImagingSectionLeave(&cookie);
+
+ return imOut;
+}
diff --git a/contrib/python/Pillow/py3/libImaging/XbmDecode.c b/contrib/python/Pillow/py3/libImaging/XbmDecode.c
new file mode 100644
index 00000000000..d6690de3d28
--- /dev/null
+++ b/contrib/python/Pillow/py3/libImaging/XbmDecode.c
@@ -0,0 +1,78 @@
+/*
+ * The Python Imaging Library.
+ * $Id$
+ *
+ * decoder for XBM hex image data
+ *
+ * history:
+ * 96-04-13 fl Created
+ *
+ * Copyright (c) Fredrik Lundh 1996.
+ * Copyright (c) Secret Labs AB 1997.
+ *
+ * See the README file for information on usage and redistribution.
+ */
+
+#include "Imaging.h"
+
+#define HEX(v) \
+ ((v >= '0' && v <= '9') ? v - '0' \
+ : (v >= 'a' && v <= 'f') ? v - 'a' + 10 \
+ : (v >= 'A' && v <= 'F') ? v - 'A' + 10 \
+ : 0)
+
+int
+ImagingXbmDecode(Imaging im, ImagingCodecState state, UINT8 *buf, Py_ssize_t bytes) {
+ enum { BYTE = 1, SKIP };
+
+ UINT8 *ptr;
+
+ if (!state->state) {
+ state->state = SKIP;
+ }
+
+ ptr = buf;
+
+ for (;;) {
+ if (state->state == SKIP) {
+ /* Skip forward until next 'x' */
+
+ while (bytes > 0) {
+ if (*ptr == 'x') {
+ break;
+ }
+ ptr++;
+ bytes--;
+ }
+
+ if (bytes == 0) {
+ return ptr - buf;
+ }
+
+ state->state = BYTE;
+ }
+
+ if (bytes < 3) {
+ return ptr - buf;
+ }
+
+ state->buffer[state->x] = (HEX(ptr[1]) << 4) + HEX(ptr[2]);
+
+ if (++state->x >= state->bytes) {
+ /* Got a full line, unpack it */
+ state->shuffle((UINT8 *)im->image[state->y], state->buffer, state->xsize);
+
+ state->x = 0;
+
+ if (++state->y >= state->ysize) {
+ /* End of file (errcode = 0) */
+ return -1;
+ }
+ }
+
+ ptr += 3;
+ bytes -= 3;
+
+ state->state = SKIP;
+ }
+}
diff --git a/contrib/python/Pillow/py3/libImaging/XbmEncode.c b/contrib/python/Pillow/py3/libImaging/XbmEncode.c
new file mode 100644
index 00000000000..eec4c0d8462
--- /dev/null
+++ b/contrib/python/Pillow/py3/libImaging/XbmEncode.c
@@ -0,0 +1,96 @@
+/*
+ * The Python Imaging Library.
+ * $Id$
+ *
+ * encoder for Xbm data
+ *
+ * history:
+ * 96-11-01 fl created
+ *
+ * Copyright (c) Fredrik Lundh 1996.
+ * Copyright (c) Secret Labs AB 1997.
+ *
+ * See the README file for information on usage and redistribution.
+ */
+
+#include "Imaging.h"
+
+int
+ImagingXbmEncode(Imaging im, ImagingCodecState state, UINT8 *buf, int bytes) {
+ const char *hex = "0123456789abcdef";
+
+ UINT8 *ptr = buf;
+ int i, n;
+
+ if (!state->state) {
+ /* 8 pixels are stored in no more than 6 bytes */
+ state->bytes = 6 * (state->xsize + 7) / 8;
+
+ state->state = 1;
+ }
+
+ if (bytes < state->bytes) {
+ state->errcode = IMAGING_CODEC_MEMORY;
+ return 0;
+ }
+
+ ptr = buf;
+
+ while (bytes >= state->bytes) {
+ state->shuffle(
+ state->buffer,
+ (UINT8 *)im->image[state->y + state->yoff] + state->xoff * im->pixelsize,
+ state->xsize);
+
+ if (state->y < state->ysize - 1) {
+ /* any line but the last */
+ for (n = 0; n < state->xsize; n += 8) {
+ i = state->buffer[n / 8];
+
+ *ptr++ = '0';
+ *ptr++ = 'x';
+ *ptr++ = hex[(i >> 4) & 15];
+ *ptr++ = hex[i & 15];
+ *ptr++ = ',';
+ bytes -= 5;
+
+ if (++state->count >= 79 / 5) {
+ *ptr++ = '\n';
+ bytes--;
+ state->count = 0;
+ }
+ }
+
+ state->y++;
+
+ } else {
+ /* last line */
+ for (n = 0; n < state->xsize; n += 8) {
+ i = state->buffer[n / 8];
+
+ *ptr++ = '0';
+ *ptr++ = 'x';
+ *ptr++ = hex[(i >> 4) & 15];
+ *ptr++ = hex[i & 15];
+
+ if (n < state->xsize - 8) {
+ *ptr++ = ',';
+ if (++state->count >= 79 / 5) {
+ *ptr++ = '\n';
+ bytes--;
+ state->count = 0;
+ }
+ } else {
+ *ptr++ = '\n';
+ }
+
+ bytes -= 5;
+ }
+
+ state->errcode = IMAGING_CODEC_END;
+ break;
+ }
+ }
+
+ return ptr - buf;
+}
diff --git a/contrib/python/Pillow/py3/libImaging/ZipCodecs.h b/contrib/python/Pillow/py3/libImaging/ZipCodecs.h
new file mode 100644
index 00000000000..50218b6c69a
--- /dev/null
+++ b/contrib/python/Pillow/py3/libImaging/ZipCodecs.h
@@ -0,0 +1,58 @@
+/*
+ * The Python Imaging Library.
+ * $Id$
+ *
+ * declarations for the ZIP codecs
+ *
+ * Copyright (c) Fredrik Lundh 1996.
+ */
+
+#include "zlib.h"
+
+/* modes */
+#define ZIP_PNG 0 /* continuous, filtered image data */
+#define ZIP_PNG_PALETTE 1 /* non-continuous data, disable filtering */
+#define ZIP_TIFF_PREDICTOR 2 /* TIFF, with predictor */
+#define ZIP_TIFF 3 /* TIFF, without predictor */
+
+typedef struct {
+ /* CONFIGURATION */
+
+ /* Codec mode */
+ int mode;
+
+ /* Optimize (max compression) SLOW!!! */
+ int optimize;
+
+ /* 0 no compression, 9 best compression, -1 default compression */
+ int compress_level;
+ /* compression strategy Z_XXX */
+ int compress_type;
+
+ /* Predefined dictionary (experimental) */
+ char *dictionary;
+ int dictionary_size;
+
+ /* PRIVATE CONTEXT (set by decoder/encoder) */
+
+ z_stream z_stream; /* (de)compression stream */
+
+ UINT8 *previous; /* previous line (allocated) */
+
+ int last_output; /* # bytes last output by inflate */
+
+ /* Compressor specific stuff */
+ UINT8 *prior; /* filter storage (allocated) */
+ UINT8 *up;
+ UINT8 *average;
+ UINT8 *paeth;
+
+ UINT8 *output; /* output data */
+
+ int prefix; /* size of filter prefix (0 for TIFF data) */
+
+ int interlaced; /* is the image interlaced? (PNG) */
+
+ int pass; /* current pass of the interlaced image (PNG) */
+
+} ZIPSTATE;
diff --git a/contrib/python/Pillow/py3/libImaging/ZipDecode.c b/contrib/python/Pillow/py3/libImaging/ZipDecode.c
new file mode 100644
index 00000000000..8749678341e
--- /dev/null
+++ b/contrib/python/Pillow/py3/libImaging/ZipDecode.c
@@ -0,0 +1,299 @@
+/*
+ * The Python Imaging Library.
+ * $Id$
+ *
+ * decoder for ZIP (deflated) image data.
+ *
+ * history:
+ * 1996-12-14 fl Created (for PNG)
+ * 1997-01-15 fl Prepared to read TIFF/ZIP
+ * 2001-11-19 fl PNG incomplete read patch (from Bernhard Herzog)
+ *
+ * Copyright (c) Fredrik Lundh 1996.
+ * Copyright (c) Secret Labs AB 1997-2001.
+ *
+ * See the README file for information on usage and redistribution.
+ */
+
+#include "Imaging.h"
+
+#ifdef HAVE_LIBZ
+
+#include "ZipCodecs.h"
+
+static const int OFFSET[] = {7, 3, 3, 1, 1, 0, 0};
+static const int STARTING_COL[] = {0, 4, 0, 2, 0, 1, 0};
+static const int STARTING_ROW[] = {0, 0, 4, 0, 2, 0, 1};
+static const int COL_INCREMENT[] = {8, 8, 4, 4, 2, 2, 1};
+static const int ROW_INCREMENT[] = {8, 8, 8, 4, 4, 2, 2};
+
+/* Get the length in bytes of a scanline in the pass specified,
+ * for interlaced images */
+static int
+get_row_len(ImagingCodecState state, int pass) {
+ int row_len = (state->xsize + OFFSET[pass]) / COL_INCREMENT[pass];
+ return ((row_len * state->bits) + 7) / 8;
+}
+
+/* -------------------------------------------------------------------- */
+/* Decoder */
+/* -------------------------------------------------------------------- */
+
+int
+ImagingZipDecode(Imaging im, ImagingCodecState state, UINT8 *buf, Py_ssize_t bytes) {
+ ZIPSTATE *context = (ZIPSTATE *)state->context;
+ int err;
+ int n;
+ UINT8 *ptr;
+ int i, bpp;
+ int row_len;
+
+ if (!state->state) {
+ /* Initialization */
+ if (context->mode == ZIP_PNG || context->mode == ZIP_PNG_PALETTE) {
+ context->prefix = 1; /* PNG */
+ }
+
+ /* overflow check for malloc */
+ if (state->bytes > INT_MAX - 1) {
+ state->errcode = IMAGING_CODEC_MEMORY;
+ return -1;
+ }
+ /* Expand standard buffer to make room for the (optional) filter
+ prefix, and allocate a buffer to hold the previous line */
+ free(state->buffer);
+ /* malloc check ok, overflow checked above */
+ state->buffer = (UINT8 *)malloc(state->bytes + 1);
+ context->previous = (UINT8 *)malloc(state->bytes + 1);
+ if (!state->buffer || !context->previous) {
+ state->errcode = IMAGING_CODEC_MEMORY;
+ return -1;
+ }
+
+ context->last_output = 0;
+
+ /* Initialize to black */
+ memset(context->previous, 0, state->bytes + 1);
+
+ /* Setup decompression context */
+ context->z_stream.zalloc = (alloc_func)NULL;
+ context->z_stream.zfree = (free_func)NULL;
+ context->z_stream.opaque = (voidpf)NULL;
+
+ err = inflateInit(&context->z_stream);
+ if (err < 0) {
+ state->errcode = IMAGING_CODEC_CONFIG;
+ free(context->previous);
+ context->previous = NULL;
+ return -1;
+ }
+
+ if (context->interlaced) {
+ context->pass = 0;
+ state->y = STARTING_ROW[context->pass];
+ }
+
+ /* Ready to decode */
+ state->state = 1;
+ }
+
+ if (context->interlaced) {
+ row_len = get_row_len(state, context->pass);
+ } else {
+ row_len = state->bytes;
+ }
+
+ /* Setup the source buffer */
+ context->z_stream.next_in = buf;
+ context->z_stream.avail_in = bytes;
+
+ /* Decompress what we've got this far */
+ while (context->z_stream.avail_in > 0) {
+ context->z_stream.next_out = state->buffer + context->last_output;
+ context->z_stream.avail_out = row_len + context->prefix - context->last_output;
+
+ err = inflate(&context->z_stream, Z_NO_FLUSH);
+
+ if (err < 0) {
+ /* Something went wrong inside the compression library */
+ if (err == Z_DATA_ERROR) {
+ state->errcode = IMAGING_CODEC_BROKEN;
+ } else if (err == Z_MEM_ERROR) {
+ state->errcode = IMAGING_CODEC_MEMORY;
+ } else {
+ state->errcode = IMAGING_CODEC_CONFIG;
+ }
+ free(context->previous);
+ context->previous = NULL;
+ inflateEnd(&context->z_stream);
+ return -1;
+ }
+
+ n = row_len + context->prefix - context->z_stream.avail_out;
+
+ if (n < row_len + context->prefix) {
+ context->last_output = n;
+ break; /* need more input data */
+ }
+
+ /* Apply predictor */
+ switch (context->mode) {
+ case ZIP_PNG:
+ switch (state->buffer[0]) {
+ case 0:
+ break;
+ case 1:
+ /* prior */
+ bpp = (state->bits + 7) / 8;
+ for (i = bpp + 1; i <= row_len; i++) {
+ state->buffer[i] += state->buffer[i - bpp];
+ }
+ break;
+ case 2:
+ /* up */
+ for (i = 1; i <= row_len; i++) {
+ state->buffer[i] += context->previous[i];
+ }
+ break;
+ case 3:
+ /* average */
+ bpp = (state->bits + 7) / 8;
+ for (i = 1; i <= bpp; i++) {
+ state->buffer[i] += context->previous[i] / 2;
+ }
+ for (; i <= row_len; i++) {
+ state->buffer[i] +=
+ (state->buffer[i - bpp] + context->previous[i]) / 2;
+ }
+ break;
+ case 4:
+ /* paeth filtering */
+ bpp = (state->bits + 7) / 8;
+ for (i = 1; i <= bpp; i++) {
+ state->buffer[i] += context->previous[i];
+ }
+ for (; i <= row_len; i++) {
+ int a, b, c;
+ int pa, pb, pc;
+
+ /* fetch pixels */
+ a = state->buffer[i - bpp];
+ b = context->previous[i];
+ c = context->previous[i - bpp];
+
+ /* distances to surrounding pixels */
+ pa = abs(b - c);
+ pb = abs(a - c);
+ pc = abs(a + b - 2 * c);
+
+ /* pick predictor with the shortest distance */
+ state->buffer[i] += (pa <= pb && pa <= pc) ? a
+ : (pb <= pc) ? b
+ : c;
+ }
+ break;
+ default:
+ state->errcode = IMAGING_CODEC_UNKNOWN;
+ free(context->previous);
+ context->previous = NULL;
+ inflateEnd(&context->z_stream);
+ return -1;
+ }
+ break;
+ case ZIP_TIFF_PREDICTOR:
+ bpp = (state->bits + 7) / 8;
+ for (i = bpp + 1; i <= row_len; i++) {
+ state->buffer[i] += state->buffer[i - bpp];
+ }
+ break;
+ }
+
+ /* Stuff data into the image */
+ if (context->interlaced) {
+ int col = STARTING_COL[context->pass];
+ if (state->bits >= 8) {
+ /* Stuff pixels in their correct location, one by one */
+ for (i = 0; i < row_len; i += ((state->bits + 7) / 8)) {
+ state->shuffle(
+ (UINT8 *)im->image[state->y] + col * im->pixelsize,
+ state->buffer + context->prefix + i,
+ 1);
+ col += COL_INCREMENT[context->pass];
+ }
+ } else {
+ /* Handle case with more than a pixel in each byte */
+ int row_bits = ((state->xsize + OFFSET[context->pass]) /
+ COL_INCREMENT[context->pass]) *
+ state->bits;
+ for (i = 0; i < row_bits; i += state->bits) {
+ UINT8 byte = *(state->buffer + context->prefix + (i / 8));
+ byte <<= (i % 8);
+ state->shuffle(
+ (UINT8 *)im->image[state->y] + col * im->pixelsize, &byte, 1);
+ col += COL_INCREMENT[context->pass];
+ }
+ }
+ /* Find next valid scanline */
+ state->y += ROW_INCREMENT[context->pass];
+ while (state->y >= state->ysize || row_len <= 0) {
+ context->pass++;
+ if (context->pass == 7) {
+ /* Force exit below */
+ state->y = state->ysize;
+ break;
+ }
+ state->y = STARTING_ROW[context->pass];
+ row_len = get_row_len(state, context->pass);
+ /* Since we're moving to the "first" line, the previous line
+ * should be black to make filters work correctly */
+ memset(state->buffer, 0, state->bytes + 1);
+ }
+ } else {
+ state->shuffle(
+ (UINT8 *)im->image[state->y + state->yoff] +
+ state->xoff * im->pixelsize,
+ state->buffer + context->prefix,
+ state->xsize);
+ state->y++;
+ }
+
+ /* all inflate output has been consumed */
+ context->last_output = 0;
+
+ if (state->y >= state->ysize || err == Z_STREAM_END) {
+ /* The image and the data should end simultaneously */
+ /* if (state->y < state->ysize || err != Z_STREAM_END)
+ state->errcode = IMAGING_CODEC_BROKEN; */
+
+ free(context->previous);
+ context->previous = NULL;
+ inflateEnd(&context->z_stream);
+ return -1; /* end of file (errcode=0) */
+ }
+
+ /* Swap buffer pointers */
+ ptr = state->buffer;
+ state->buffer = context->previous;
+ context->previous = ptr;
+ }
+
+ return bytes; /* consumed all of it */
+}
+
+int
+ImagingZipDecodeCleanup(ImagingCodecState state) {
+ /* called to free the decompression engine when the decode terminates
+ due to a corrupt or truncated image
+ */
+ ZIPSTATE *context = (ZIPSTATE *)state->context;
+
+ /* Clean up */
+ if (context->previous) {
+ inflateEnd(&context->z_stream);
+ free(context->previous);
+ context->previous = NULL;
+ }
+ return -1;
+}
+
+#endif
diff --git a/contrib/python/Pillow/py3/libImaging/ZipEncode.c b/contrib/python/Pillow/py3/libImaging/ZipEncode.c
new file mode 100644
index 00000000000..edbce36822c
--- /dev/null
+++ b/contrib/python/Pillow/py3/libImaging/ZipEncode.c
@@ -0,0 +1,367 @@
+/*
+ * The Python Imaging Library.
+ * $Id$
+ *
+ * coder for ZIP (deflated) image data
+ *
+ * History:
+ * 96-12-29 fl created
+ * 96-12-30 fl adaptive filter selection, encoder tuning
+ *
+ * Copyright (c) Fredrik Lundh 1996.
+ * Copyright (c) Secret Labs AB 1997.
+ *
+ * See the README file for information on usage and redistribution.
+ */
+
+#include "Imaging.h"
+
+#ifdef HAVE_LIBZ
+
+#include "ZipCodecs.h"
+
+int
+ImagingZipEncode(Imaging im, ImagingCodecState state, UINT8 *buf, int bytes) {
+ ZIPSTATE *context = (ZIPSTATE *)state->context;
+ int err;
+ int compress_level, compress_type;
+ UINT8 *ptr;
+ int i, bpp, s, sum;
+ ImagingSectionCookie cookie;
+
+ if (!state->state) {
+ /* Initialization */
+
+ /* Valid modes are ZIP_PNG, ZIP_PNG_PALETTE, and ZIP_TIFF */
+
+ /* overflow check for malloc */
+ if (state->bytes > INT_MAX - 1) {
+ state->errcode = IMAGING_CODEC_MEMORY;
+ return -1;
+ }
+
+ /* Expand standard buffer to make room for the filter selector,
+ and allocate filter buffers */
+ free(state->buffer);
+ /* malloc check ok, overflow checked above */
+ state->buffer = (UINT8 *)malloc(state->bytes + 1);
+ context->previous = (UINT8 *)malloc(state->bytes + 1);
+ context->prior = (UINT8 *)malloc(state->bytes + 1);
+ context->up = (UINT8 *)malloc(state->bytes + 1);
+ context->average = (UINT8 *)malloc(state->bytes + 1);
+ context->paeth = (UINT8 *)malloc(state->bytes + 1);
+ if (!state->buffer || !context->previous || !context->prior || !context->up ||
+ !context->average || !context->paeth) {
+ free(context->paeth);
+ free(context->average);
+ free(context->up);
+ free(context->prior);
+ free(context->previous);
+ state->errcode = IMAGING_CODEC_MEMORY;
+ return -1;
+ }
+
+ /* Initialise filter buffers */
+ state->buffer[0] = 0;
+ context->prior[0] = 1;
+ context->up[0] = 2;
+ context->average[0] = 3;
+ context->paeth[0] = 4;
+
+ /* Initialise previous buffer to black */
+ memset(context->previous, 0, state->bytes + 1);
+
+ /* Setup compression context */
+ context->z_stream.zalloc = (alloc_func)0;
+ context->z_stream.zfree = (free_func)0;
+ context->z_stream.opaque = (voidpf)0;
+ context->z_stream.next_in = 0;
+ context->z_stream.avail_in = 0;
+
+ compress_level =
+ (context->optimize) ? Z_BEST_COMPRESSION : context->compress_level;
+
+ if (context->compress_type == -1) {
+ compress_type =
+ (context->mode == ZIP_PNG) ? Z_FILTERED : Z_DEFAULT_STRATEGY;
+ } else {
+ compress_type = context->compress_type;
+ }
+
+ err = deflateInit2(
+ &context->z_stream,
+ /* compression level */
+ compress_level,
+ /* compression method */
+ Z_DEFLATED,
+ /* compression memory resources */
+ 15,
+ 9,
+ /* compression strategy (image data are filtered)*/
+ compress_type);
+ if (err < 0) {
+ state->errcode = IMAGING_CODEC_CONFIG;
+ return -1;
+ }
+
+ if (context->dictionary && context->dictionary_size > 0) {
+ err = deflateSetDictionary(
+ &context->z_stream,
+ (unsigned char *)context->dictionary,
+ context->dictionary_size);
+ if (err < 0) {
+ state->errcode = IMAGING_CODEC_CONFIG;
+ return -1;
+ }
+ }
+
+ /* Ready to decode */
+ state->state = 1;
+ }
+
+ /* Setup the destination buffer */
+ context->z_stream.next_out = buf;
+ context->z_stream.avail_out = bytes;
+ if (context->z_stream.next_in && context->z_stream.avail_in > 0) {
+ /* We have some data from previous round, deflate it first */
+ err = deflate(&context->z_stream, Z_NO_FLUSH);
+
+ if (err < 0) {
+ /* Something went wrong inside the compression library */
+ if (err == Z_DATA_ERROR) {
+ state->errcode = IMAGING_CODEC_BROKEN;
+ } else if (err == Z_MEM_ERROR) {
+ state->errcode = IMAGING_CODEC_MEMORY;
+ } else {
+ state->errcode = IMAGING_CODEC_CONFIG;
+ }
+ free(context->paeth);
+ free(context->average);
+ free(context->up);
+ free(context->prior);
+ free(context->previous);
+ deflateEnd(&context->z_stream);
+ return -1;
+ }
+ }
+
+ ImagingSectionEnter(&cookie);
+ for (;;) {
+ switch (state->state) {
+ case 1:
+
+ /* Compress image data */
+ while (context->z_stream.avail_out > 0) {
+ if (state->y >= state->ysize) {
+ /* End of image; now flush compressor buffers */
+ state->state = 2;
+ break;
+ }
+
+ /* Stuff image data into the compressor */
+ state->shuffle(
+ state->buffer + 1,
+ (UINT8 *)im->image[state->y + state->yoff] +
+ state->xoff * im->pixelsize,
+ state->xsize);
+
+ state->y++;
+
+ context->output = state->buffer;
+
+ if (context->mode == ZIP_PNG) {
+ /* Filter the image data. For each line, select
+ the filter that gives the least total distance
+ from zero for the filtered data (taken from
+ LIBPNG) */
+
+ bpp = (state->bits + 7) / 8;
+
+ /* 0. No filter */
+ for (i = 1, sum = 0; i <= state->bytes; i++) {
+ UINT8 v = state->buffer[i];
+ sum += (v < 128) ? v : 256 - v;
+ }
+
+ /* 2. Up. We'll test this first to save time when
+ an image line is identical to the one above. */
+ if (sum > 0) {
+ for (i = 1, s = 0; i <= state->bytes; i++) {
+ UINT8 v = state->buffer[i] - context->previous[i];
+ context->up[i] = v;
+ s += (v < 128) ? v : 256 - v;
+ }
+ if (s < sum) {
+ context->output = context->up;
+ sum = s; /* 0 if line was duplicated */
+ }
+ }
+
+ /* 1. Prior */
+ if (sum > 0) {
+ for (i = 1, s = 0; i <= bpp; i++) {
+ UINT8 v = state->buffer[i];
+ context->prior[i] = v;
+ s += (v < 128) ? v : 256 - v;
+ }
+ for (; i <= state->bytes; i++) {
+ UINT8 v = state->buffer[i] - state->buffer[i - bpp];
+ context->prior[i] = v;
+ s += (v < 128) ? v : 256 - v;
+ }
+ if (s < sum) {
+ context->output = context->prior;
+ sum = s; /* 0 if line is solid */
+ }
+ }
+
+ /* 3. Average (not very common in real-life images,
+ so its only used with the optimize option) */
+ if (context->optimize && sum > 0) {
+ for (i = 1, s = 0; i <= bpp; i++) {
+ UINT8 v = state->buffer[i] - context->previous[i] / 2;
+ context->average[i] = v;
+ s += (v < 128) ? v : 256 - v;
+ }
+ for (; i <= state->bytes; i++) {
+ UINT8 v =
+ state->buffer[i] -
+ (state->buffer[i - bpp] + context->previous[i]) / 2;
+ context->average[i] = v;
+ s += (v < 128) ? v : 256 - v;
+ }
+ if (s < sum) {
+ context->output = context->average;
+ sum = s;
+ }
+ }
+
+ /* 4. Paeth */
+ if (sum > 0) {
+ for (i = 1, s = 0; i <= bpp; i++) {
+ UINT8 v = state->buffer[i] - context->previous[i];
+ context->paeth[i] = v;
+ s += (v < 128) ? v : 256 - v;
+ }
+ for (; i <= state->bytes; i++) {
+ UINT8 v;
+ int a, b, c;
+ int pa, pb, pc;
+
+ /* fetch pixels */
+ a = state->buffer[i - bpp];
+ b = context->previous[i];
+ c = context->previous[i - bpp];
+
+ /* distances to surrounding pixels */
+ pa = abs(b - c);
+ pb = abs(a - c);
+ pc = abs(a + b - 2 * c);
+
+ /* pick predictor with the shortest distance */
+ v = state->buffer[i] - ((pa <= pb && pa <= pc) ? a
+ : (pb <= pc) ? b
+ : c);
+ context->paeth[i] = v;
+ s += (v < 128) ? v : 256 - v;
+ }
+ if (s < sum) {
+ context->output = context->paeth;
+ sum = s;
+ }
+ }
+ }
+
+ /* Compress this line */
+ context->z_stream.next_in = context->output;
+ context->z_stream.avail_in = state->bytes + 1;
+
+ err = deflate(&context->z_stream, Z_NO_FLUSH);
+
+ if (err < 0) {
+ /* Something went wrong inside the compression library */
+ if (err == Z_DATA_ERROR) {
+ state->errcode = IMAGING_CODEC_BROKEN;
+ } else if (err == Z_MEM_ERROR) {
+ state->errcode = IMAGING_CODEC_MEMORY;
+ } else {
+ state->errcode = IMAGING_CODEC_CONFIG;
+ }
+ free(context->paeth);
+ free(context->average);
+ free(context->up);
+ free(context->prior);
+ free(context->previous);
+ deflateEnd(&context->z_stream);
+ ImagingSectionLeave(&cookie);
+ return -1;
+ }
+
+ /* Swap buffer pointers */
+ ptr = state->buffer;
+ state->buffer = context->previous;
+ context->previous = ptr;
+ }
+
+ if (context->z_stream.avail_out == 0) {
+ break; /* Buffer full */
+ }
+
+ case 2:
+
+ /* End of image data; flush compressor buffers */
+
+ while (context->z_stream.avail_out > 0) {
+ err = deflate(&context->z_stream, Z_FINISH);
+
+ if (err == Z_STREAM_END) {
+ free(context->paeth);
+ free(context->average);
+ free(context->up);
+ free(context->prior);
+ free(context->previous);
+
+ deflateEnd(&context->z_stream);
+
+ state->errcode = IMAGING_CODEC_END;
+
+ break;
+ }
+
+ if (context->z_stream.avail_out == 0) {
+ break; /* Buffer full */
+ }
+ }
+ }
+ ImagingSectionLeave(&cookie);
+ return bytes - context->z_stream.avail_out;
+ }
+
+ /* Should never ever arrive here... */
+ state->errcode = IMAGING_CODEC_CONFIG;
+ ImagingSectionLeave(&cookie);
+ return -1;
+}
+
+/* -------------------------------------------------------------------- */
+/* Cleanup */
+/* -------------------------------------------------------------------- */
+
+int
+ImagingZipEncodeCleanup(ImagingCodecState state) {
+ ZIPSTATE *context = (ZIPSTATE *)state->context;
+
+ if (context->dictionary) {
+ free(context->dictionary);
+ context->dictionary = NULL;
+ }
+
+ return -1;
+}
+
+const char *
+ImagingZipVersion(void) {
+ return zlibVersion();
+}
+
+#endif
diff --git a/contrib/python/Pillow/py3/libImaging/codec_fd.c b/contrib/python/Pillow/py3/libImaging/codec_fd.c
new file mode 100644
index 00000000000..5261681107b
--- /dev/null
+++ b/contrib/python/Pillow/py3/libImaging/codec_fd.c
@@ -0,0 +1,69 @@
+#include "Python.h"
+#include "Imaging.h"
+
+Py_ssize_t
+_imaging_read_pyFd(PyObject *fd, char *dest, Py_ssize_t bytes) {
+ /* dest should be a buffer bytes long, returns length of read
+ -1 on error */
+
+ PyObject *result;
+ char *buffer;
+ Py_ssize_t length;
+ int bytes_result;
+
+ result = PyObject_CallMethod(fd, "read", "n", bytes);
+
+ bytes_result = PyBytes_AsStringAndSize(result, &buffer, &length);
+ if (bytes_result == -1) {
+ goto err;
+ }
+
+ if (length > bytes) {
+ goto err;
+ }
+
+ memcpy(dest, buffer, length);
+
+ Py_DECREF(result);
+ return length;
+
+err:
+ Py_DECREF(result);
+ return -1;
+}
+
+Py_ssize_t
+_imaging_write_pyFd(PyObject *fd, char *src, Py_ssize_t bytes) {
+ PyObject *result;
+ PyObject *byteObj;
+
+ byteObj = PyBytes_FromStringAndSize(src, bytes);
+ result = PyObject_CallMethod(fd, "write", "O", byteObj);
+
+ Py_DECREF(byteObj);
+ Py_DECREF(result);
+
+ return bytes;
+}
+
+int
+_imaging_seek_pyFd(PyObject *fd, Py_ssize_t offset, int whence) {
+ PyObject *result;
+
+ result = PyObject_CallMethod(fd, "seek", "ni", offset, whence);
+
+ Py_DECREF(result);
+ return 0;
+}
+
+Py_ssize_t
+_imaging_tell_pyFd(PyObject *fd) {
+ PyObject *result;
+ Py_ssize_t location;
+
+ result = PyObject_CallMethod(fd, "tell", NULL);
+ location = PyLong_AsSsize_t(result);
+
+ Py_DECREF(result);
+ return location;
+}
diff --git a/contrib/python/Pillow/py3/map.c b/contrib/python/Pillow/py3/map.c
new file mode 100644
index 00000000000..c298bd1482a
--- /dev/null
+++ b/contrib/python/Pillow/py3/map.c
@@ -0,0 +1,146 @@
+/*
+ * The Python Imaging Library.
+ *
+ * standard memory mapping interface for the Imaging library
+ *
+ * history:
+ * 1998-03-05 fl added Win32 read mapping
+ * 1999-02-06 fl added "I;16" support
+ * 2003-04-21 fl added PyImaging_MapBuffer primitive
+ *
+ * Copyright (c) 1998-2003 by Secret Labs AB.
+ * Copyright (c) 2003 by Fredrik Lundh.
+ *
+ * See the README file for information on usage and redistribution.
+ */
+
+/*
+ * FIXME: should move the memory mapping primitives into libImaging!
+ */
+
+#include "Python.h"
+
+#include "libImaging/Imaging.h"
+
+/* compatibility wrappers (defined in _imaging.c) */
+extern int
+PyImaging_CheckBuffer(PyObject *buffer);
+extern int
+PyImaging_GetBuffer(PyObject *buffer, Py_buffer *view);
+
+extern PyObject *
+PyImagingNew(Imaging im);
+
+/* -------------------------------------------------------------------- */
+/* Buffer mapper */
+
+typedef struct ImagingBufferInstance {
+ struct ImagingMemoryInstance im;
+ PyObject *target;
+ Py_buffer view;
+} ImagingBufferInstance;
+
+static void
+mapping_destroy_buffer(Imaging im) {
+ ImagingBufferInstance *buffer = (ImagingBufferInstance *)im;
+
+ PyBuffer_Release(&buffer->view);
+ Py_XDECREF(buffer->target);
+}
+
+PyObject *
+PyImaging_MapBuffer(PyObject *self, PyObject *args) {
+ Py_ssize_t y, size;
+ Imaging im;
+
+ PyObject *target;
+ Py_buffer view;
+ char *mode;
+ char *codec;
+ Py_ssize_t offset;
+ int xsize, ysize;
+ int stride;
+ int ystep;
+
+ if (!PyArg_ParseTuple(
+ args,
+ "O(ii)sn(sii)",
+ &target,
+ &xsize,
+ &ysize,
+ &codec,
+ &offset,
+ &mode,
+ &stride,
+ &ystep)) {
+ return NULL;
+ }
+
+ if (!PyImaging_CheckBuffer(target)) {
+ PyErr_SetString(PyExc_TypeError, "expected string or buffer");
+ return NULL;
+ }
+
+ if (stride <= 0) {
+ if (!strcmp(mode, "L") || !strcmp(mode, "P")) {
+ stride = xsize;
+ } else if (!strncmp(mode, "I;16", 4)) {
+ stride = xsize * 2;
+ } else {
+ stride = xsize * 4;
+ }
+ }
+
+ if (stride > 0 && ysize > PY_SSIZE_T_MAX / stride) {
+ PyErr_SetString(PyExc_MemoryError, "Integer overflow in ysize");
+ return NULL;
+ }
+
+ size = (Py_ssize_t)ysize * stride;
+
+ if (offset > PY_SSIZE_T_MAX - size) {
+ PyErr_SetString(PyExc_MemoryError, "Integer overflow in offset");
+ return NULL;
+ }
+
+ /* check buffer size */
+ if (PyImaging_GetBuffer(target, &view) < 0) {
+ return NULL;
+ }
+
+ if (view.len < 0) {
+ PyErr_SetString(PyExc_ValueError, "buffer has negative size");
+ PyBuffer_Release(&view);
+ return NULL;
+ }
+ if (offset + size > view.len) {
+ PyErr_SetString(PyExc_ValueError, "buffer is not large enough");
+ PyBuffer_Release(&view);
+ return NULL;
+ }
+
+ im = ImagingNewPrologueSubtype(mode, xsize, ysize, sizeof(ImagingBufferInstance));
+ if (!im) {
+ PyBuffer_Release(&view);
+ return NULL;
+ }
+
+ /* setup file pointers */
+ if (ystep > 0) {
+ for (y = 0; y < ysize; y++) {
+ im->image[y] = (char *)view.buf + offset + y * stride;
+ }
+ } else {
+ for (y = 0; y < ysize; y++) {
+ im->image[ysize - y - 1] = (char *)view.buf + offset + y * stride;
+ }
+ }
+
+ im->destroy = mapping_destroy_buffer;
+
+ Py_INCREF(target);
+ ((ImagingBufferInstance *)im)->target = target;
+ ((ImagingBufferInstance *)im)->view = view;
+
+ return PyImagingNew(im);
+}
diff --git a/contrib/python/Pillow/py3/outline.c b/contrib/python/Pillow/py3/outline.c
new file mode 100644
index 00000000000..27cc255cf84
--- /dev/null
+++ b/contrib/python/Pillow/py3/outline.c
@@ -0,0 +1,187 @@
+/*
+ * THIS IS WORK IN PROGRESS.
+ *
+ * The Python Imaging Library.
+ *
+ * "arrow" outline stuff. the contents of this module
+ * will be merged with the path module and the rest of
+ * the arrow graphics package, but not before PIL 1.1.
+ * use at your own risk.
+ *
+ * history:
+ * 99-01-10 fl Added to PIL (experimental)
+ *
+ * Copyright (c) Secret Labs AB 1999.
+ * Copyright (c) Fredrik Lundh 1999.
+ *
+ * See the README file for information on usage and redistribution.
+ */
+
+#include "Python.h"
+
+#include "libImaging/Imaging.h"
+
+/* -------------------------------------------------------------------- */
+/* Class */
+
+typedef struct {
+ PyObject_HEAD ImagingOutline outline;
+} OutlineObject;
+
+static PyTypeObject OutlineType;
+
+#define PyOutline_Check(op) (Py_TYPE(op) == &OutlineType)
+
+static OutlineObject *
+_outline_new(void) {
+ OutlineObject *self;
+
+ if (PyType_Ready(&OutlineType) < 0) {
+ return NULL;
+ }
+
+ self = PyObject_New(OutlineObject, &OutlineType);
+ if (self == NULL) {
+ return NULL;
+ }
+
+ self->outline = ImagingOutlineNew();
+
+ return self;
+}
+
+static void
+_outline_dealloc(OutlineObject *self) {
+ ImagingOutlineDelete(self->outline);
+ PyObject_Del(self);
+}
+
+ImagingOutline
+PyOutline_AsOutline(PyObject *outline) {
+ if (PyOutline_Check(outline)) {
+ return ((OutlineObject *)outline)->outline;
+ }
+
+ return NULL;
+}
+
+/* -------------------------------------------------------------------- */
+/* Factories */
+
+PyObject *
+PyOutline_Create(PyObject *self, PyObject *args) {
+ if (!PyArg_ParseTuple(args, ":outline")) {
+ return NULL;
+ }
+
+ return (PyObject *)_outline_new();
+}
+
+/* -------------------------------------------------------------------- */
+/* Methods */
+
+static PyObject *
+_outline_move(OutlineObject *self, PyObject *args) {
+ float x0, y0;
+ if (!PyArg_ParseTuple(args, "ff", &x0, &y0)) {
+ return NULL;
+ }
+
+ ImagingOutlineMove(self->outline, x0, y0);
+
+ Py_INCREF(Py_None);
+ return Py_None;
+}
+
+static PyObject *
+_outline_line(OutlineObject *self, PyObject *args) {
+ float x1, y1;
+ if (!PyArg_ParseTuple(args, "ff", &x1, &y1)) {
+ return NULL;
+ }
+
+ ImagingOutlineLine(self->outline, x1, y1);
+
+ Py_INCREF(Py_None);
+ return Py_None;
+}
+
+static PyObject *
+_outline_curve(OutlineObject *self, PyObject *args) {
+ float x1, y1, x2, y2, x3, y3;
+ if (!PyArg_ParseTuple(args, "ffffff", &x1, &y1, &x2, &y2, &x3, &y3)) {
+ return NULL;
+ }
+
+ ImagingOutlineCurve(self->outline, x1, y1, x2, y2, x3, y3);
+
+ Py_INCREF(Py_None);
+ return Py_None;
+}
+
+static PyObject *
+_outline_close(OutlineObject *self, PyObject *args) {
+ if (!PyArg_ParseTuple(args, ":close")) {
+ return NULL;
+ }
+
+ ImagingOutlineClose(self->outline);
+
+ Py_INCREF(Py_None);
+ return Py_None;
+}
+
+static PyObject *
+_outline_transform(OutlineObject *self, PyObject *args) {
+ double a[6];
+ if (!PyArg_ParseTuple(args, "(dddddd)", a + 0, a + 1, a + 2, a + 3, a + 4, a + 5)) {
+ return NULL;
+ }
+
+ ImagingOutlineTransform(self->outline, a);
+
+ Py_INCREF(Py_None);
+ return Py_None;
+}
+
+static struct PyMethodDef _outline_methods[] = {
+ {"line", (PyCFunction)_outline_line, METH_VARARGS},
+ {"curve", (PyCFunction)_outline_curve, METH_VARARGS},
+ {"move", (PyCFunction)_outline_move, METH_VARARGS},
+ {"close", (PyCFunction)_outline_close, METH_VARARGS},
+ {"transform", (PyCFunction)_outline_transform, METH_VARARGS},
+ {NULL, NULL} /* sentinel */
+};
+
+static PyTypeObject OutlineType = {
+ PyVarObject_HEAD_INIT(NULL, 0) "Outline", /*tp_name*/
+ sizeof(OutlineObject), /*tp_basicsize*/
+ 0, /*tp_itemsize*/
+ /* methods */
+ (destructor)_outline_dealloc, /*tp_dealloc*/
+ 0, /*tp_vectorcall_offset*/
+ 0, /*tp_getattr*/
+ 0, /*tp_setattr*/
+ 0, /*tp_as_async*/
+ 0, /*tp_repr*/
+ 0, /*tp_as_number*/
+ 0, /*tp_as_sequence*/
+ 0, /*tp_as_mapping*/
+ 0, /*tp_hash*/
+ 0, /*tp_call*/
+ 0, /*tp_str*/
+ 0, /*tp_getattro*/
+ 0, /*tp_setattro*/
+ 0, /*tp_as_buffer*/
+ Py_TPFLAGS_DEFAULT, /*tp_flags*/
+ 0, /*tp_doc*/
+ 0, /*tp_traverse*/
+ 0, /*tp_clear*/
+ 0, /*tp_richcompare*/
+ 0, /*tp_weaklistoffset*/
+ 0, /*tp_iter*/
+ 0, /*tp_iternext*/
+ _outline_methods, /*tp_methods*/
+ 0, /*tp_members*/
+ 0, /*tp_getset*/
+};
diff --git a/contrib/python/Pillow/py3/path.c b/contrib/python/Pillow/py3/path.c
new file mode 100644
index 00000000000..cc0698c4d2a
--- /dev/null
+++ b/contrib/python/Pillow/py3/path.c
@@ -0,0 +1,615 @@
+/*
+ * The Python Imaging Library.
+ *
+ * 2D path utilities
+ *
+ * history:
+ * 1996-11-04 fl Added to PIL (incomplete)
+ * 1996-11-05 fl Added sequence semantics
+ * 1997-02-28 fl Fixed getbbox
+ * 1997-06-12 fl Added id attribute
+ * 1997-06-14 fl Added slicing and setitem
+ * 1998-12-29 fl Improved sequence handling (from Richard Jones)
+ * 1999-01-10 fl Fixed IndexError test for 1.5 (from Fred Drake)
+ * 2000-10-12 fl Added special cases for tuples and lists
+ * 2002-10-27 fl Added clipping boilerplate
+ * 2004-09-19 fl Added tolist(flat) variant
+ * 2005-05-06 fl Added buffer interface support to path constructor
+ *
+ * notes:
+ * FIXME: fill in remaining slots in the sequence api
+ *
+ * Copyright (c) 1997-2005 by Secret Labs AB
+ * Copyright (c) 1997-2005 by Fredrik Lundh
+ *
+ * See the README file for information on usage and redistribution.
+ */
+
+#include "Python.h"
+#include "libImaging/Imaging.h"
+
+#include <math.h>
+
+/* compatibility wrappers (defined in _imaging.c) */
+extern int
+PyImaging_CheckBuffer(PyObject *buffer);
+extern int
+PyImaging_GetBuffer(PyObject *buffer, Py_buffer *view);
+
+/* -------------------------------------------------------------------- */
+/* Class */
+/* -------------------------------------------------------------------- */
+
+typedef struct {
+ PyObject_HEAD Py_ssize_t count;
+ double *xy;
+ int index; /* temporary use, e.g. in decimate */
+} PyPathObject;
+
+static PyTypeObject PyPathType;
+
+static double *
+alloc_array(Py_ssize_t count) {
+ double *xy;
+ if (count < 0) {
+ return ImagingError_MemoryError();
+ }
+ if ((unsigned long long)count > (SIZE_MAX / (2 * sizeof(double))) - 1) {
+ return ImagingError_MemoryError();
+ }
+ xy = calloc(2 * count + 1, sizeof(double));
+ if (!xy) {
+ ImagingError_MemoryError();
+ }
+ return xy;
+}
+
+static PyPathObject *
+path_new(Py_ssize_t count, double *xy, int duplicate) {
+ PyPathObject *path;
+
+ if (duplicate) {
+ /* duplicate path */
+ double *p = alloc_array(count);
+ if (!p) {
+ return NULL;
+ }
+ memcpy(p, xy, count * 2 * sizeof(double));
+ xy = p;
+ }
+
+ if (PyType_Ready(&PyPathType) < 0) {
+ free(xy);
+ return NULL;
+ }
+
+ path = PyObject_New(PyPathObject, &PyPathType);
+ if (path == NULL) {
+ free(xy);
+ return NULL;
+ }
+
+ path->count = count;
+ path->xy = xy;
+
+ return path;
+}
+
+static void
+path_dealloc(PyPathObject *path) {
+ free(path->xy);
+ PyObject_Del(path);
+}
+
+/* -------------------------------------------------------------------- */
+/* Helpers */
+/* -------------------------------------------------------------------- */
+
+#define PyPath_Check(op) (Py_TYPE(op) == &PyPathType)
+
+Py_ssize_t
+PyPath_Flatten(PyObject *data, double **pxy) {
+ Py_ssize_t i, j, n;
+ double *xy;
+
+ if (PyPath_Check(data)) {
+ /* This was another path object. */
+ PyPathObject *path = (PyPathObject *)data;
+ xy = alloc_array(path->count);
+ if (!xy) {
+ return -1;
+ }
+ memcpy(xy, path->xy, 2 * path->count * sizeof(double));
+ *pxy = xy;
+ return path->count;
+ }
+
+ if (PyImaging_CheckBuffer(data)) {
+ /* Assume the buffer contains floats */
+ Py_buffer buffer;
+ if (PyImaging_GetBuffer(data, &buffer) == 0) {
+ float *ptr = (float *)buffer.buf;
+ n = buffer.len / (2 * sizeof(float));
+ xy = alloc_array(n);
+ if (!xy) {
+ return -1;
+ }
+ for (i = 0; i < n + n; i++) {
+ xy[i] = ptr[i];
+ }
+ *pxy = xy;
+ PyBuffer_Release(&buffer);
+ return n;
+ }
+ PyErr_Clear();
+ }
+
+ if (!PySequence_Check(data)) {
+ PyErr_SetString(PyExc_TypeError, "argument must be sequence");
+ return -1;
+ }
+
+ j = 0;
+ n = PyObject_Length(data);
+ /* Just in case __len__ breaks (or doesn't exist) */
+ if (PyErr_Occurred()) {
+ return -1;
+ }
+
+ /* Allocate for worst case */
+ xy = alloc_array(n);
+ if (!xy) {
+ return -1;
+ }
+
+#define assign_item_to_array(op, decref) \
+if (PyFloat_Check(op)) { \
+ xy[j++] = PyFloat_AS_DOUBLE(op); \
+} else if (PyLong_Check(op)) { \
+ xy[j++] = (float)PyLong_AS_LONG(op); \
+} else if (PyNumber_Check(op)) { \
+ xy[j++] = PyFloat_AsDouble(op); \
+} else if (PyArg_ParseTuple(op, "dd", &x, &y)) { \
+ xy[j++] = x; \
+ xy[j++] = y; \
+} else { \
+ PyErr_SetString(PyExc_ValueError, "incorrect coordinate type"); \
+ if (decref) { \
+ Py_DECREF(op); \
+ } \
+ free(xy); \
+ return -1; \
+}
+
+ /* Copy table to path array */
+ if (PyList_Check(data)) {
+ for (i = 0; i < n; i++) {
+ double x, y;
+ PyObject *op = PyList_GET_ITEM(data, i);
+ assign_item_to_array(op, 0);
+ }
+ } else if (PyTuple_Check(data)) {
+ for (i = 0; i < n; i++) {
+ double x, y;
+ PyObject *op = PyTuple_GET_ITEM(data, i);
+ assign_item_to_array(op, 0);
+ }
+ } else {
+ for (i = 0; i < n; i++) {
+ double x, y;
+ PyObject *op = PySequence_GetItem(data, i);
+ if (!op) {
+ /* treat IndexError as end of sequence */
+ if (PyErr_Occurred() && PyErr_ExceptionMatches(PyExc_IndexError)) {
+ PyErr_Clear();
+ break;
+ } else {
+ free(xy);
+ return -1;
+ }
+ }
+ assign_item_to_array(op, 1);
+ Py_DECREF(op);
+ }
+ }
+
+ if (j & 1) {
+ PyErr_SetString(PyExc_ValueError, "wrong number of coordinates");
+ free(xy);
+ return -1;
+ }
+
+ *pxy = xy;
+ return j / 2;
+}
+
+/* -------------------------------------------------------------------- */
+/* Factories */
+/* -------------------------------------------------------------------- */
+
+PyObject *
+PyPath_Create(PyObject *self, PyObject *args) {
+ PyObject *data;
+ Py_ssize_t count;
+ double *xy;
+
+ if (PyArg_ParseTuple(args, "n:Path", &count)) {
+ /* number of vertices */
+ xy = alloc_array(count);
+ if (!xy) {
+ return NULL;
+ }
+
+ } else {
+ /* sequence or other path */
+ PyErr_Clear();
+ if (!PyArg_ParseTuple(args, "O", &data)) {
+ return NULL;
+ }
+
+ count = PyPath_Flatten(data, &xy);
+ if (count < 0) {
+ return NULL;
+ }
+ }
+
+ return (PyObject *)path_new(count, xy, 0);
+}
+
+/* -------------------------------------------------------------------- */
+/* Methods */
+/* -------------------------------------------------------------------- */
+
+static PyObject *
+path_compact(PyPathObject *self, PyObject *args) {
+ /* Simple-minded method to shorten path. A point is removed if
+ the city block distance to the previous point is less than the
+ given distance */
+ Py_ssize_t i, j;
+ double *xy;
+
+ double cityblock = 2.0;
+
+ if (!PyArg_ParseTuple(args, "|d:compact", &cityblock)) {
+ return NULL;
+ }
+
+ xy = self->xy;
+
+ /* remove bogus vertices */
+ for (i = j = 1; i < self->count; i++) {
+ if (fabs(xy[j + j - 2] - xy[i + i]) + fabs(xy[j + j - 1] - xy[i + i + 1]) >=
+ cityblock) {
+ xy[j + j] = xy[i + i];
+ xy[j + j + 1] = xy[i + i + 1];
+ j++;
+ }
+ }
+
+ i = self->count - j;
+ self->count = j;
+
+ /* shrink coordinate array */
+ /* malloc check ok, self->count is smaller than it was before */
+ self->xy = realloc(self->xy, 2 * self->count * sizeof(double));
+
+ return Py_BuildValue("i", i); /* number of removed vertices */
+}
+
+static PyObject *
+path_getbbox(PyPathObject *self, PyObject *args) {
+ /* Find bounding box */
+ Py_ssize_t i;
+ double *xy;
+ double x0, y0, x1, y1;
+
+ if (!PyArg_ParseTuple(args, ":getbbox")) {
+ return NULL;
+ }
+
+ xy = self->xy;
+
+ if (self->count == 0) {
+ x0 = x1 = 0;
+ y0 = y1 = 0;
+ } else {
+ x0 = x1 = xy[0];
+ y0 = y1 = xy[1];
+
+ for (i = 1; i < self->count; i++) {
+ if (xy[i + i] < x0) {
+ x0 = xy[i + i];
+ }
+ if (xy[i + i] > x1) {
+ x1 = xy[i + i];
+ }
+ if (xy[i + i + 1] < y0) {
+ y0 = xy[i + i + 1];
+ }
+ if (xy[i + i + 1] > y1) {
+ y1 = xy[i + i + 1];
+ }
+ }
+ }
+
+ return Py_BuildValue("dddd", x0, y0, x1, y1);
+}
+
+static PyObject *
+path_getitem(PyPathObject *self, Py_ssize_t i) {
+ if (i < 0) {
+ i = self->count + i;
+ }
+ if (i < 0 || i >= self->count) {
+ PyErr_SetString(PyExc_IndexError, "path index out of range");
+ return NULL;
+ }
+
+ return Py_BuildValue("dd", self->xy[i + i], self->xy[i + i + 1]);
+}
+
+static PyObject *
+path_getslice(PyPathObject *self, Py_ssize_t ilow, Py_ssize_t ihigh) {
+ /* adjust arguments */
+ if (ilow < 0) {
+ ilow = 0;
+ } else if (ilow >= self->count) {
+ ilow = self->count;
+ }
+ if (ihigh < 0) {
+ ihigh = 0;
+ }
+ if (ihigh < ilow) {
+ ihigh = ilow;
+ } else if (ihigh > self->count) {
+ ihigh = self->count;
+ }
+
+ return (PyObject *)path_new(ihigh - ilow, self->xy + ilow * 2, 1);
+}
+
+static Py_ssize_t
+path_len(PyPathObject *self) {
+ return self->count;
+}
+
+static PyObject *
+path_map(PyPathObject *self, PyObject *args) {
+ /* Map coordinate set through function */
+ Py_ssize_t i;
+ double *xy;
+ PyObject *function;
+
+ if (!PyArg_ParseTuple(args, "O:map", &function)) {
+ return NULL;
+ }
+
+ xy = self->xy;
+
+ /* apply function to coordinate set */
+ for (i = 0; i < self->count; i++) {
+ double x = xy[i + i];
+ double y = xy[i + i + 1];
+ PyObject *item = PyObject_CallFunction(function, "dd", x, y);
+ if (!item || !PyArg_ParseTuple(item, "dd", &x, &y)) {
+ Py_XDECREF(item);
+ return NULL;
+ }
+ xy[i + i] = x;
+ xy[i + i + 1] = y;
+ Py_DECREF(item);
+ }
+
+ Py_INCREF(Py_None);
+ return Py_None;
+}
+
+static int
+path_setitem(PyPathObject *self, Py_ssize_t i, PyObject *op) {
+ double *xy;
+
+ if (i < 0 || i >= self->count) {
+ PyErr_SetString(PyExc_IndexError, "path assignment index out of range");
+ return -1;
+ }
+
+ if (op == NULL) {
+ PyErr_SetString(PyExc_TypeError, "cannot delete from path");
+ return -1;
+ }
+
+ xy = &self->xy[i + i];
+
+ if (!PyArg_ParseTuple(op, "dd", &xy[0], &xy[1])) {
+ return -1;
+ }
+
+ return 0;
+}
+
+static PyObject *
+path_tolist(PyPathObject *self, PyObject *args) {
+ PyObject *list;
+ Py_ssize_t i;
+
+ int flat = 0;
+ if (!PyArg_ParseTuple(args, "|i:tolist", &flat)) {
+ return NULL;
+ }
+
+ if (flat) {
+ list = PyList_New(self->count * 2);
+ if (list == NULL) {
+ return NULL;
+ }
+ for (i = 0; i < self->count * 2; i++) {
+ PyObject *item;
+ item = PyFloat_FromDouble(self->xy[i]);
+ if (!item) {
+ goto error;
+ }
+ PyList_SetItem(list, i, item);
+ }
+ } else {
+ list = PyList_New(self->count);
+ if (list == NULL) {
+ return NULL;
+ }
+ for (i = 0; i < self->count; i++) {
+ PyObject *item;
+ item = Py_BuildValue("dd", self->xy[i + i], self->xy[i + i + 1]);
+ if (!item) {
+ goto error;
+ }
+ PyList_SetItem(list, i, item);
+ }
+ }
+
+ return list;
+
+error:
+ Py_DECREF(list);
+ return NULL;
+}
+
+static PyObject *
+path_transform(PyPathObject *self, PyObject *args) {
+ /* Apply affine transform to coordinate set */
+ Py_ssize_t i;
+ double *xy;
+ double a, b, c, d, e, f;
+
+ double wrap = 0.0;
+
+ if (!PyArg_ParseTuple(
+ args, "(dddddd)|d:transform", &a, &b, &c, &d, &e, &f, &wrap)) {
+ return NULL;
+ }
+
+ xy = self->xy;
+
+ /* transform the coordinate set */
+ if (b == 0.0 && d == 0.0) {
+ /* scaling */
+ for (i = 0; i < self->count; i++) {
+ xy[i + i] = a * xy[i + i] + c;
+ xy[i + i + 1] = e * xy[i + i + 1] + f;
+ }
+ } else {
+ /* affine transform */
+ for (i = 0; i < self->count; i++) {
+ double x = xy[i + i];
+ double y = xy[i + i + 1];
+ xy[i + i] = a * x + b * y + c;
+ xy[i + i + 1] = d * x + e * y + f;
+ }
+ }
+
+ /* special treatment of geographical map data */
+ if (wrap != 0.0) {
+ for (i = 0; i < self->count; i++) {
+ xy[i + i] = fmod(xy[i + i], wrap);
+ }
+ }
+
+ Py_INCREF(Py_None);
+ return Py_None;
+}
+
+static struct PyMethodDef methods[] = {
+ {"getbbox", (PyCFunction)path_getbbox, METH_VARARGS},
+ {"tolist", (PyCFunction)path_tolist, METH_VARARGS},
+ {"compact", (PyCFunction)path_compact, METH_VARARGS},
+ {"map", (PyCFunction)path_map, METH_VARARGS},
+ {"transform", (PyCFunction)path_transform, METH_VARARGS},
+ {NULL, NULL} /* sentinel */
+};
+
+static PyObject *
+path_getattr_id(PyPathObject *self, void *closure) {
+ return Py_BuildValue("n", (Py_ssize_t)self->xy);
+}
+
+static struct PyGetSetDef getsetters[] = {{"id", (getter)path_getattr_id}, {NULL}};
+
+static PyObject *
+path_subscript(PyPathObject *self, PyObject *item) {
+ if (PyIndex_Check(item)) {
+ Py_ssize_t i;
+ i = PyNumber_AsSsize_t(item, PyExc_IndexError);
+ if (i == -1 && PyErr_Occurred()) {
+ return NULL;
+ }
+ return path_getitem(self, i);
+ }
+ if (PySlice_Check(item)) {
+ int len = 4;
+ Py_ssize_t start, stop, step, slicelength;
+
+ if (PySlice_GetIndicesEx(item, len, &start, &stop, &step, &slicelength) < 0) {
+ return NULL;
+ }
+
+ if (slicelength <= 0) {
+ double *xy = alloc_array(0);
+ return (PyObject *)path_new(0, xy, 0);
+ } else if (step == 1) {
+ return path_getslice(self, start, stop);
+ } else {
+ PyErr_SetString(PyExc_TypeError, "slice steps not supported");
+ return NULL;
+ }
+ } else {
+ PyErr_Format(
+ PyExc_TypeError,
+ "Path indices must be integers, not %.200s",
+ Py_TYPE(item)->tp_name);
+ return NULL;
+ }
+}
+
+static PySequenceMethods path_as_sequence = {
+ (lenfunc)path_len, /*sq_length*/
+ (binaryfunc)0, /*sq_concat*/
+ (ssizeargfunc)0, /*sq_repeat*/
+ (ssizeargfunc)path_getitem, /*sq_item*/
+ (ssizessizeargfunc)path_getslice, /*sq_slice*/
+ (ssizeobjargproc)path_setitem, /*sq_ass_item*/
+ (ssizessizeobjargproc)0, /*sq_ass_slice*/
+};
+
+static PyMappingMethods path_as_mapping = {
+ (lenfunc)path_len, (binaryfunc)path_subscript, NULL};
+
+static PyTypeObject PyPathType = {
+ PyVarObject_HEAD_INIT(NULL, 0) "Path", /*tp_name*/
+ sizeof(PyPathObject), /*tp_basicsize*/
+ 0, /*tp_itemsize*/
+ /* methods */
+ (destructor)path_dealloc, /*tp_dealloc*/
+ 0, /*tp_vectorcall_offset*/
+ 0, /*tp_getattr*/
+ 0, /*tp_setattr*/
+ 0, /*tp_as_async*/
+ 0, /*tp_repr*/
+ 0, /*tp_as_number*/
+ &path_as_sequence, /*tp_as_sequence*/
+ &path_as_mapping, /*tp_as_mapping*/
+ 0, /*tp_hash*/
+ 0, /*tp_call*/
+ 0, /*tp_str*/
+ 0, /*tp_getattro*/
+ 0, /*tp_setattro*/
+ 0, /*tp_as_buffer*/
+ Py_TPFLAGS_DEFAULT, /*tp_flags*/
+ 0, /*tp_doc*/
+ 0, /*tp_traverse*/
+ 0, /*tp_clear*/
+ 0, /*tp_richcompare*/
+ 0, /*tp_weaklistoffset*/
+ 0, /*tp_iter*/
+ 0, /*tp_iternext*/
+ methods, /*tp_methods*/
+ 0, /*tp_members*/
+ getsetters, /*tp_getset*/
+};
diff --git a/contrib/python/Pillow/py3/ya.make b/contrib/python/Pillow/py3/ya.make
new file mode 100644
index 00000000000..199e5efe626
--- /dev/null
+++ b/contrib/python/Pillow/py3/ya.make
@@ -0,0 +1,265 @@
+# Generated by devtools/yamaker from nixpkgs 22.11.
+
+PY3_LIBRARY()
+
+LICENSE(
+ CC-BY-4.0 AND
+ CC0-1.0 AND
+ HPND AND
+ MIT AND
+ Public-Domain
+)
+
+LICENSE_TEXTS(.yandex_meta/licenses.list.txt)
+
+VERSION(10.1.0)
+
+ORIGINAL_SOURCE(mirror://pypi/P/Pillow/Pillow-10.1.0.tar.gz)
+
+PEERDIR(
+ contrib/libs/freetype
+ contrib/libs/lcms2
+ contrib/libs/libjpeg-turbo
+ contrib/libs/libtiff
+ contrib/libs/libwebp
+ contrib/libs/libwebp/demux
+ contrib/libs/libwebp/mux
+ contrib/libs/openjpeg
+ contrib/libs/python
+ contrib/libs/zlib
+ contrib/python/cffi
+ contrib/python/olefile
+)
+
+ADDINCL(
+ contrib/libs/freetype/include
+ contrib/libs/lcms2/include
+ contrib/libs/libjpeg-turbo
+ contrib/libs/libwebp
+ contrib/libs/openjpeg/include
+)
+
+NO_COMPILER_WARNINGS()
+
+NO_LINT()
+
+CFLAGS(
+ -DHAVE_LIBJPEG
+ -DHAVE_LIBTIFF
+ -DHAVE_LIBZ
+ -DHAVE_OPENJPEG
+ -DHAVE_WEBPMUX
+ -DPILLOW_VERSION=\"10.1.0\"
+)
+
+IF (NOT OPENSOURCE)
+ PEERDIR(
+ contrib/libs/libimagequant
+ )
+ ADDINCL(
+ contrib/libs/libimagequant
+ )
+ CFLAGS(
+ -DHAVE_LIBIMAGEQUANT
+ )
+ENDIF()
+
+SRCS(
+ _imaging.c
+ _imagingcms.c
+ _imagingft.c
+ _imagingmath.c
+ _imagingmorph.c
+ _webp.c
+ decode.c
+ display.c
+ encode.c
+ libImaging/Access.c
+ libImaging/AlphaComposite.c
+ libImaging/Bands.c
+ libImaging/BcnDecode.c
+ libImaging/BitDecode.c
+ libImaging/Blend.c
+ libImaging/BoxBlur.c
+ libImaging/Chops.c
+ libImaging/ColorLUT.c
+ libImaging/Convert.c
+ libImaging/ConvertYCbCr.c
+ libImaging/Copy.c
+ libImaging/Crop.c
+ libImaging/Dib.c
+ libImaging/Draw.c
+ libImaging/Effects.c
+ libImaging/EpsEncode.c
+ libImaging/File.c
+ libImaging/Fill.c
+ libImaging/Filter.c
+ libImaging/FliDecode.c
+ libImaging/Geometry.c
+ libImaging/GetBBox.c
+ libImaging/GifDecode.c
+ libImaging/GifEncode.c
+ libImaging/HexDecode.c
+ libImaging/Histo.c
+ libImaging/Jpeg2KDecode.c
+ libImaging/Jpeg2KEncode.c
+ libImaging/JpegDecode.c
+ libImaging/JpegEncode.c
+ libImaging/Matrix.c
+ libImaging/ModeFilter.c
+ libImaging/Negative.c
+ libImaging/Offset.c
+ libImaging/Pack.c
+ libImaging/PackDecode.c
+ libImaging/Palette.c
+ libImaging/Paste.c
+ libImaging/PcdDecode.c
+ libImaging/PcxDecode.c
+ libImaging/PcxEncode.c
+ libImaging/Point.c
+ libImaging/Quant.c
+ libImaging/QuantHash.c
+ libImaging/QuantHeap.c
+ libImaging/QuantOctree.c
+ libImaging/QuantPngQuant.c
+ libImaging/RankFilter.c
+ libImaging/RawDecode.c
+ libImaging/RawEncode.c
+ libImaging/Reduce.c
+ libImaging/Resample.c
+ libImaging/SgiRleDecode.c
+ libImaging/Storage.c
+ libImaging/SunRleDecode.c
+ libImaging/TgaRleDecode.c
+ libImaging/TgaRleEncode.c
+ libImaging/TiffDecode.c
+ libImaging/Unpack.c
+ libImaging/UnpackYCC.c
+ libImaging/UnsharpMask.c
+ libImaging/XbmDecode.c
+ libImaging/XbmEncode.c
+ libImaging/ZipDecode.c
+ libImaging/ZipEncode.c
+ libImaging/codec_fd.c
+ map.c
+ outline.c
+ path.c
+)
+
+PY_REGISTER(
+ PIL._imaging
+ PIL._imagingcms
+ PIL._imagingft
+ PIL._imagingmath
+ PIL._imagingmorph
+ PIL._webp
+)
+
+PY_SRCS(
+ TOP_LEVEL
+ PIL/__init__.py
+ PIL/__main__.py
+ PIL/_binary.py
+ PIL/_deprecate.py
+ PIL/_util.py
+ PIL/_version.py
+ PIL/BdfFontFile.py
+ PIL/BlpImagePlugin.py
+ PIL/BmpImagePlugin.py
+ PIL/BufrStubImagePlugin.py
+ PIL/ContainerIO.py
+ PIL/CurImagePlugin.py
+ PIL/DcxImagePlugin.py
+ PIL/DdsImagePlugin.py
+ PIL/EpsImagePlugin.py
+ PIL/ExifTags.py
+ PIL/features.py
+ PIL/FitsImagePlugin.py
+ PIL/FliImagePlugin.py
+ PIL/FontFile.py
+ PIL/FpxImagePlugin.py
+ PIL/FtexImagePlugin.py
+ PIL/GbrImagePlugin.py
+ PIL/GdImageFile.py
+ PIL/GifImagePlugin.py
+ PIL/GimpGradientFile.py
+ PIL/GimpPaletteFile.py
+ PIL/GribStubImagePlugin.py
+ PIL/Hdf5StubImagePlugin.py
+ PIL/IcnsImagePlugin.py
+ PIL/IcoImagePlugin.py
+ PIL/Image.py
+ PIL/ImageChops.py
+ PIL/ImageCms.py
+ PIL/ImageColor.py
+ PIL/ImageDraw.py
+ PIL/ImageDraw2.py
+ PIL/ImageEnhance.py
+ PIL/ImageFile.py
+ PIL/ImageFilter.py
+ PIL/ImageFont.py
+ PIL/ImageMath.py
+ PIL/ImageMode.py
+ PIL/ImageMorph.py
+ PIL/ImageOps.py
+ PIL/ImagePalette.py
+ PIL/ImagePath.py
+ PIL/ImageSequence.py
+ PIL/ImageShow.py
+ PIL/ImageStat.py
+ PIL/ImageTransform.py
+ PIL/ImageWin.py
+ PIL/ImImagePlugin.py
+ PIL/ImtImagePlugin.py
+ PIL/IptcImagePlugin.py
+ PIL/Jpeg2KImagePlugin.py
+ PIL/JpegImagePlugin.py
+ PIL/JpegPresets.py
+ PIL/McIdasImagePlugin.py
+ PIL/MicImagePlugin.py
+ PIL/MpegImagePlugin.py
+ PIL/MpoImagePlugin.py
+ PIL/MspImagePlugin.py
+ PIL/PaletteFile.py
+ PIL/PalmImagePlugin.py
+ PIL/PcdImagePlugin.py
+ PIL/PcfFontFile.py
+ PIL/PcxImagePlugin.py
+ PIL/PdfImagePlugin.py
+ PIL/PdfParser.py
+ PIL/PixarImagePlugin.py
+ PIL/PngImagePlugin.py
+ PIL/PpmImagePlugin.py
+ PIL/PsdImagePlugin.py
+ PIL/PSDraw.py
+ PIL/PyAccess.py
+ PIL/QoiImagePlugin.py
+ PIL/SgiImagePlugin.py
+ PIL/SpiderImagePlugin.py
+ PIL/SunImagePlugin.py
+ PIL/TarIO.py
+ PIL/TgaImagePlugin.py
+ PIL/TiffImagePlugin.py
+ PIL/TiffTags.py
+ PIL/WalImageFile.py
+ PIL/WebPImagePlugin.py
+ PIL/WmfImagePlugin.py
+ PIL/XbmImagePlugin.py
+ PIL/XpmImagePlugin.py
+ PIL/XVThumbImagePlugin.py
+)
+
+IF (OS_DARWIN OR OS_WINDOWS)
+ PY_SRCS(
+ TOP_LEVEL
+ PIL/ImageGrab.py
+ )
+ENDIF()
+
+RESOURCE_FILES(
+ PREFIX contrib/python/Pillow/py3/
+ .dist-info/METADATA
+ .dist-info/top_level.txt
+)
+
+END()