diff options
| author | shumkovnd <[email protected]> | 2023-11-10 14:39:34 +0300 |
|---|---|---|
| committer | shumkovnd <[email protected]> | 2023-11-10 16:42:24 +0300 |
| commit | 77eb2d3fdcec5c978c64e025ced2764c57c00285 (patch) | |
| tree | c51edb0748ca8d4a08d7c7323312c27ba1a8b79a /contrib/python/Pillow/py3 | |
| parent | dd6d20cadb65582270ac23f4b3b14ae189704b9d (diff) | |
KIKIMR-19287: add task_stats_drawing script
Diffstat (limited to 'contrib/python/Pillow/py3')
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 +# <[email protected]>. +# 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 + <[email protected]>. + 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 +# ([email protected]) +# +# 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, + ×tamp, + &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, ×tamp)) { + 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(¶ms); + 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, ¶ms); + + 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(¶ms); + + 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, ¶ms); + } + + /* 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, ¶ms, 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 + [email protected] */ + +/* 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() |
