Compare commits

...

345 Commits
11.2.1 ... main

Author SHA1 Message Date
Hugo van Kemenade
8ec31431cb
Drop support for PyPy3.10 (#9112) 2025-07-28 22:34:58 +03:00
Andrew Murray
98d38a3bff
Updated libpng to 1.6.50 (#9058) 2025-07-28 18:52:06 +10:00
Andrew Murray
53b6d57b73 Drop support for PyPy3.10 2025-07-26 19:39:54 +10:00
renovate[bot]
7dbcb32cbe
Update cygwin/cygwin-install-action action to v6 (#9108)
Co-authored-by: Andrew Murray <radarhere@users.noreply.github.com>
2025-07-26 19:32:57 +10:00
Luke Granger-Brown
ec6d5efe4d
Deprecate ImageCmsProfile product_name and product_info (#8995)
Co-authored-by: Andrew Murray <radarhere@users.noreply.github.com>
2025-07-26 17:33:11 +10:00
Hugo van Kemenade
640f55a655
Update for pyroma 5.0 (#9093) 2025-07-24 12:25:04 +03:00
Andrew Murray
f4d86e4f44 Use teardown_method 2025-07-24 07:27:39 +10:00
Andrew Murray
a39d14648b Updated manifest 2025-07-16 13:54:43 +10:00
Andrew Murray
a426eb55af Remove file after test completion 2025-07-16 13:54:33 +10:00
Andrew Murray
91bbeb5dcb Revert iOS change until the test runs again 2025-07-16 13:54:13 +10:00
wiredfool
d56032047d
Add parallel compile from pybind11 (#8990) 2025-07-15 18:26:13 +02:00
Hugo van Kemenade
5e26d2fa2c
Improve WmfImagePlugin test coverage (#9090) 2025-07-15 09:16:08 +03:00
renovate[bot]
638eb1b999
Update dependency mypy to v1.17.0 (#9092) 2025-07-15 13:23:40 +10:00
Hugo van Kemenade
71d495add8
Improve DdsImagePlugin test coverage (#9091) 2025-07-14 23:40:29 +03:00
Andrew Murray
7516805121 Improved DDS test coverage 2025-07-14 19:29:27 +10:00
Andrew Murray
d85fa7a247 Improved WmfImagePlugin test coverage 2025-07-13 16:13:44 +10:00
Hugo van Kemenade
d80cf0ee1b
Improve ImageMath test coverage (#9087) 2025-07-11 20:35:36 +03:00
Andrew Murray
a8bb7579dc Improved ImageMath test coverage 2025-07-11 21:06:30 +10:00
Hugo van Kemenade
7b1ba29b5b
Remove unused _save_cjpeg (#9084) 2025-07-11 10:58:56 +03:00
Hugo van Kemenade
3c4fe62c1e
Update libwebp to 1.6.0 (#9082) 2025-07-11 10:46:17 +03:00
Andrew Murray
985544d557 Do not disable libwebpexamples 2025-07-11 13:28:08 +10:00
Andrew Murray
722c130b31
Restored URL
Co-authored-by: Russell Keith-Magee <russell@keith-magee.com>
2025-07-11 13:12:38 +10:00
Andrew Murray
d88986a184
Link transitive dependencies
Co-authored-by: Russell Keith-Magee <russell@keith-magee.com>
2025-07-11 12:53:43 +10:00
Andrew Murray
50dde1c125 Remove unused _save_cjpeg 2025-07-10 23:19:16 +10:00
Andrew Murray
6c12d188db Updated libwebp to 1.6.0 2025-07-10 22:33:31 +10:00
Hugo van Kemenade
d74fdc4b5d
Ensure dynamic libjpeg libraries are not linked (#9081) 2025-07-10 11:06:32 +03:00
Russell Keith-Magee
2af930b2f7
Ensure dynamic libjpeg libraries are not linked. 2025-07-10 12:07:38 +08:00
Hugo van Kemenade
329d6a6a62
Remove reference to libtiff 3.x (#9072) 2025-07-08 20:01:35 +03:00
Hugo van Kemenade
3e5df07b34
Fix unclosed file warning (#9065) 2025-07-08 19:57:09 +03:00
Hugo van Kemenade
d58f4d5f1f
Added "Colors" to concepts (#9067) 2025-07-08 19:56:53 +03:00
Andrew Murray
cbd47d8609 Removed handling of deprecated WebP features 2025-07-08 23:07:07 +10:00
Andrew Murray
c9cf688ee7 Removed ImageDraw.getdraw hints deprecation section 2025-07-08 21:10:26 +10:00
renovate[bot]
2195faf0dc
Update dependency cibuildwheel to v3.0.1 (#9075) 2025-07-08 13:44:13 +10:00
Andrew Murray
06f5cd1dde
Restored manylinux2014 wheels (#9059) 2025-07-08 11:31:03 +10:00
Hugo van Kemenade
27d47b3abf
[pre-commit.ci] pre-commit autoupdate (#9073) 2025-07-07 23:43:15 +03:00
Andrew Murray
e88f312029 Fix unclosed file warning 2025-07-08 06:38:16 +10:00
Andrew Murray
4cfef00574 Added "Colors" to concepts 2025-07-08 06:37:03 +10:00
pre-commit-ci[bot]
14b0cebfc1
[pre-commit.ci] pre-commit autoupdate
updates:
- [github.com/astral-sh/ruff-pre-commit: v0.12.0 → v0.12.2](https://github.com/astral-sh/ruff-pre-commit/compare/v0.12.0...v0.12.2)
- [github.com/PyCQA/bandit: 1.8.5 → 1.8.6](https://github.com/PyCQA/bandit/compare/1.8.5...1.8.6)
- [github.com/pre-commit/mirrors-clang-format: v20.1.6 → v20.1.7](https://github.com/pre-commit/mirrors-clang-format/compare/v20.1.6...v20.1.7)
- [github.com/python-jsonschema/check-jsonschema: 0.33.1 → 0.33.2](https://github.com/python-jsonschema/check-jsonschema/compare/0.33.1...0.33.2)
- [github.com/woodruffw/zizmor-pre-commit: v1.9.0 → v1.11.0](https://github.com/woodruffw/zizmor-pre-commit/compare/v1.9.0...v1.11.0)
2025-07-07 17:16:48 +00:00
Hugo van Kemenade
44a553a0a2
Pyroma now supports PEP 639 (#9064) 2025-07-07 19:12:30 +03:00
Hugo van Kemenade
ef129003d9
Update macOS tested Pillow versions (#9068) 2025-07-07 16:23:04 +03:00
Andrew Murray
756dd04705 Removed reference to libtiff 3.x 2025-07-07 19:09:39 +10:00
Andrew Murray
a84458ffbd Revert "Work around pyroma test"
This reverts commit d8a0cb5db1.
2025-07-07 18:57:58 +10:00
Hugo van Kemenade
dcd202568a
Remove deprecations for Pillow 12.0.0 (#9053) 2025-07-07 11:49:02 +03:00
Andrew Murray
dc9e0cf326
Thanks, folks! (#9056) 2025-07-07 09:48:39 +10:00
Andrew Murray
1ee91f22ba Updated macOS tested Pillow versions 2025-07-05 22:51:02 +10:00
Jeffrey A. Clark
77f3a091b8
Setup nit: "fork" should be lowercased (#9055) 2025-07-01 10:54:12 -04:00
Andrew Murray
f2417d8b39 Added release notes 2025-07-02 00:00:21 +10:00
Andrew Murray
0e3aac1ed1 Updated deprecation timeline 2025-07-02 00:00:21 +10:00
Andrew Murray
92bafe6b88 Removed support for FreeType <= 2.9.0 2025-07-02 00:00:21 +10:00
Andrew Murray
aaf217cea0 Removed ICNS (width, height, scale) sizes 2025-07-02 00:00:21 +10:00
Andrew Murray
9fbc255ce5 Removed non-image modes in ImageCms 2025-07-02 00:00:21 +10:00
Andrew Murray
b4bc43fed2 Removed ImageCms constants and versions() 2025-07-02 00:00:21 +10:00
Andrew Murray
4301c1fde6 Removed ImageMath eval and options parameters 2025-07-02 00:00:21 +10:00
Andrew Murray
0a29d6392a Removed IptcImageFile helper functions 2025-07-02 00:00:21 +10:00
Andrew Murray
9c9449af34 Removed support for LibTIFF < 4 2025-07-02 00:00:16 +10:00
Andrew Murray
a7e00fba8b Removed ImageDraw.getdraw hints parameter 2025-07-01 23:57:50 +10:00
Andrew Murray
88018c1c2d Removed id and unsafe_ptrs 2025-07-01 23:57:50 +10:00
Andrew Murray
cce39084f5 Removed specific WebP feature checks 2025-07-01 23:57:50 +10:00
Andrew Murray
b72b8dd84d Removed JpegImageFile.huffman_ac and JpegImageFile.huffman_dc 2025-07-01 23:57:50 +10:00
Andrew Murray
1800e580d2 Removed ImageFile raise_oserror() 2025-07-01 23:57:50 +10:00
Andrew Murray
5d4a05465d Removed Image isImageType() 2025-07-01 23:57:50 +10:00
Andrew Murray
583f0a50d5 Removed BGR;15, BGR;16 and BGR;24 modes 2025-07-01 23:57:46 +10:00
Jeffrey A. Clark
d4ef93150f Thanks, folks!
As a general rule I think we should acknowledge when significant
contribtions come from outside the core team. We know the core team
does a lot of work (thank you!) but it's not always obvious when
significant contributions come from outside the core team.

In the old change log, we had ACKs via `[radarhere]` syntax which I
miss. I don't expect we'll start using the old change log again but
maybe we can make a note in the release notes to include such ACKs as
needed and appropriate.
2025-07-01 09:25:32 -04:00
Jeffrey A. Clark
0cd2d3b24b Setup nit: "fork" should be lowercased 2025-07-01 09:10:20 -04:00
Andrew Murray
37cd041e5e 12.0.0.dev0 version bump 2025-07-01 19:25:23 +10:00
Andrew Murray
89f1f4626a 11.3.0 version bump 2025-07-01 17:41:24 +10:00
Andrew Murray
f2de251c76
Updated check script paths (#9052) 2025-07-01 15:17:56 +10:00
Hugo van Kemenade
84855d11c8
Raise FileNotFoundError when opening an empty path (#9048) 2025-06-30 17:48:44 +03:00
Andrew Murray
204d11d4da Raise FileNotFoundError when opening an empty path 2025-06-30 22:29:41 +10:00
Hugo van Kemenade
2b39f7581e
Handle IPTC TIFF tags with incorrect type (#8925) 2025-06-30 15:25:19 +03:00
Hugo van Kemenade
e7a53ba19b
Do not update palette for L mode GIF frame (#8924) 2025-06-30 15:24:18 +03:00
Hugo van Kemenade
c22230b761
Use save parameters as encoderinfo defaults (#9001) 2025-06-30 15:13:12 +03:00
Russell Keith-Magee
da10ed1cf3
Add support for iOS (#9030)
Co-authored-by: Andrew Murray <radarhere@users.noreply.github.com>
2025-06-30 21:46:07 +10:00
Kylian Ronfleux--Corail
be2b4e7864
Fix qtables and quality scaling (#8879)
Co-authored-by: Andrew Murray <radarhere@users.noreply.github.com>
2025-06-30 20:46:40 +10:00
Andrew Murray
d4162f8505 Updated return type 2025-06-30 18:27:49 +10:00
Hugo van Kemenade
a2fbd58f4b
Read 16-bit McIdas images into I;16B mode to allow for memory mapping (#9046) 2025-06-29 15:38:26 +03:00
Hugo van Kemenade
144890255f
Support ttb multiline text (#8730) 2025-06-28 14:35:13 +03:00
Hugo van Kemenade
c084bd7d95
Use unpacking (#9044) 2025-06-28 14:33:10 +03:00
Hugo van Kemenade
d263e3ba13
Fix saving MPO with more than one appended image (#8979) 2025-06-28 14:32:33 +03:00
Andrew Murray
4ac2403532 Read 16-bit images into I;16B mode to allow for memory mapping 2025-06-28 15:48:44 +10:00
Andrew Murray
26ae44e059 Merge branch 'main' into ttb 2025-06-28 13:49:43 +10:00
Andrew Murray
ed82f4d235 Use unpacking 2025-06-28 10:57:23 +10:00
Andrew Murray
5732a86cc6
Use snake case
Co-authored-by: Hugo van Kemenade <1324225+hugovk@users.noreply.github.com>
2025-06-28 10:52:25 +10:00
Andrew Murray
a370209fea
Add match parameter to pytest.warns() (#9038) 2025-06-28 10:29:34 +10:00
Andrew Murray
69c0c422c8
Increase pytest verbosity (#9040)
Co-authored-by: Hugo van Kemenade <1324225+hugovk@users.noreply.github.com>
2025-06-28 10:29:01 +10:00
Andrew Murray
a0a1ff14c4 Merge branch 'main' into encoderinfo_frames 2025-06-28 01:35:59 +10:00
Andrew Murray
646b4a4ecd
Merge branch 'main' into mpo 2025-06-28 01:20:15 +10:00
Andrew Murray
41129ce1cb
Use list
Co-authored-by: Hugo van Kemenade <1324225+hugovk@users.noreply.github.com>
2025-06-28 01:20:02 +10:00
Hugo van Kemenade
3a66b1d291
Restore original encoderinfo after saving (#8942) 2025-06-27 18:04:43 +03:00
Hugo van Kemenade
935e9e4fbd
Return PixelAccess from first load of ICO and IPTC images (#8922) 2025-06-27 18:02:48 +03:00
Hugo van Kemenade
c96b27711c
Improve justifying text (#8905) 2025-06-27 18:01:25 +03:00
Hugo van Kemenade
ba37249ab7
Set color table fourth channel to zero for 1 and L mode when saving BMP (#8889) 2025-06-27 18:00:06 +03:00
Hugo van Kemenade
3a18e555f0
Assert palette is not None (#8877) 2025-06-27 17:53:55 +03:00
Hugo van Kemenade
3d21c16977
Improve reading XPM images (#8874) 2025-06-27 17:53:34 +03:00
Andrew Murray
d07aa6fd17
Added release notes for #9041 (#9042)
Co-authored-by: Hugo van Kemenade <1324225+hugovk@users.noreply.github.com>
2025-06-28 00:30:22 +10:00
Andrew Murray
ef98b3510e
Fix buffer overflow when saving compressed DDS images (#9041)
Co-authored-by: Eric Soroos <eric-github@soroos.net>
2025-06-28 00:29:58 +10:00
Hugo van Kemenade
958c449b98
Close image after assert
Co-authored-by: Andrew Murray <3112309+radarhere@users.noreply.github.com>
2025-06-27 16:17:20 +03:00
Andrew Murray
e783aff688
Improve SgiImagePlugin test coverage (#8896) 2025-06-27 15:32:30 +03:00
Hugo van Kemenade
6aff8fcc18
Fix PT031 (#135) 2025-06-27 14:30:41 +03:00
Andrew Murray
a61a23d7ae Fixed PT031 2025-06-27 13:00:48 +10:00
Andrew Murray
092e37a56c
Merge branch 'main' into bump-pre-commit 2025-06-27 11:04:44 +10:00
Hugo van Kemenade
1788ab7887
Do not import type checking (#8854) 2025-06-26 18:52:09 +03:00
Hugo van Kemenade
cb061f7269
Update ruff pre-commit ID (#8994) 2025-06-26 18:50:17 +03:00
Hugo van Kemenade
a27731b2af
Improve type hints (#8883) 2025-06-26 18:49:39 +03:00
Hugo van Kemenade
d1894dcd46 Add match parameter to pytest.warns() 2025-06-26 18:12:36 +03:00
Hugo van Kemenade
234875bf90 Update Ruff hook from legacy 2025-06-26 17:56:26 +03:00
Hugo van Kemenade
b9afe18646 Bump pre-commit hooks 2025-06-26 17:56:26 +03:00
Frankie Dintino
3d261a2101
Add AVIF to wheels using only aomenc and dav1d AVIF codecs for reduced size (#8858)
Co-authored-by: Andrew Murray <radarhere@users.noreply.github.com>
Co-authored-by: Hugo van Kemenade <1324225+hugovk@users.noreply.github.com>
2025-06-26 16:21:44 +10:00
Andrew Murray
23ed906b62 Removed default limit of 4 2025-06-25 22:00:36 +10:00
Andrew Murray
ecd264fffc Use "parallel" config setting and 4 as defaults 2025-06-25 21:43:03 +10:00
Hugo van Kemenade
46d969168e
Use PEP 489 multi-phase initialization (#8983) 2025-06-25 11:32:37 +01:00
Andrew Murray
a549c5528a
Merge branch 'main' into pybind11 2025-06-25 20:31:48 +10:00
Hugo van Kemenade
1e5eb3b29d
Support saving I;16L TIFF images (#9015) 2025-06-25 11:11:47 +01:00
Hugo van Kemenade
8655b7c559
Only check DHT marker for libjpeg-turbo (#9025) 2025-06-25 11:09:55 +01:00
Hugo van Kemenade
8cf8b0dde0
Do not call sys.executable in ImageShow in PyInstaller application (#9028) 2025-06-25 10:46:46 +01:00
Hugo van Kemenade
c704f43288
Deprecate fromarray mode argument (#9018) 2025-06-25 10:44:39 +01:00
Hugo van Kemenade
0450f99596
Search for libtiff library file first on Windows and macOS (#9034) 2025-06-25 10:42:00 +01:00
Andrew Murray
e1ee8afc7d Search for libtiff library file first on Windows and macOS 2025-06-25 10:42:09 +10:00
Andrew Murray
acd8b0c2ac
Fix libtiff cleanup (#9002) 2025-06-25 09:09:31 +10:00
Hugo van Kemenade
129267bc82
Use percent formatting for _dbg calls (#9035) 2025-06-24 18:37:07 +01:00
Andrew Murray
18f8af78d3 Pass strings or tuples of strings to _dbg 2025-06-24 20:35:09 +10:00
Andrew Murray
1557585411 Use percent formatting 2025-06-24 20:29:38 +10:00
Andrew Murray
2954964cd2
Removed ImageCmsProfile._set method (#9032)
Co-authored-by: Luke Granger-Brown <git@lukegb.com>
2025-06-23 07:05:43 +10:00
Andrew Murray
ae02518314
Use same AVIF URL when fetching dependency (#8871) 2025-06-22 22:08:51 +10:00
Hugo van Kemenade
f8d53fb8e6
Added Python 3.14 macOS x86-64 wheels (#9031) 2025-06-22 09:36:57 +01:00
Andrew Murray
78bc045db9
Merge branch 'main' into fromarray_mode 2025-06-21 21:03:37 +10:00
Hugo van Kemenade
13faa4681c
Deprecate saving I mode images as PNG (#9023) 2025-06-21 11:24:28 +01:00
Andrew Murray
216dc4ca60 Added Python 3.14 macOS x86-64 wheels 2025-06-21 19:12:23 +10:00
Andrew Murray
f937dd27cd Do not call sys.executable in PyInstaller application 2025-06-20 23:44:30 +10:00
Andrew Murray
2316c930f9 Removed default argument 2025-06-19 22:46:09 +10:00
thisismypassport
ef0bab0c65
Support writing QOI images (#9007)
Co-authored-by: Andrew Murray <radarhere@users.noreply.github.com>
2025-06-19 18:16:26 +10:00
renovate[bot]
92de1db067
Update dependency mypy to v1.16.1 (#9026) 2025-06-19 11:12:40 +10:00
Andrew Murray
79e0b0b6ad Allow for custom stacklevel in deprecations 2025-06-18 22:19:20 +10:00
Andrew Murray
a4e8d675b4 Only check DHT marker for libjpeg-turbo 2025-06-18 21:59:31 +10:00
Andrew Murray
d23d56e195 Deprecate saving I mode images as PNG 2025-06-17 23:10:15 +10:00
Hugo van Kemenade
4d0ebb040a
Add release notes for #8912 and #8969 (#9019) 2025-06-16 13:25:43 +03:00
Andrew Murray
7b5e11deb7 Updated heading 2025-06-16 20:06:53 +10:00
Andrew Murray
c19afb9430
Use names
Co-authored-by: Hugo van Kemenade <1324225+hugovk@users.noreply.github.com>
2025-06-16 20:05:34 +10:00
Hugo van Kemenade
ef683e9d78
Simplify C error handling (#9021) 2025-06-16 13:01:46 +03:00
Hugo van Kemenade
01267f9dac
Improve BLP tests (#9020) 2025-06-16 13:00:30 +03:00
Hugo van Kemenade
850271a912
Fix warning (#9016) 2025-06-16 12:57:45 +03:00
Andrew Murray
8309962926 Replaced ImagingError_OSError with PyErr_SetString 2025-06-16 08:19:27 +10:00
Andrew Murray
cb433ad00a Replaced ImagingError_Clear with PyErr_Clear 2025-06-16 08:15:08 +10:00
Andrew Murray
ce8083e0d8 Match error message 2025-06-14 18:40:03 +10:00
Andrew Murray
59667bbec5 Use *_tofile helpers 2025-06-14 18:39:30 +10:00
Hugo van Kemenade
222b86222e
Update libpng to 1.6.49 (#9014) 2025-06-14 10:24:58 +03:00
Andrew Murray
3ac1edf6da Added release notes for #8912 2025-06-14 17:13:02 +10:00
Andrew Murray
27ce12bb7a Added release notes for #8969 2025-06-14 16:44:42 +10:00
Andrew Murray
e6af31e709 Deprecate fromarray mode argument 2025-06-14 16:09:11 +10:00
Andrew Murray
5aa09cd107 Updated libpng to 1.6.49 2025-06-14 12:23:01 +10:00
Andrew Murray
925fe51904 Support saving I;16L images 2025-06-14 12:22:48 +10:00
Andrew Murray
4ba97d1327 Removed entries for non-existent modes 2025-06-14 12:22:48 +10:00
Andrew Murray
a219e96fd3 Fixed warning 2025-06-14 12:22:29 +10:00
Hugo van Kemenade
a3d91cb0ce
CI: Require Python >= 3.13.5 on Windows (#9017) 2025-06-14 12:21:31 +10:00
Andrew Murray
2e5117305b
Add Python 3.14 beta wheels (#9012) 2025-06-13 18:59:32 +10:00
Hugo van Kemenade
3841db0252 Fix: Invalid skip selector: 'pp39-*' 2025-06-13 00:08:52 +03:00
Hugo van Kemenade
aca0e57126 Add 3.14 to CI targets 2025-06-12 23:47:28 +03:00
Hugo van Kemenade
4a1eea8466 Add Python 3.14 beta wheels 2025-06-12 23:47:28 +03:00
Hugo van Kemenade
a76dca9c45
Test Python 3.14t on macOS and Linux (#9011) 2025-06-12 19:26:11 +03:00
Hugo van Kemenade
5996dbdc3a
Update dependency cibuildwheel to v3 (#9010) 2025-06-12 19:13:45 +03:00
Andrew Murray
9bffc015e6 Use pypy.exe if it exists 2025-06-12 23:52:51 +10:00
Andrew Murray
b9aac77003 Test Python 3.14t 2025-06-12 22:48:27 +10:00
Andrew Murray
d2295c0843 Do not activate virtualenv 2025-06-12 18:53:35 +10:00
renovate[bot]
b65a7acf25
Update dependency cibuildwheel to v3 2025-06-11 13:20:34 +00:00
Andrew Murray
8ccdc399df
Remove padding between interleaved PCX palette data (#9005) 2025-06-11 16:19:09 +03:00
Andrew Murray
7f7c27f66a
Start QOI decoding with a zero-initialized array of previously seen pixels (#9008) 2025-06-11 22:56:57 +10:00
Andrew Murray
3eb893f0c1
Updated libjpeg-turbo to 3.1.1 (#9009) 2025-06-11 13:56:28 +03:00
Andrew Murray
056dc89a3c
Correct drawing I;16 horizontal lines (#8985) 2025-06-10 15:12:40 +03:00
Hugo van Kemenade
ff624fe1e6
Reduce number of bytes read for PCX header (#9004) 2025-06-10 15:01:02 +03:00
Andrew Murray
d7a45cc250
ImageFont does not handle multiline text (#9000) 2025-06-10 14:57:37 +03:00
Hugo van Kemenade
de053fbae0
Handle XMP data from an UNDEFINED TIFF tag (#8997) 2025-06-10 14:57:15 +03:00
Andrew Murray
36cea19532
Do not decode bytes in PPM error message (#8958) 2025-06-10 14:08:29 +03:00
Andrew Murray
646885e546
Parse XMP tag bytes without decoding to string (#8960)
Co-authored-by: Andrew Murray <radarhere@users.noreply.github.com>
2025-06-10 14:06:28 +03:00
Andrew Murray
e65e5bea45 Start decoding with a zero-initialized array of previously seen pixels 2025-06-10 20:30:18 +10:00
Andrew Murray
b844007cdc
Clear TIFF core image if memory mapping was used for last load (#8962) 2025-06-10 17:03:31 +10:00
Andrew Murray
6bd55684e0
Only accept missing tkinter when building wheels on Windows (#8981) 2025-06-10 09:00:08 +03:00
Andrew Murray
7b163cc35d
Use mask in C when drawing wide polygon lines (#8984) 2025-06-10 11:46:12 +10:00
Hugo van Kemenade
05636dca17
Simplify code (#8863) 2025-06-09 19:33:55 +03:00
Andrew Murray
7341e70f6b Reduced number of bytes read for header 2025-06-09 12:21:53 +10:00
Andrew Murray
313969cf0b Removed unnecessary seek 2025-06-09 12:21:49 +10:00
Andrew Murray
ef1f90fe1c
Check for equality rather than inequality
Co-authored-by: Hugo van Kemenade <1324225+hugovk@users.noreply.github.com>
2025-06-09 09:06:08 +10:00
Hugo van Kemenade
8e5a15bab7
Call startswith once with a tuple (#8998) 2025-06-08 19:19:53 +02:00
Andrew Murray
0bb99e5561 Use save parameters as encoderinfo defaults 2025-06-07 15:08:16 +10:00
Andrew Murray
04c984f2f2 Removed duplicate code 2025-06-07 11:29:11 +10:00
Andrew Murray
89c38258dc Assert getcolors() does not return None 2025-06-07 11:13:30 +10:00
Andrew Murray
a3da70e76e Assert load() does not return None 2025-06-07 11:13:30 +10:00
Andrew Murray
cba096b4a9 Assert pixel data is tuple 2025-06-07 11:13:12 +10:00
Andrew Murray
33460d2f82 Assert _getmp() does not return None 2025-06-07 11:09:38 +10:00
Andrew Murray
0d1edba311 Assert tile args is tuple 2025-06-07 11:09:38 +10:00
renovate[bot]
f3b05d6fab
Update dependency mypy to v1.16.0 (#8991)
Co-authored-by: Andrew Murray <radarhere@users.noreply.github.com>
2025-06-07 11:07:21 +10:00
Andrew Murray
9d5ea827e4 Call startswith once with a tuple 2025-06-05 18:16:05 +10:00
Andrew Murray
f03c23683e Trim whitespace from end when parsing XMP data 2025-06-04 20:08:58 +10:00
Andrew Murray
cb077a16c8 Handle UNDEFINED XMP data 2025-06-04 20:07:13 +10:00
Hugo van Kemenade
1bf32ae892
Fix test (#8996) 2025-06-04 09:06:09 +02:00
Andrew Murray
eb0256acc0 Fixed test 2025-06-03 22:44:26 +10:00
Andrew Murray
fa7413904b Updated ruff ID 2025-06-03 14:13:22 +10:00
pre-commit-ci[bot]
070e1eba62
[pre-commit.ci] pre-commit autoupdate (#8993) 2025-06-03 14:08:24 +10:00
Andrew Murray
95603e9717
Use ImageFile.MAXBLOCK in tobytes() (#8906) 2025-06-02 20:14:11 +10:00
Andrew Murray
892fd2c2af
Removed unreachable code (#8918) 2025-06-01 15:41:48 +10:00
Eric Soroos
b931402046 add pybind11 elsewhere so mypy can find it 2025-05-31 15:14:17 +02:00
wiredfool
2059e06005 Add parallel compile from pybind11 2025-05-31 14:46:07 +02:00
Hugo van Kemenade
d730e60078
Update Ubuntu CI targets (#8988) 2025-05-31 10:52:59 +02:00
Hugo van Kemenade
598066d9e1
Stop testing deprecated Windows Server 2019 runner image (#8989) 2025-05-31 07:12:50 +02:00
Andrew Murray
9327e425ba Stop testing deprecated Windows Server 2019 2025-05-31 12:02:16 +10:00
Andrew Murray
bc4138f169 ubuntu-latest now uses Ubuntu 24.04 2025-05-31 11:48:49 +10:00
仓鼠
3944db288a
Update MinGW package names (#8987) 2025-05-31 11:10:45 +10:00
wiredfool
256f6ea1c1
Valgrind Memory Leak Checking (#8954) 2025-05-30 14:28:40 +01:00
wiredfool
f34b4a1806
Add parallel test target, using pytest-xdist (#8972) 2025-05-30 13:28:09 +01:00
wiredfool
0ba69613c9
Add support for flat uint8 arrow arrays for multi channel images (#8908) 2025-05-30 13:11:09 +01:00
wiredfool
22d6265063
Updated docstring (#8943) 2025-05-30 13:06:47 +01:00
wiredfool
e16f387bdf
Mention that tobytes() with the raw encoder uses Pack.c (#8878) 2025-05-30 13:01:19 +01:00
wiredfool
506691729a
Apply suggestions from code review
Co-authored-by: Andrew Murray <3112309+radarhere@users.noreply.github.com>
2025-05-30 10:40:35 +01:00
wiredfool
399b6c1045
Update Makefile
Co-authored-by: Andrew Murray <3112309+radarhere@users.noreply.github.com>
2025-05-30 10:40:07 +01:00
wiredfool
98cf15e9e4
Update depends/docker-test-valgrind-memory.sh
Co-authored-by: Andrew Murray <3112309+radarhere@users.noreply.github.com>
2025-05-30 10:35:13 +01:00
wiredfool
6a60b2e6dd Remove Tests/ path arg, this is already configured 2025-05-30 10:27:11 +01:00
Andrew Murray
62da23bf83 Removed polygon from DRAW struct 2025-05-29 18:27:22 +10:00
Andrew Murray
fcac6e7896 Removed hasAlpha argument 2025-05-29 18:27:17 +10:00
Andrew Murray
2ee2a1496d Simplified code 2025-05-29 18:26:09 +10:00
Andrew Murray
5000c83bcc Use multi-phase initialization 2025-05-28 23:50:18 +10:00
Andrew Murray
5b854b2332 Merge branch 'main' into valgrind-leakcheck 2025-05-28 08:27:57 +10:00
Andrew Murray
5a04b9581b
Run slow tests on valgrind, but without timeout (#8975) 2025-05-28 08:20:35 +10:00
Hugo van Kemenade
06e618c470
Removed CMAKE_POLICY_VERSION_MINIMUM=3.5 for libavif (#8973) 2025-05-27 17:18:20 +03:00
Hugo van Kemenade
90f115cd33
Reduced number of bytes read in WMF header (#8964) 2025-05-27 17:17:15 +03:00
Andrew Murray
bcc6e42bf8 Fixed saving MPO with more than one appended image 2025-05-27 21:08:58 +10:00
Andrew Murray
0eef5e20ef
Merge pull request #29 from wiredfool/tiff_mmap
Mark the image read-only in the C layer if it's created from a read only buffer
2025-05-27 09:12:43 +10:00
wiredfool
eff667a861 Mark the image read-only in the C layer if it's created from a read only buffer 2025-05-26 08:15:13 +10:00
Hugo van Kemenade
086e05f42f
Do not build against libavif < 1 (#8969) 2025-05-25 23:29:21 +03:00
Hugo van Kemenade
bce93319a9
Updated libpng to 1.6.48 (#8940) 2025-05-25 14:14:56 +03:00
Andrew Murray
041acf1344 Clear core image if memory mapping was used for last load 2025-05-25 15:00:47 +10:00
Andrew Murray
6096f335c1
Merge branch 'main' into valgrind-leakcheck 2025-05-24 15:41:48 +10:00
Andrew Murray
bcf1f85b30
Merge branch 'main' into parallel-test 2025-05-24 12:03:59 +10:00
Andrew Murray
57b77bde96 Removed CMAKE_POLICY_VERSION_MINIMUM=3.5 2025-05-24 11:55:18 +10:00
Andrew Murray
a4f477565a
Merge branch 'main' into fix_arrow_8907 2025-05-24 10:34:05 +10:00
Andrew Murray
5c2cabfa6c
Merge branch 'main' into avif 2025-05-24 10:30:29 +10:00
Andrew Murray
4eb89f8e5b Reduced number of bytes read for header 2025-05-24 10:24:42 +10:00
Andrew Murray
e018dc99fa Updated libpng to 1.6.48 2025-05-24 08:51:51 +10:00
Hugo van Kemenade
7e4d8e2f55
Updated Ghostscript to 10.5.1 (#8939) 2025-05-23 19:17:35 +03:00
Hugo van Kemenade
038ca7f3cc
Updated harfbuzz to 11.2.1 (#8937) 2025-05-23 19:17:23 +03:00
Hugo van Kemenade
97aa25ac6b
Updated libavif to 1.3.0 (#8949) 2025-05-23 19:17:07 +03:00
Eric Soroos
4d0678ca33 Add parallel test target, using pytest-xdist 2025-05-23 16:35:57 +02:00
pre-commit-ci[bot]
c63db77db3 [pre-commit.ci] auto fixes from pre-commit.com hooks
for more information, see https://pre-commit.ci
2025-05-23 13:37:05 +00:00
Eric Soroos
60a1a20536 add timeouts to two more tests 2025-05-23 15:35:23 +02:00
wiredfool
edfc2caf62 Merge radarhere/fix_arrow_8907
* edit of elt typ
2025-05-23 11:04:55 +01:00
Andrew Murray
6807bd3d70 Added type hints 2025-05-23 11:04:08 +01:00
wiredfool
9526d949b0
Update Tests/test_pyarrow.py
Co-authored-by: Andrew Murray <3112309+radarhere@users.noreply.github.com>
2025-05-23 10:58:28 +01:00
wiredfool
2603a249df
Update depends/docker-test-valgrind-memory.sh
Co-authored-by: Andrew Murray <3112309+radarhere@users.noreply.github.com>
2025-05-23 10:57:03 +01:00
Andrew Murray
7824d2f8c6 Update rust when building libavif 2025-05-23 08:48:38 +10:00
Andrew Murray
45d1c4162b Do not build against libavif < 1 2025-05-22 15:55:43 +10:00
pre-commit-ci[bot]
c35082b619 [pre-commit.ci] auto fixes from pre-commit.com hooks
for more information, see https://pre-commit.ci
2025-05-17 08:48:02 +00:00
Eric Soroos
20b49a332b Remove timeout as the specific reason,
pytest-timeout doesn't raise a timeout error.
2025-05-17 10:45:43 +02:00
Eric Soroos
ff50e30d3e Fix memory leak in text_layout_raqm on 0 length string 2025-05-16 12:47:22 +02:00
Eric Soroos
6391f2c207 Merge remote-tracking branch 'upstream/main' into valgrind-leakcheck
* Some failing tests are on main but not last released version
2025-05-16 12:14:37 +02:00
Eric Soroos
f1957b49b2 Xfail timouts in Valgrind tests
* ensure that the env variable is set in the makefile
2025-05-16 12:08:45 +02:00
Eric Soroos
2d506f6f5a correct target 2025-05-15 22:06:35 +02:00
Eric Soroos
a6b8b3af77 executable 2025-05-15 22:04:14 +02:00
Eric Soroos
218f055865 Add github workflow/test-script 2025-05-15 21:59:02 +02:00
Eric Soroos
d5449d5760 Guess so. 2025-05-15 21:11:31 +02:00
Eric Soroos
fb126af7a6 Adding pytest-valgrind install 2025-05-15 21:10:48 +02:00
Andrew Murray
efa2288643 Updated libavif to 1.3.0 2025-05-15 08:38:33 +10:00
Eric Soroos
7aa6a61d43 Wrap Makefile 2025-05-13 23:50:52 +02:00
Eric Soroos
789631c60c Fix memory leak when JpegEncode returns an error. 2025-05-13 23:31:09 +02:00
Eric Soroos
f792e0b1ef Fix memory leak
* Return after setting the error for advanced features without
  libraqm. Not returning here leads to an alloc that's never freed.
2025-05-13 22:48:36 +02:00
Eric Soroos
e2e40c5456 Fix memory leak in TiffEncode
* If setimage errors out, the tiff client state was not freed.
2025-05-13 22:33:27 +02:00
Eric Soroos
a9bcd7db88 Fix leak of destination image in ImagingUnsharpMask when an error occurs 2025-05-13 19:50:55 +02:00
Eric Soroos
eaab435403 Fix leak in webp_encode
* Free the output buffer on webp encode error
2025-05-13 10:58:37 +02:00
Eric Soroos
84b88a9fbc Suppress all python level leaks for now 2025-05-13 10:58:12 +02:00
Eric Soroos
fdfba982c8 fix memory leak in arrow schema 2025-05-13 10:28:09 +02:00
Eric Soroos
4984c45da2 valgrind memory leak check 2025-05-13 10:27:38 +02:00
Andrew Murray
c64a7b5098 Updated harfbuzz to 11.2.1 2025-05-13 07:41:00 +10:00
Eric Soroos
74ab5ac4cd Fix memory leak in arrow export using array structure 2025-05-12 00:27:56 +02:00
Andrew Murray
78887f6114 Corrected comment 2025-05-09 23:52:18 +10:00
Hugo van Kemenade
3c71559804
Improve support for Python 3.14 (#8948) 2025-05-08 19:43:29 +03:00
Andrew Murray
215069af5d Added support for Python 3.14 2025-05-08 22:39:31 +10:00
Andrew Murray
71a916ad53 Do not install PyQt6 on Python 3.14 2025-05-08 22:13:49 +10:00
Andrew Murray
c7193f74fc Updated error message 2025-05-08 20:10:34 +10:00
pre-commit-ci[bot]
d02f786873
[pre-commit.ci] pre-commit autoupdate (#8944)
Co-authored-by: Hugo van Kemenade <1324225+hugovk@users.noreply.github.com>
2025-05-08 19:16:40 +10:00
Andrew Murray
4d56b90f38 Updated docstring 2025-05-05 07:12:20 +10:00
Andrew Murray
0e292a80c8 Restore original encoderinfo after saving 2025-05-03 00:52:35 +10:00
Andrew Murray
2245fd09de Updated Ghostscript to 10.5.1 2025-04-30 07:54:07 +10:00
Hugo van Kemenade
07df26aa5d
Refactor docs Makefile (#8933)
Co-authored-by: Andrew Murray <radarhere@users.noreply.github.com>
2025-04-29 22:37:45 +10:00
Hugo van Kemenade
e23f017af1
Allow loading ImageFile state from Pillow < 11.2.1 (#8938) 2025-04-29 15:05:10 +03:00
Andrew Murray
47bebfc801 Allow loading state from Pillow < 11.2.1 2025-04-29 14:57:10 +10:00
Jeffrey A. Clark
c2f1b981b7
Add template for quarterly release issue (#8932) 2025-04-28 11:52:00 -04:00
Andrew Murray
dbe538a130 Updated template name 2025-04-28 06:19:18 +10:00
Andrew Murray
f1d5cdaa07 Use sentence case 2025-04-28 06:17:47 +10:00
Jeffrey A. Clark
e140027262 Move checklist to issue template 2025-04-27 15:45:40 -04:00
Jeffrey A. Clark
e6ff42303b
Update .github/ISSUE_TEMPLATE/RELEASE.md
Co-authored-by: Hugo van Kemenade <1324225+hugovk@users.noreply.github.com>
2025-04-27 15:38:02 -04:00
Jeffrey A. Clark
1eba198b62
Update .github/ISSUE_TEMPLATE/RELEASE.md
Co-authored-by: Hugo van Kemenade <1324225+hugovk@users.noreply.github.com>
2025-04-27 15:37:56 -04:00
Jeffrey A. Clark
fcaffa2229
Update .github/ISSUE_TEMPLATE/RELEASE.md
Co-authored-by: Hugo van Kemenade <1324225+hugovk@users.noreply.github.com>
2025-04-27 15:37:50 -04:00
Jeffrey A. Clark
6881863eab
Update .github/ISSUE_TEMPLATE/RELEASE.md
Co-authored-by: Hugo van Kemenade <1324225+hugovk@users.noreply.github.com>
2025-04-27 15:37:42 -04:00
Hugo van Kemenade
6a63907341
Remove outdated comment (#8929) 2025-04-27 18:16:34 +03:00
Andrew Murray
6f672191ad
Branch uses .x 2025-04-27 22:30:35 +10:00
Jeffrey A. Clark
0205fb4fa2
Update .github/ISSUE_TEMPLATE/RELEASE.md
Co-authored-by: Andrew Murray <3112309+radarhere@users.noreply.github.com>
2025-04-27 08:21:57 -04:00
Jeffrey A. Clark
8ab3bc469e
Update .github/ISSUE_TEMPLATE/RELEASE.md
Co-authored-by: Andrew Murray <3112309+radarhere@users.noreply.github.com>
2025-04-27 08:21:48 -04:00
renovate[bot]
da9d5522f7
Update dependency cibuildwheel to v2.23.3 (#8931)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-04-27 21:35:08 +10:00
Jeffrey A. Clark
4c2227758e Add template for quarterly release issue 2025-04-27 07:09:53 -04:00
Andrew Murray
225182414c libavif below 1.0 is not supported 2025-04-25 17:14:13 +10:00
Andrew Murray
3bd55822cd Handle IPTC TIFF tags with incorrect type 2025-04-24 13:26:58 +10:00
Andrew Murray
d8afcb762f Do not update palette for L mode frame 2025-04-23 23:09:08 +10:00
Andrew Murray
1e365d8c72 Return PixelAccess on first load 2025-04-23 21:10:54 +10:00
Andrew Murray
7a48a9fae0 Do not load image more than once 2025-04-23 20:34:53 +10:00
wiredfool
45e24e429f Rearrance so black doesn't screw up the formatting 2025-04-21 10:54:00 +01:00
wiredfool
bc4b664b70 Add integer range tests 2025-04-21 10:46:45 +01:00
wiredfool
ce204f47f4 lint 2025-04-21 10:37:32 +01:00
wiredfool
6bf791a3e7 Use a named tuple for the packed parameters 2025-04-21 10:27:49 +01:00
Andrew Murray
58e48745cc
Add list of third-party plugins (#8910)
Co-authored-by: Hugo van Kemenade <1324225+hugovk@users.noreply.github.com>
2025-04-21 12:14:08 +03:00
Hugo van Kemenade
8f123cd692
Update redirected URL (#8919) 2025-04-21 12:13:25 +03:00
Hugo van Kemenade
348589a367
Docs: use sentence case for headers (#8914)
Co-authored-by: Andrew Murray <3112309+radarhere@users.noreply.github.com>
2025-04-21 19:03:31 +10:00
Hugo van Kemenade
d03ce3d235
Docs: remove unused Makefile targets (#8917)
Co-authored-by: Andrew Murray <3112309+radarhere@users.noreply.github.com>
2025-04-21 18:22:03 +10:00
Andrew Murray
8fe7a7aaf8 Update redirected URL 2025-04-21 17:32:47 +10:00
Adian Kozlica
4402797b35
Add support for Grim in Wayland sessions ImageGrab (#8912)
Co-authored-by: Andrew Murray <radarhere@users.noreply.github.com>
2025-04-21 12:36:40 +10:00
Hugo van Kemenade
03e7871afd
Add make [-C docs] htmllive to rebuild and reload HTML files (#8913)
Co-authored-by: Andrew Murray <3112309+radarhere@users.noreply.github.com>
2025-04-20 00:18:01 +03:00
Hugo van Kemenade
d546233be4
Remove indentation from lists (#8915) 2025-04-19 22:50:57 +03:00
Andrew Murray
cf48bbf0c4 Removed indentation from list 2025-04-19 20:26:03 +10:00
Hugo van Kemenade
eb0395442c
Merge pull request #8904 from radarhere/harfbuzz 2025-04-18 14:29:58 +03:00
Andrew Murray
00ae9dda35 Changed harfbuzz buildtype to minsize 2025-04-18 18:49:11 +10:00
wiredfool
ac500460df lint 2025-04-17 22:22:31 +01:00
wiredfool
c729d4e208 Test uint32 array creation -> image32 images 2025-04-17 22:16:27 +01:00
wiredfool
3d77723a0c Added arrow support for a flat array of 4*uint8 for image32 modes 2025-04-17 21:42:42 +01:00
Andrew Murray
bc05a88ce6 Anchor left when justifying words 2025-04-17 20:56:02 +10:00
Andrew Murray
b955cee725 Do not justify last line 2025-04-17 19:36:52 +10:00
Andrew Murray
cccc07269a Do not justify a single word 2025-04-17 19:23:24 +10:00
Andrew Murray
ccc4668d4e Updated harfbuzz to 11.1.0 2025-04-17 08:04:34 +10:00
Andrew Murray
3d4119521c
Close file pointer earlier (#8895) 2025-04-16 18:49:57 +03:00
Andrew Murray
f630ec097b
Build Windows arm64 wheels on arm64 runner (#8898) 2025-04-16 14:05:08 +03:00
Andrew Murray
6ea7dc8eea
Add Fedora 42 (#8899) 2025-04-16 10:06:52 +03:00
Andrew Murray
507fefbce4
Python 3.13 is tested on Arch (#8894) 2025-04-15 14:02:35 +03:00
Andrew Murray
bd39801a7b
Merge pull request #8893 from python-pillow/doc-fix
Move XV Thumbnails to read only section
2025-04-15 12:16:17 +10:00
Jeffrey A. Clark
8b1777b999 Move XV Thumbnails to read only section 2025-04-14 14:51:01 -04:00
Andrew Murray
4716bb7818
Update macOS tested Pillow versions (#8890) 2025-04-13 16:59:05 +03:00
Andrew Murray
c6434dbbbc Set color table fourth channel to zero for 1 and L mode when saving 2025-04-13 23:00:06 +10:00
Hugo van Kemenade
1299039ec4
Merge pull request #8887 from radarhere/fedora
Removed Fedora 40
2025-04-13 11:27:27 +03:00
Andrew Murray
5294021438 Removed Fedora 40 2025-04-13 09:26:06 +10:00
Hugo van Kemenade
f9083264ff 11.3.0.dev0 version bump 2025-04-12 20:56:35 +03:00
Andrew Murray
b2945ec2aa Test truncated header 2025-04-10 22:07:55 +10:00
Andrew Murray
dce9608961 Test unknown colour and missing colour key 2025-04-10 21:59:04 +10:00
Andrew Murray
af52060e97 Mention that tobytes() with the raw encoder uses Pack.c 2025-04-10 20:45:53 +10:00
Andrew Murray
34efaaddf3 Improved type hints 2025-04-10 18:57:58 +10:00
Andrew Murray
6512a8e371 Test not enough image data 2025-04-10 18:57:58 +10:00
Andrew Murray
395bd6bd12 Allow more than 256 colours 2025-04-10 18:57:58 +10:00
Andrew Murray
89ac20d2b9 Allow more than 1 character per pixel 2025-04-10 18:57:58 +10:00
Andrew Murray
7b459a8524 Improved reading XPM images 2025-04-10 18:57:58 +10:00
Andrew Murray
75d3f1d3bd Assert palette is not None 2025-04-10 18:41:12 +10:00
Andrew Murray
867c4772c2 Do not import type checking 2025-04-01 20:23:16 +11:00
Andrew Murray
f056c259a7 Support ttb multiline text 2025-02-06 22:30:09 +11:00
297 changed files with 15110 additions and 2576 deletions

View File

@ -66,7 +66,7 @@ if [[ $(uname) != CYGWIN* ]]; then
pushd depends && ./install_raqm.sh && popd
# libavif
pushd depends && CMAKE_POLICY_VERSION_MINIMUM=3.5 ./install_libavif.sh && popd
pushd depends && ./install_libavif.sh && popd
# extra test images
pushd depends && ./install_extra_test_images.sh && popd

View File

@ -1 +1 @@
cibuildwheel==2.23.2
cibuildwheel==3.0.1

View File

@ -1,9 +1,11 @@
mypy==1.15.0
mypy==1.17.0
IceSpringPySideStubs-PyQt6
IceSpringPySideStubs-PySide6
ipython
numpy
packaging
pyarrow-stubs
pybind11
pytest
sphinx
types-atheris

View File

@ -1,3 +1,3 @@
python.exe -c "from PIL import Image"
IF ERRORLEVEL 1 EXIT /B
python.exe -bb -m pytest -v -x -W always --cov PIL --cov Tests --cov-report term --cov-report xml Tests
python.exe -bb -m pytest -vv -x -W always --cov PIL --cov Tests --cov-report term --cov-report xml Tests

View File

@ -4,4 +4,4 @@ set -e
python3 -c "from PIL import Image"
python3 -bb -m pytest -v -x -W always --cov PIL --cov Tests --cov-report term --cov-report xml Tests $REVERSE
python3 -bb -m pytest -vv -x -W always --cov PIL --cov Tests --cov-report term --cov-report xml Tests $REVERSE

View File

@ -18,6 +18,5 @@ exclude_also =
[run]
omit =
Tests/32bit_segfault_check.py
Tests/check_*.py
checks/*.py
Tests/createfontdatachunk.py

46
.github/ISSUE_TEMPLATE/RELEASE.md vendored Normal file
View File

@ -0,0 +1,46 @@
---
name: "Maintainers only: Release"
about: For maintainers to schedule a quarterly release
labels: Release
---
## 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.
* [ ] Add release notes e.g. https://github.com/python-pillow/Pillow/pull/8885
* [ ] Check [GitHub Actions](https://github.com/python-pillow/Pillow/actions) to confirm passing tests in `main` branch.
* [ ] Check that all the wheel builds pass the tests in the [GitHub Actions "Wheels" workflow](https://github.com/python-pillow/Pillow/actions/workflows/wheels.yml) jobs by manually triggering them.
* [ ] 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` in a freshly cloned repo.
* [ ] Create branch and tag for release e.g.:
```bash
git branch [[MAJOR.MINOR]].x
git tag [[MAJOR.MINOR]].0
git push --tags
```
* [ ] Check the [GitHub Actions "Wheels" workflow](https://github.com/python-pillow/Pillow/actions/workflows/wheels.yml) has passed, including the "Upload release to PyPI" job. This will have been triggered by the new tag.
* [ ] 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
```
## Publicize release
* [ ] Announce release availability via [Mastodon](https://fosstodon.org/@pillow) e.g. https://fosstodon.org/@pillow/110639450470725321
## 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]]
```

View File

@ -52,7 +52,7 @@ jobs:
persist-credentials: false
- name: Install Cygwin
uses: cygwin/cygwin-install-action@v5
uses: cygwin/cygwin-install-action@v6
with:
packages: >
gcc-g++
@ -89,10 +89,6 @@ jobs:
with:
dirs: 'C:\cygwin\bin;C:\cygwin\lib\lapack'
- name: Select Python version
run: |
ln -sf c:/cygwin/bin/python3.${{ matrix.python-minor-version }} c:/cygwin/bin/python3
- name: pip cache
uses: actions/cache@v4
with:

View File

@ -47,8 +47,8 @@ jobs:
centos-stream-10-amd64,
debian-12-bookworm-x86,
debian-12-bookworm-amd64,
fedora-40-amd64,
fedora-41-amd64,
fedora-42-amd64,
gentoo,
ubuntu-22.04-jammy-amd64,
ubuntu-24.04-noble-amd64,

View File

@ -0,0 +1,60 @@
name: Test Valgrind Memory Leaks
# like the Docker tests, but running valgrind only on *.c/*.h changes.
# this is very expensive. Only run on the pull request.
on:
# push:
# branches:
# - "**"
# paths:
# - ".github/workflows/test-valgrind.yml"
# - "**.c"
# - "**.h"
pull_request:
paths:
- ".github/workflows/test-valgrind.yml"
- "**.c"
- "**.h"
- "depends/docker-test-valgrind-memory.sh"
workflow_dispatch:
permissions:
contents: read
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
jobs:
build:
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
docker: [
ubuntu-22.04-jammy-amd64-valgrind,
]
dockerTag: [main]
name: ${{ matrix.docker }}
steps:
- uses: actions/checkout@v4
with:
persist-credentials: false
- name: Build system information
run: python3 .github/workflows/system-info.py
- name: Docker pull
run: |
docker pull pythonpillow/${{ matrix.docker }}:${{ matrix.dockerTag }}
- name: Build and Run Valgrind
run: |
# The Pillow user in the docker container is UID 1001
sudo chown -R 1001 $GITHUB_WORKSPACE
docker run --name pillow_container -e "PILLOW_VALGRIND_TEST=true" -v $GITHUB_WORKSPACE:/Pillow pythonpillow/${{ matrix.docker }}:${{ matrix.dockerTag }} /Pillow/depends/docker-test-valgrind-memory.sh
sudo chown -R runner $GITHUB_WORKSPACE

View File

@ -31,16 +31,15 @@ env:
jobs:
build:
runs-on: ${{ matrix.os }}
runs-on: windows-latest
strategy:
fail-fast: false
matrix:
python-version: ["pypy3.11", "pypy3.10", "3.10", "3.11", "3.12", "3.13", "3.14"]
python-version: ["pypy3.11", "3.10", "3.11", "3.12", ">=3.13.5", "3.14"]
architecture: ["x64"]
os: ["windows-latest"]
include:
# Test the oldest Python on 32-bit
- { python-version: "3.9", architecture: "x86", os: "windows-2019" }
- { python-version: "3.9", architecture: "x86" }
timeout-minutes: 45
@ -84,7 +83,7 @@ jobs:
python3 -m pip install --upgrade pip
- name: Install CPython dependencies
if: "!contains(matrix.python-version, 'pypy') && matrix.architecture != 'x86'"
if: "!contains(matrix.python-version, 'pypy') && !contains(matrix.python-version, '3.14') && matrix.architecture != 'x86'"
run: |
python3 -m pip install PyQt6
@ -98,8 +97,8 @@ jobs:
choco install nasm --no-progress
echo "C:\Program Files\NASM" >> $env:GITHUB_PATH
choco install ghostscript --version=10.5.0 --no-progress
echo "C:\Program Files\gs\gs10.05.0\bin" >> $env:GITHUB_PATH
choco install ghostscript --version=10.5.1 --no-progress
echo "C:\Program Files\gs\gs10.05.1\bin" >> $env:GITHUB_PATH
# Install extra test images
xcopy /S /Y Tests\test-images\* Tests\images

View File

@ -42,7 +42,7 @@ jobs:
]
python-version: [
"pypy3.11",
"pypy3.10",
"3.14t",
"3.14",
"3.13t",
"3.13",
@ -55,6 +55,7 @@ jobs:
- { python-version: "3.11", PYTHONOPTIMIZE: 1, REVERSE: "--reverse" }
- { python-version: "3.10", PYTHONOPTIMIZE: 2 }
# Free-threaded
- { python-version: "3.14t", disable-gil: true }
- { python-version: "3.13t", disable-gil: true }
# M1 only available for 3.10+
- { os: "macos-13", python-version: "3.9" }

View File

@ -1,77 +1,160 @@
#!/bin/bash
# Setup that needs to be done before multibuild utils are invoked
PROJECTDIR=$(pwd)
if [[ "$(uname -s)" == "Darwin" ]]; then
# Safety check - macOS builds require that CIBW_ARCHS is set, and that it
# only contains a single value (even though cibuildwheel allows multiple
# values in CIBW_ARCHS).
# Safety check - Pillow builds require that CIBW_ARCHS is set, and that it only
# contains a single value (even though cibuildwheel allows multiple values in
# CIBW_ARCHS). This check doesn't work on Linux because of how the CIBW_ARCHS
# variable is exposed.
function check_cibw_archs {
if [[ -z "$CIBW_ARCHS" ]]; then
echo "ERROR: Pillow macOS builds require CIBW_ARCHS be defined."
echo "ERROR: Pillow builds require CIBW_ARCHS be defined."
exit 1
fi
if [[ "$CIBW_ARCHS" == *" "* ]]; then
echo "ERROR: Pillow macOS builds only support a single architecture in CIBW_ARCHS."
echo "ERROR: Pillow builds only support a single architecture in CIBW_ARCHS."
exit 1
fi
}
# Setup that needs to be done before multibuild utils are invoked. Process
# potential cross-build platforms before native platforms to ensure that we pick
# up the cross environment.
PROJECTDIR=$(pwd)
if [[ "$CIBW_PLATFORM" == "ios" ]]; then
check_cibw_archs
# On iOS, CIBW_ARCHS is actually a multi-arch - arm64_iphoneos,
# arm64_iphonesimulator or x86_64_iphonesimulator. Split into the CPU
# platform, and the iOS SDK.
PLAT=$(echo $CIBW_ARCHS | sed "s/\(.*\)_\(.*\)/\1/")
IOS_SDK=$(echo $CIBW_ARCHS | sed "s/\(.*\)_\(.*\)/\2/")
# Build iOS builds in `build/iphoneos` or `build/iphonesimulator`
# (depending on the build target). Install them into `build/deps/iphoneos`
# or `build/deps/iphonesimulator`
WORKDIR=$(pwd)/build/$IOS_SDK
BUILD_PREFIX=$(pwd)/build/deps/$IOS_SDK
PATCH_DIR=$(pwd)/patches/iOS
# GNU tooling insists on using aarch64 rather than arm64
if [[ $PLAT == "arm64" ]]; then
GNU_ARCH=aarch64
else
GNU_ARCH=x86_64
fi
IOS_SDK_PATH=$(xcrun --sdk $IOS_SDK --show-sdk-path)
CMAKE_SYSTEM_NAME=iOS
IOS_HOST_TRIPLE=$PLAT-apple-ios$IPHONEOS_DEPLOYMENT_TARGET
if [[ "$IOS_SDK" == "iphonesimulator" ]]; then
IOS_HOST_TRIPLE=$IOS_HOST_TRIPLE-simulator
fi
# GNU Autotools doesn't recognize the existence of arm64-apple-ios-simulator
# as a valid host. However, the only difference between arm64-apple-ios and
# arm64-apple-ios-simulator is the choice of sysroot, and that is
# coordinated by CC, CFLAGS etc. From the perspective of configure, the two
# platforms are identical, so we can use arm64-apple-ios consistently.
# This (mostly) avoids us needing to patch config.sub in dependency sources.
HOST_CONFIGURE_FLAGS="--disable-shared --enable-static --host=$GNU_ARCH-apple-ios --build=$GNU_ARCH-apple-darwin"
# CMake has native support for iOS. However, most of that support is based
# on using the Xcode builder, which isn't very helpful for most of Pillow's
# dependencies. Therefore, we lean on the OSX configurations, plus CC, CFLAGS
# etc. to ensure the right sysroot is selected.
HOST_CMAKE_FLAGS="-DCMAKE_SYSTEM_NAME=$CMAKE_SYSTEM_NAME -DCMAKE_SYSTEM_PROCESSOR=$GNU_ARCH -DCMAKE_OSX_DEPLOYMENT_TARGET=$IPHONEOS_DEPLOYMENT_TARGET -DCMAKE_OSX_SYSROOT=$IOS_SDK_PATH -DBUILD_SHARED_LIBS=NO -DENABLE_SHARED=NO"
# Meson needs to be pointed at a cross-platform configuration file
# This will be generated once CC etc. have been evaluated.
HOST_MESON_FLAGS="--cross-file $WORKDIR/meson-cross.txt -Dprefer_static=true -Ddefault_library=static"
elif [[ "$(uname -s)" == "Darwin" ]]; then
check_cibw_archs
# Build macOS dependencies in `build/darwin`
# Install them into `build/deps/darwin`
PLAT=$CIBW_ARCHS
WORKDIR=$(pwd)/build/darwin
BUILD_PREFIX=$(pwd)/build/deps/darwin
else
# Build prefix will default to /usr/local
PLAT="${CIBW_ARCHS:-$AUDITWHEEL_ARCH}"
WORKDIR=$(pwd)/build
MB_ML_LIBC=${AUDITWHEEL_POLICY::9}
MB_ML_VER=${AUDITWHEEL_POLICY:9}
fi
PLAT="${CIBW_ARCHS:-$AUDITWHEEL_ARCH}"
# Define custom utilities
source wheels/multibuild/common_utils.sh
source wheels/multibuild/library_builders.sh
if [ -z "$IS_MACOS" ]; then
if [[ -z "$IS_MACOS" ]]; then
source wheels/multibuild/manylinux_utils.sh
fi
ARCHIVE_SDIR=pillow-depends-main
# Package versions for fresh source builds
# Package versions for fresh source builds. Version numbers with "Patched"
# annotations have a source code patch that is required for some platforms. If
# you change those versions, ensure the patch is also updated.
FREETYPE_VERSION=2.13.3
HARFBUZZ_VERSION=11.0.1
LIBPNG_VERSION=1.6.47
JPEGTURBO_VERSION=3.1.0
HARFBUZZ_VERSION=11.2.1
LIBPNG_VERSION=1.6.50
JPEGTURBO_VERSION=3.1.1
OPENJPEG_VERSION=2.5.3
XZ_VERSION=5.8.1
TIFF_VERSION=4.7.0
LCMS2_VERSION=2.17
ZLIB_VERSION=1.3.1
ZLIB_NG_VERSION=2.2.4
LIBWEBP_VERSION=1.5.0
LIBWEBP_VERSION=1.6.0
BZIP2_VERSION=1.0.8
LIBXCB_VERSION=1.17.0
BROTLI_VERSION=1.1.0
BROTLI_VERSION=1.1.0 # Patched; next release won't need patching. See patch file.
LIBAVIF_VERSION=1.3.0
function build_pkg_config {
if [ -e pkg-config-stamp ]; then return; fi
# This essentially duplicates the Homebrew recipe
CFLAGS="$CFLAGS -Wno-int-conversion" build_simple pkg-config 0.29.2 https://pkg-config.freedesktop.org/releases tar.gz \
# This essentially duplicates the Homebrew recipe.
# On iOS, we need a binary that can be executed on the build machine; but we
# can create a host-specific pc-path to store iOS .pc files. To ensure a
# macOS-compatible build, we temporarily clear environment flags that set
# iOS-specific values.
if [[ -n "$IOS_SDK" ]]; then
ORIGINAL_HOST_CONFIGURE_FLAGS=$HOST_CONFIGURE_FLAGS
ORIGINAL_IPHONEOS_DEPLOYMENT_TARGET=$IPHONEOS_DEPLOYMENT_TARGET
unset HOST_CONFIGURE_FLAGS
unset IPHONEOS_DEPLOYMENT_TARGET
fi
CFLAGS="$CFLAGS -Wno-int-conversion" CPPFLAGS="" build_simple pkg-config 0.29.2 https://pkg-config.freedesktop.org/releases tar.gz \
--disable-debug --disable-host-tool --with-internal-glib \
--with-pc-path=$BUILD_PREFIX/share/pkgconfig:$BUILD_PREFIX/lib/pkgconfig \
--with-system-include-path=$(xcrun --show-sdk-path --sdk macosx)/usr/include
if [[ -n "$IOS_SDK" ]]; then
HOST_CONFIGURE_FLAGS=$ORIGINAL_HOST_CONFIGURE_FLAGS
IPHONEOS_DEPLOYMENT_TARGET=$ORIGINAL_IPHONEOS_DEPLOYMENT_TARGET
fi;
export PKG_CONFIG=$BUILD_PREFIX/bin/pkg-config
touch pkg-config-stamp
}
function build_zlib_ng {
if [ -e zlib-stamp ]; then return; fi
# zlib-ng uses a "configure" script, but it's not a GNU autotools script, so
# it doesn't honor the usual flags. Temporarily disable any
# cross-compilation flags.
ORIGINAL_HOST_CONFIGURE_FLAGS=$HOST_CONFIGURE_FLAGS
unset HOST_CONFIGURE_FLAGS
build_github zlib-ng/zlib-ng $ZLIB_NG_VERSION --zlib-compat
if [ -n "$IS_MACOS" ]; then
HOST_CONFIGURE_FLAGS=$ORIGINAL_HOST_CONFIGURE_FLAGS
if [[ -n "$IS_MACOS" ]] && [[ -z "$IOS_SDK" ]]; then
# Ensure that on macOS, the library name is an absolute path, not an
# @rpath, so that delocate picks up the right library (and doesn't need
# DYLD_LIBRARY_PATH to be set). The default Makefile doesn't have an
# option to control the install_name.
# option to control the install_name. This isn't needed on iOS, as iOS
# only builds the static library.
install_name_tool -id $BUILD_PREFIX/lib/libz.1.dylib $BUILD_PREFIX/lib/libz.1.dylib
fi
touch zlib-stamp
@ -81,7 +164,7 @@ function build_brotli {
if [ -e brotli-stamp ]; then return; fi
local out_dir=$(fetch_unpack https://github.com/google/brotli/archive/v$BROTLI_VERSION.tar.gz brotli-$BROTLI_VERSION.tar.gz)
(cd $out_dir \
&& cmake -DCMAKE_INSTALL_PREFIX=$BUILD_PREFIX -DCMAKE_INSTALL_LIBDIR=$BUILD_PREFIX/lib -DCMAKE_INSTALL_NAME_DIR=$BUILD_PREFIX/lib . \
&& cmake -DCMAKE_INSTALL_PREFIX=$BUILD_PREFIX -DCMAKE_INSTALL_LIBDIR=$BUILD_PREFIX/lib -DCMAKE_INSTALL_NAME_DIR=$BUILD_PREFIX/lib $HOST_CMAKE_FLAGS . \
&& make install)
touch brotli-stamp
}
@ -92,12 +175,65 @@ function build_harfbuzz {
local out_dir=$(fetch_unpack https://github.com/harfbuzz/harfbuzz/releases/download/$HARFBUZZ_VERSION/harfbuzz-$HARFBUZZ_VERSION.tar.xz harfbuzz-$HARFBUZZ_VERSION.tar.xz)
(cd $out_dir \
&& meson setup build --prefix=$BUILD_PREFIX --libdir=$BUILD_PREFIX/lib --buildtype=release -Dfreetype=enabled -Dglib=disabled -Dtests=disabled)
&& meson setup build --prefix=$BUILD_PREFIX --libdir=$BUILD_PREFIX/lib --buildtype=minsize -Dfreetype=enabled -Dglib=disabled -Dtests=disabled $HOST_MESON_FLAGS)
(cd $out_dir/build \
&& meson install)
touch harfbuzz-stamp
}
function build_libavif {
if [ -e libavif-stamp ]; then return; fi
python3 -m pip install meson ninja
if [[ "$PLAT" == "x86_64" ]] || [ -n "$SANITIZER" ]; then
build_simple nasm 2.16.03 https://www.nasm.us/pub/nasm/releasebuilds/2.16.03
fi
local build_type=MinSizeRel
local lto=ON
local libavif_cmake_flags
if [ -n "$IS_MACOS" ]; then
lto=OFF
libavif_cmake_flags=(
-DCMAKE_C_FLAGS_MINSIZEREL="-Oz -DNDEBUG -flto" \
-DCMAKE_CXX_FLAGS_MINSIZEREL="-Oz -DNDEBUG -flto" \
-DCMAKE_SHARED_LINKER_FLAGS_INIT="-Wl,-S,-x,-dead_strip_dylibs" \
)
else
if [[ "$MB_ML_VER" == 2014 ]] && [[ "$PLAT" == "x86_64" ]]; then
build_type=Release
fi
libavif_cmake_flags=(-DCMAKE_SHARED_LINKER_FLAGS_INIT="-Wl,--strip-all,-z,relro,-z,now")
fi
local out_dir=$(fetch_unpack https://github.com/AOMediaCodec/libavif/archive/refs/tags/v$LIBAVIF_VERSION.tar.gz libavif-$LIBAVIF_VERSION.tar.gz)
# CONFIG_AV1_HIGHBITDEPTH=0 is a flag for libaom (included as a subproject
# of libavif) that disables support for encoding high bit depth images.
(cd $out_dir \
&& cmake \
-DCMAKE_INSTALL_PREFIX=$BUILD_PREFIX \
-DCMAKE_INSTALL_LIBDIR=$BUILD_PREFIX/lib \
-DCMAKE_INSTALL_NAME_DIR=$BUILD_PREFIX/lib \
-DBUILD_SHARED_LIBS=ON \
-DAVIF_LIBSHARPYUV=LOCAL \
-DAVIF_LIBYUV=LOCAL \
-DAVIF_CODEC_AOM=LOCAL \
-DCONFIG_AV1_HIGHBITDEPTH=0 \
-DAVIF_CODEC_AOM_DECODE=OFF \
-DAVIF_CODEC_DAV1D=LOCAL \
-DCMAKE_INTERPROCEDURAL_OPTIMIZATION=$lto \
-DCMAKE_C_VISIBILITY_PRESET=hidden \
-DCMAKE_CXX_VISIBILITY_PRESET=hidden \
-DCMAKE_BUILD_TYPE=$build_type \
"${libavif_cmake_flags[@]}" \
. \
&& make install)
touch libavif-stamp
}
function build {
build_xz
if [ -z "$IS_ALPINE" ] && [ -z "$SANITIZER" ] && [ -z "$IS_MACOS" ]; then
@ -110,19 +246,19 @@ function build {
fi
build_simple xcb-proto 1.17.0 https://xorg.freedesktop.org/archive/individual/proto
if [ -n "$IS_MACOS" ]; then
if [[ -n "$IS_MACOS" ]]; then
build_simple xorgproto 2024.1 https://www.x.org/pub/individual/proto
build_simple libXau 1.0.12 https://www.x.org/pub/individual/lib
build_simple libpthread-stubs 0.5 https://xcb.freedesktop.org/dist
else
sed s/\${pc_sysrootdir\}// $BUILD_PREFIX/share/pkgconfig/xcb-proto.pc > $BUILD_PREFIX/lib/pkgconfig/xcb-proto.pc
sed "s/\${pc_sysrootdir\}//" $BUILD_PREFIX/share/pkgconfig/xcb-proto.pc > $BUILD_PREFIX/lib/pkgconfig/xcb-proto.pc
fi
build_simple libxcb $LIBXCB_VERSION https://www.x.org/releases/individual/lib
build_libjpeg_turbo
if [ -n "$IS_MACOS" ]; then
if [[ -n "$IS_MACOS" ]]; then
# Custom tiff build to include jpeg; by default, configure won't include
# headers/libs in the custom macOS prefix. Explicitly disable webp,
# headers/libs in the custom macOS/iOS prefix. Explicitly disable webp,
# libdeflate and zstd, because on x86_64 macs, it will pick up the
# Homebrew versions of those libraries from /usr/local.
build_simple tiff $TIFF_VERSION https://download.osgeo.org/libtiff tar.gz \
@ -132,6 +268,10 @@ function build {
build_tiff
fi
if [[ -z "$IOS_SDK" ]]; then
# Short term workaround; don't build libavif on iOS
build_libavif
fi
build_libpng
build_lcms2
build_openjpeg
@ -140,20 +280,54 @@ function build {
if [[ -n "$IS_MACOS" ]]; then
webp_cflags="$webp_cflags -Wl,-headerpad_max_install_names"
fi
CFLAGS="$CFLAGS $webp_cflags" build_simple libwebp $LIBWEBP_VERSION \
webp_ldflags=""
if [[ -n "$IOS_SDK" ]]; then
webp_ldflags="$webp_ldflags -llzma -lz"
fi
CFLAGS="$CFLAGS $webp_cflags" LDFLAGS="$LDFLAGS $webp_ldflags" build_simple libwebp $LIBWEBP_VERSION \
https://storage.googleapis.com/downloads.webmproject.org/releases/webp tar.gz \
--enable-libwebpmux --enable-libwebpdemux
build_brotli
if [ -n "$IS_MACOS" ]; then
if [[ -n "$IS_MACOS" ]]; then
# Custom freetype build
build_simple freetype $FREETYPE_VERSION https://download.savannah.gnu.org/releases/freetype tar.gz --with-harfbuzz=no
else
build_freetype
fi
build_harfbuzz
if [[ -z "$IOS_SDK" ]]; then
# On iOS, there's no vendor-provided raqm, and we can't ship it due to
# licensing, so there's no point building harfbuzz.
build_harfbuzz
fi
}
function create_meson_cross_config {
cat << EOF > $WORKDIR/meson-cross.txt
[binaries]
pkg-config = '$BUILD_PREFIX/bin/pkg-config'
cmake = '$(which cmake)'
c = '$CC'
cpp = '$CXX'
strip = '$STRIP'
[built-in options]
c_args = '$CFLAGS -I$BUILD_PREFIX/include'
cpp_args = '$CXXFLAGS -I$BUILD_PREFIX/include'
c_link_args = '$CFLAGS -L$BUILD_PREFIX/lib'
cpp_link_args = '$CFLAGS -L$BUILD_PREFIX/lib'
[host_machine]
system = 'darwin'
subsystem = 'ios'
kernel = 'xnu'
cpu_family = '$(uname -m)'
cpu = '$(uname -m)'
endian = 'little'
EOF
}
# Perform all dependency builds in the build subfolder.
@ -172,28 +346,53 @@ if [[ ! -d $WORKDIR/pillow-depends-main ]]; then
fi
if [[ -n "$IS_MACOS" ]]; then
# Homebrew (or similar packaging environments) install can contain some of
# the libraries that we're going to build. However, they may be compiled
# with a MACOSX_DEPLOYMENT_TARGET that doesn't match what we want to use,
# and they may bring in other dependencies that we don't want. The same will
# be true of any other locations on the path. To avoid conflicts, strip the
# path down to the bare minimum (which, on macOS, won't include any
# development dependencies).
export PATH="$BUILD_PREFIX/bin:$(dirname $(which python3)):/usr/bin:/bin:/usr/sbin:/sbin:/Library/Apple/usr/bin"
export CMAKE_PREFIX_PATH=$BUILD_PREFIX
# Ensure the basic structure of the build prefix directory exists.
mkdir -p "$BUILD_PREFIX/bin"
mkdir -p "$BUILD_PREFIX/lib"
# Ensure pkg-config is available
# Ensure pkg-config is available. This is done *before* setting CC, CFLAGS
# etc. to ensure that the build is *always* a macOS build, even when building
# for iOS.
build_pkg_config
# Ensure cmake is available
# Ensure cmake is available, and that the default prefix used by CMake is
# the build prefix
python3 -m pip install cmake
export CMAKE_PREFIX_PATH=$BUILD_PREFIX
if [[ -n "$IOS_SDK" ]]; then
export AR="$(xcrun --find --sdk $IOS_SDK ar)"
export CPP="$(xcrun --find --sdk $IOS_SDK clang) -E"
export CC=$(xcrun --find --sdk $IOS_SDK clang)
export CXX=$(xcrun --find --sdk $IOS_SDK clang++)
export LD=$(xcrun --find --sdk $IOS_SDK ld)
export STRIP=$(xcrun --find --sdk $IOS_SDK strip)
CPPFLAGS="$CPPFLAGS --sysroot=$IOS_SDK_PATH"
CFLAGS="-target $IOS_HOST_TRIPLE --sysroot=$IOS_SDK_PATH -mios-version-min=$IPHONEOS_DEPLOYMENT_TARGET"
CXXFLAGS="-target $IOS_HOST_TRIPLE --sysroot=$IOS_SDK_PATH -mios-version-min=$IPHONEOS_DEPLOYMENT_TARGET"
# Having IPHONEOS_DEPLOYMENT_TARGET in the environment causes problems
# with some cross-building toolchains, because it introduces implicit
# behavior into clang.
unset IPHONEOS_DEPLOYMENT_TARGET
# Now that we know CC etc., we can create a meson cross-configuration file
create_meson_cross_config
fi
fi
wrap_wheel_builder build
# A safety catch for iOS. iOS can't use dynamic libraries, but clang will prefer
# to link dynamic libraries to static libraries. The only way to reliably
# prevent this is to not have dynamic libraries available in the first place.
# The build process *shouldn't* generate any dylibs... but just in case, purge
# any dylibs that *have* been installed into the build prefix directory.
if [[ -n "$IOS_SDK" ]]; then
find "$BUILD_PREFIX" -name "*.dylib" -exec rm -rf {} \;
fi
# Return to the project root to finish the build
popd > /dev/null

View File

@ -9,17 +9,18 @@ if ("$venv" -like "*\cibw-run-*\pp*-win_amd64\*") {
C:\vc_redist.x64.exe /install /quiet /norestart | Out-Null
}
$env:path += ";$pillow\winbuild\build\bin\"
& "$venv\Scripts\activate.ps1"
& reg add "HKLM\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Image File Execution Options\python.exe" /v "GlobalFlag" /t REG_SZ /d "0x02000000" /f
if ("$venv" -like "*\cibw-run-*-win_amd64\*") {
& python -m pip install numpy
if (Test-Path $venv\Scripts\pypy.exe) {
$python = "pypy.exe"
} else {
$python = "python.exe"
}
& reg add "HKLM\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Image File Execution Options\python.exe" /v "GlobalFlag" /t REG_SZ /d "0x02000000" /f
cd $pillow
& python -VV
& $venv\Scripts\$python -VV
if (!$?) { exit $LASTEXITCODE }
& python selftest.py
& $venv\Scripts\$python selftest.py
if (!$?) { exit $LASTEXITCODE }
& python -m pytest -vx Tests\check_wheel.py
& $venv\Scripts\$python -m pytest -vv -x checks\check_wheel.py
if (!$?) { exit $LASTEXITCODE }
& python -m pytest -vx Tests
& $venv\Scripts\$python -m pytest -vv -x Tests
if (!$?) { exit $LASTEXITCODE }

View File

@ -25,8 +25,6 @@ else
yum install -y fribidi
fi
python3 -m pip install numpy
if [ ! -d "test-images-main" ]; then
curl -fsSL -o pillow-test-images.zip https://github.com/python-pillow/test-images/archive/main.zip
unzip pillow-test-images.zip
@ -35,5 +33,5 @@ fi
# Runs tests
python3 selftest.py
python3 -m pytest Tests/check_wheel.py
python3 -m pytest
python3 -m pytest -vv -x checks/check_wheel.py
python3 -m pytest -vv -x

View File

@ -51,40 +51,60 @@ jobs:
matrix:
include:
- name: "macOS 10.10 x86_64"
platform: macos
os: macos-13
cibw_arch: x86_64
build: "cp3{9,10,11}*"
macosx_deployment_target: "10.10"
- name: "macOS 10.13 x86_64"
platform: macos
os: macos-13
cibw_arch: x86_64
build: "cp3{12,13}*"
build: "cp3{12,13,14}*"
macosx_deployment_target: "10.13"
- name: "macOS 10.15 x86_64"
platform: macos
os: macos-13
cibw_arch: x86_64
build: "pp3*"
macosx_deployment_target: "10.15"
- name: "macOS arm64"
platform: macos
os: macos-latest
cibw_arch: arm64
macosx_deployment_target: "11.0"
- name: "manylinux2014 and musllinux x86_64"
platform: linux
os: ubuntu-latest
cibw_arch: x86_64
manylinux: "manylinux2014"
- name: "manylinux_2_28 x86_64"
platform: linux
os: ubuntu-latest
cibw_arch: x86_64
build: "*manylinux*"
manylinux: "manylinux_2_28"
- name: "manylinux2014 and musllinux aarch64"
platform: linux
os: ubuntu-24.04-arm
cibw_arch: aarch64
manylinux: "manylinux2014"
- name: "manylinux_2_28 aarch64"
platform: linux
os: ubuntu-24.04-arm
cibw_arch: aarch64
build: "*manylinux*"
manylinux: "manylinux_2_28"
- name: "iOS arm64 device"
platform: ios
os: macos-latest
cibw_arch: arm64_iphoneos
- name: "iOS arm64 simulator"
platform: ios
os: macos-latest
cibw_arch: arm64_iphonesimulator
- name: "iOS x86_64 simulator"
platform: ios
os: macos-13
cibw_arch: x86_64_iphonesimulator
steps:
- uses: actions/checkout@v4
with:
@ -103,6 +123,7 @@ jobs:
run: |
python3 -m cibuildwheel --output-dir wheelhouse
env:
CIBW_PLATFORM: ${{ matrix.platform }}
CIBW_ARCHS: ${{ matrix.cibw_arch }}
CIBW_BUILD: ${{ matrix.build }}
CIBW_ENABLE: cpython-prerelease cpython-freethreading pypy
@ -110,25 +131,27 @@ jobs:
CIBW_MANYLINUX_PYPY_AARCH64_IMAGE: ${{ matrix.manylinux }}
CIBW_MANYLINUX_PYPY_X86_64_IMAGE: ${{ matrix.manylinux }}
CIBW_MANYLINUX_X86_64_IMAGE: ${{ matrix.manylinux }}
CIBW_SKIP: pp39-*
MACOSX_DEPLOYMENT_TARGET: ${{ matrix.macosx_deployment_target }}
- uses: actions/upload-artifact@v4
with:
name: dist-${{ matrix.os }}${{ matrix.macosx_deployment_target && format('-{0}', matrix.macosx_deployment_target) }}-${{ matrix.cibw_arch }}${{ matrix.manylinux && format('-{0}', matrix.manylinux) }}
name: dist-${{ matrix.name }}
path: ./wheelhouse/*.whl
windows:
if: github.event_name != 'schedule' || github.repository_owner == 'python-pillow'
name: Windows ${{ matrix.cibw_arch }}
runs-on: windows-latest
runs-on: ${{ matrix.os }}
strategy:
fail-fast: false
matrix:
include:
- cibw_arch: x86
os: windows-latest
- cibw_arch: AMD64
os: windows-latest
- cibw_arch: ARM64
os: windows-11-arm
steps:
- uses: actions/checkout@v4
with:
@ -157,7 +180,7 @@ jobs:
# Install extra test images
xcopy /S /Y Tests\test-images\* Tests\images
& python.exe winbuild\build_prepare.py -v --no-imagequant --no-avif --architecture=${{ matrix.cibw_arch }}
& python.exe winbuild\build_prepare.py -v --no-imagequant --architecture=${{ matrix.cibw_arch }}
shell: pwsh
- name: Build wheels
@ -185,7 +208,6 @@ jobs:
CIBW_BEFORE_ALL: "{package}\\winbuild\\build\\build_dep_all.cmd"
CIBW_CACHE_PATH: "C:\\cibw"
CIBW_ENABLE: cpython-prerelease cpython-freethreading pypy
CIBW_SKIP: pp39-*
CIBW_TEST_SKIP: "*-win_arm64"
CIBW_TEST_COMMAND: 'docker run --rm
-v {project}:C:\pillow

7
.github/zizmor.yml vendored Normal file
View File

@ -0,0 +1,7 @@
# Configuration for the zizmor static analysis tool, run via pre-commit in CI
# https://woodruffw.github.io/zizmor/configuration/
rules:
unpinned-uses:
config:
policies:
"*": ref-pin

View File

@ -1,8 +1,8 @@
repos:
- repo: https://github.com/astral-sh/ruff-pre-commit
rev: v0.11.4
rev: v0.12.2
hooks:
- id: ruff
- id: ruff-check
args: [--exit-non-zero-on-fix]
- repo: https://github.com/psf/black-pre-commit-mirror
@ -11,7 +11,7 @@ repos:
- id: black
- repo: https://github.com/PyCQA/bandit
rev: 1.8.3
rev: 1.8.6
hooks:
- id: bandit
args: [--severity-level=high]
@ -21,10 +21,10 @@ repos:
rev: v1.5.5
hooks:
- id: remove-tabs
exclude: (Makefile$|\.bat$|\.cmake$|\.eps$|\.fits$|\.gd$|\.opt$)
exclude: (Makefile$|\.bat$|\.cmake$|\.eps$|\.fits$|\.gd$|\.opt$|\.patch$)
- repo: https://github.com/pre-commit/mirrors-clang-format
rev: v20.1.0
rev: v20.1.7
hooks:
- id: clang-format
types: [c]
@ -46,19 +46,19 @@ repos:
- id: check-yaml
args: [--allow-multiple-documents]
- id: end-of-file-fixer
exclude: ^Tests/images/
exclude: ^Tests/images/|\.patch$
- id: trailing-whitespace
exclude: ^.github/.*TEMPLATE|^Tests/(fonts|images)/
exclude: ^.github/.*TEMPLATE|^Tests/(fonts|images)/|\.patch$
- repo: https://github.com/python-jsonschema/check-jsonschema
rev: 0.32.1
rev: 0.33.2
hooks:
- id: check-github-workflows
- id: check-readthedocs
- id: check-renovate
- repo: https://github.com/woodruffw/zizmor-pre-commit
rev: v1.5.2
rev: v1.11.0
hooks:
- id: zizmor
@ -68,7 +68,7 @@ repos:
- id: sphinx-lint
- repo: https://github.com/tox-dev/pyproject-fmt
rev: v2.5.1
rev: v2.6.0
hooks:
- id: pyproject-fmt

View File

@ -13,6 +13,9 @@ include LICENSE
include Makefile
include tox.ini
graft Tests
graft Tests/images
graft checks
graft patches
graft src
graft depends
graft winbuild
@ -26,8 +29,19 @@ exclude .editorconfig
exclude .readthedocs.yml
exclude codecov.yml
exclude renovate.json
exclude Tests/images/README.md
exclude Tests/images/crash*.tif
exclude Tests/images/string_dimension.tiff
global-exclude .git*
global-exclude *.pyc
global-exclude *.so
prune .ci
prune wheels
prune winbuild/build
prune winbuild/depends
prune Tests/errors
prune Tests/images/jpeg2000
prune Tests/images/msp
prune Tests/images/picins
prune Tests/images/sunraster
prune Tests/test-images

View File

@ -23,6 +23,10 @@ doc html:
htmlview:
$(MAKE) -C docs htmlview
.PHONY: htmllive
htmllive:
$(MAKE) -C docs htmllive
.PHONY: doccheck
doccheck:
$(MAKE) doc
@ -43,6 +47,7 @@ help:
@echo " docserve run an HTTP server on the docs directory"
@echo " html make HTML docs"
@echo " htmlview open the index page built by the html target in your browser"
@echo " htmllive rebuild and reload HTML files in your browser"
@echo " install make and install"
@echo " install-coverage make and install with C coverage"
@echo " lint run the lint checks"
@ -70,7 +75,7 @@ debug:
.PHONY: release-test
release-test:
python3 Tests/check_release_notes.py
python3 checks/check_release_notes.py
python3 -m pip install -e .[tests]
python3 selftest.py
python3 -m pytest Tests
@ -92,13 +97,27 @@ test:
python3 -c "import pytest" > /dev/null 2>&1 || python3 -m pip install pytest
python3 -m pytest -qq
.PHONY: test-p
test-p:
python3 -c "import xdist" > /dev/null 2>&1 || python3 -m pip install pytest-xdist
python3 -m pytest -qq -n auto
.PHONY: valgrind
valgrind:
python3 -c "import pytest_valgrind" > /dev/null 2>&1 || python3 -m pip install pytest-valgrind
PYTHONMALLOC=malloc valgrind --suppressions=Tests/oss-fuzz/python.supp --leak-check=no \
PILLOW_VALGRIND_TEST=true PYTHONMALLOC=malloc valgrind --suppressions=Tests/oss-fuzz/python.supp --leak-check=no \
--log-file=/tmp/valgrind-output \
python3 -m pytest --no-memcheck -vv --valgrind --valgrind-log=/tmp/valgrind-output
.PHONY: valgrind-leak
valgrind-leak:
python3 -c "import pytest_valgrind" > /dev/null 2>&1 || python3 -m pip install pytest-valgrind
PILLOW_VALGRIND_TEST=true PYTHONMALLOC=malloc valgrind --suppressions=Tests/oss-fuzz/python.supp \
--leak-check=full --show-leak-kinds=definite --errors-for-leak-kinds=definite \
--log-file=/tmp/valgrind-output \
python3 -m pytest -vv --valgrind --valgrind-log=/tmp/valgrind-output
.PHONY: readme
readme:
python3 -c "import markdown2" > /dev/null 2>&1 || python3 -m pip install markdown2

View File

@ -95,7 +95,7 @@ This library provides extensive file format support, an efficient internal repre
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
## More information
- [Documentation](https://pillow.readthedocs.io/)
- [Installation](https://pillow.readthedocs.io/en/latest/installation/basic-installation.html)
@ -107,6 +107,6 @@ The core image library is designed for fast access to data stored in a few basic
- [Changelog](https://github.com/python-pillow/Pillow/releases)
- [Pre-fork](https://github.com/python-pillow/Pillow/blob/main/CHANGES.rst#pre-fork)
## Report a Vulnerability
## Report a vulnerability
To report a security vulnerability, please follow the procedure described in the [Tidelift security policy](https://tidelift.com/docs/security).

View File

@ -1,34 +1,15 @@
# Release Checklist
# 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
## 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) to confirm passing tests in `main` branch.
* [ ] Check that all the wheel builds pass the tests in the [GitHub Actions "Wheels" workflow](https://github.com/python-pillow/Pillow/actions/workflows/wheels.yml) jobs by manually triggering them.
* [ ] 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` 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
```
* [ ] Check the [GitHub Actions "Wheels" workflow](https://github.com/python-pillow/Pillow/actions/workflows/wheels.yml)
has passed, including the "Upload release to PyPI" job. This will have been triggered
by the new tag.
* [ ] 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
* [ ] Create a new issue and select the "Maintainers only: Release" template.
## Point release
Released as needed for security, installation or critical bug fixes.
@ -58,7 +39,7 @@ Released as needed for security, installation or critical bug fixes.
git push
```
## Embargoed Release
## Embargoed release
Released as needed privately to individual vendors for critical security-related bug fixes.
@ -82,7 +63,7 @@ Released as needed privately to individual vendors for critical security-related
git push origin 2.5.x
```
## Publicize Release
## Publicize release
* [ ] Announce release availability via [Mastodon](https://fosstodon.org/@pillow) e.g. https://fosstodon.org/@pillow/110639450470725321
@ -90,7 +71,7 @@ Released as needed privately to individual vendors for critical security-related
* [ ] 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
## Docker images
* [ ] Update Pillow in the Docker Images repository
```bash

View File

@ -1,4 +1,4 @@
Pillow Tests
Pillow tests
============
Test scripts are named ``test_xxx.py``. Helper classes and functions can be found in ``helper.py``.

View File

@ -1,49 +0,0 @@
from __future__ import annotations
import platform
import sys
from PIL import features
from .helper import is_pypy
def test_wheel_modules() -> None:
expected_modules = {"pil", "tkinter", "freetype2", "littlecms2", "webp"}
# tkinter is not available in cibuildwheel installed CPython on Windows
try:
import tkinter
assert tkinter
except ImportError:
expected_modules.remove("tkinter")
assert set(features.get_supported_modules()) == expected_modules
def test_wheel_codecs() -> None:
expected_codecs = {"jpg", "jpg_2000", "zlib", "libtiff"}
assert set(features.get_supported_codecs()) == expected_codecs
def test_wheel_features() -> None:
expected_features = {
"webp_anim",
"webp_mux",
"transp_webp",
"raqm",
"fribidi",
"harfbuzz",
"libjpeg_turbo",
"zlib_ng",
"xcb",
}
if sys.platform == "win32":
expected_features.remove("xcb")
elif sys.platform == "darwin" and not is_pypy() and platform.processor() != "arm":
expected_features.remove("zlib_ng")
assert set(features.get_supported_features()) == expected_features

View File

@ -161,6 +161,12 @@ def assert_tuple_approx_equal(
pytest.fail(msg + ": " + repr(actuals) + " != " + repr(targets))
def timeout_unless_slower_valgrind(timeout: float) -> pytest.MarkDecorator:
if "PILLOW_VALGRIND_TEST" in os.environ:
return pytest.mark.pil_noop_mark()
return pytest.mark.timeout(timeout)
def skip_unless_feature(feature: str) -> pytest.MarkDecorator:
reason = f"{feature} not available"
return pytest.mark.skipif(not features.check(feature), reason=reason)
@ -265,17 +271,13 @@ def _cached_hopper(mode: str) -> Image.Image:
im = hopper("L")
else:
im = hopper()
if mode.startswith("BGR;"):
with pytest.warns(DeprecationWarning):
im = im.convert(mode)
else:
try:
im = im.convert(mode)
except ImportError:
if mode == "LAB":
im = Image.open("Tests/images/hopper.Lab.tif")
else:
raise
try:
im = im.convert(mode)
except ImportError:
if mode == "LAB":
im = Image.open("Tests/images/hopper.Lab.tif")
else:
raise
return im
@ -289,16 +291,6 @@ def djpeg_available() -> bool:
return False
def cjpeg_available() -> bool:
if shutil.which("cjpeg"):
try:
subprocess.check_call(["cjpeg", "-version"])
return True
except subprocess.CalledProcessError: # pragma: no cover
return False
return False
def netpbm_available() -> bool:
return bool(shutil.which("ppmquant") and shutil.which("ppmtogif"))

View File

@ -0,0 +1,390 @@
/* XPM */
static const char *hopper[] = {
/* columns rows colors chars-per-pixel */
"128 128 256 2 ",
" c #0C0C0D",
". c #0A0708",
"X c #1C0A04",
"o c #120B0C",
"O c #170808",
"+ c #0B110D",
"@ c #16120C",
"# c #0D0D12",
"$ c #0D0D1A",
"% c #070A16",
"& c #120D13",
"* c #120E1A",
"= c #1A0C16",
"- c #0D1114",
"; c #0D121B",
": c #091518",
"> c #131215",
", c #14131B",
"< c #1A141C",
"1 c #1B191D",
"2 c #191517",
"3 c #250906",
"4 c #390904",
"5 c #27150A",
"6 c #250A18",
"7 c #251719",
"8 c #361410",
"9 c #342215",
"0 c #0C0C24",
"q c #0C0D2B",
"w c #060927",
"e c #130D24",
"r c #150D2A",
"t c #0C1225",
"y c #0C122C",
"u c #061227",
"i c #151422",
"p c #1A1522",
"a c #1C1B23",
"s c #13132C",
"d c #19172A",
"f c #0C0D35",
"g c #130E37",
"h c #0D1436",
"j c #131333",
"k c #13143C",
"l c #191838",
"z c #241926",
"x c #231B38",
"c c #2E1226",
"v c #372628",
"b c #292538",
"n c #362B37",
"m c #2F2A2F",
"M c #1A2233",
"N c #4C150D",
"B c #740F10",
"V c #512916",
"C c #793419",
"Z c #6D2C13",
"A c #4E1524",
"S c #741624",
"D c #4E332E",
"F c #6F3629",
"G c #574438",
"H c #744831",
"J c #775A2E",
"K c #0E1444",
"L c #141443",
"P c #1B1A44",
"I c #14144B",
"U c #1A1B4C",
"Y c #181747",
"T c #1B1B53",
"R c #181955",
"E c #0F0E44",
"W c #231C46",
"Q c #231C56",
"! c #1C234E",
"~ c #272547",
"^ c #2E2F52",
"/ c #2E3765",
"( c #483947",
") c #742D4A",
"_ c #364970",
"` c #534A51",
"' c #6E534D",
"] c #756654",
"[ c #53556D",
"{ c #6B5B69",
"} c #746B71",
"| c #5E616A",
" . c #880C15",
".. c #881217",
"X. c #8D0D0F",
"o. c #8B3218",
"O. c #8C3828",
"+. c #AC2F30",
"@. c #9A1825",
"#. c #CE202B",
"$. c #8A452A",
"%. c #974A2B",
"&. c #884934",
"*. c #954B35",
"=. c #995539",
"-. c #895736",
";. c #A75738",
":. c #A84E30",
">. c #996839",
",. c #B6683B",
"<. c #AE6835",
"1. c #A35419",
"2. c #D26D19",
"3. c #CC712E",
"4. c #CD6922",
"5. c #A83152",
"6. c #985845",
"7. c #8A5748",
"8. c #AE5A46",
"9. c #916A4F",
"0. c #A96647",
"q. c #B76947",
"w. c #BA744A",
"e. c #B97757",
"r. c #AB6F53",
"t. c #8D736D",
"y. c #B27669",
"u. c #91566F",
"i. c #C56B4A",
"p. c #C8764B",
"a. c #C87856",
"s. c #D47A59",
"d. c #C96E53",
"f. c #C77C64",
"g. c #D17969",
"h. c #D45D68",
"j. c #C52A46",
"k. c #D58932",
"l. c #B38355",
"z. c #968775",
"x. c #BA8667",
"c. c #B38C74",
"v. c #AB9C73",
"b. c #C9845A",
"n. c #D7855B",
"m. c #D39454",
"M. c #E28C5B",
"N. c #F7B251",
"B. c #C78867",
"V. c #D98866",
"C. c #D8956A",
"Z. c #C79878",
"A. c #D89876",
"S. c #CD8C70",
"D. c #E38A68",
"F. c #E5956A",
"G. c #E79776",
"H. c #ED9176",
"J. c #D6A371",
"K. c #E8A379",
"L. c #F3A677",
"P. c #D8A05D",
"I. c #3D65AB",
"U. c #3F67B2",
"Y. c #3B5C9C",
"T. c #506796",
"R. c #72748D",
"E. c #446AAE",
"W. c #4869A9",
"Q. c #4166B2",
"!. c #436BB3",
"~. c #496EB4",
"^. c #476DB9",
"/. c #4A71B6",
"(. c #4C73BA",
"). c #4772B6",
"_. c #5176BC",
"`. c #547BBD",
"'. c #577BB7",
"]. c #5572A9",
"[. c #6B7CAA",
"{. c #505B8C",
"}. c #557CC1",
"|. c #4C73C2",
" X c #897987",
".X c #9F7593",
"XX c #C46B87",
"oX c #5981BF",
"OX c #5884BD",
"+X c #768AB9",
"@X c #7288B5",
"#X c #5C83C3",
"$X c #5D8AC5",
"%X c #6186C5",
"&X c #648AC6",
"*X c #6B8DC6",
"=X c #668BC9",
"-X c #6B8ECA",
";X c #6586C6",
":X c #738DC7",
">X c #6D91CB",
",X c #6C94C6",
"<X c #7294CC",
"1X c #7895C8",
"2X c #6E92D1",
"3X c #7294D3",
"4X c #7698D5",
"5X c #708ED1",
"6X c #7799E3",
"7X c #9B9399",
"8X c #928890",
"9X c #B89887",
"0X c #A99191",
"qX c #B9A598",
"wX c #B1A394",
"eX c #8C8EAA",
"rX c #AB9AA6",
"tX c #ABA4A9",
"yX c #B7A9A8",
"uX c #B7ABB4",
"iX c #B6AFB7",
"pX c #C69B86",
"aX c #D4978B",
"sX c #EF9C83",
"dX c #CAA487",
"fX c #D7A787",
"gX c #C7A899",
"hX c #D1B294",
"jX c #E9A887",
"kX c #F8A886",
"lX c #F9B798",
"zX c #F1B291",
"xX c #C9B3AD",
"cX c #F4B9A5",
"vX c #D497B3",
"bX c #D5C6B1",
"nX c #FEC4A6",
"mX c #EAD0B2",
"MX c #EDD1A4",
"NX c #8399C8",
"BX c #B2B4CD",
"VX c #C7BBC7",
"CX c #D3CBCF",
"ZX c #ECDAD1",
"AX c #F6E6DA",
"SX c #F7EACF",
"DX c #D1D1E9",
"FX c #E7DDE4",
"GX c #E9E5E8",
"HX c #F7EAE6",
"JX c #FDF6E9",
"KX c #FEFCFE",
"LX c #FAF7F7",
"PX c #F1EBF6",
"IX c #DCE2E5",
"UX c #BEC5DF",
/* pixels */
"L k f k P l y j T R I I U U L U R Q T L E E E R R R E U j } GX9XfXpXxXR.j ~ ~ = V Z.G > b R.DXPXLXHXHXHXHXCX~ / T.Y.T.T.W.T.W.E.Q.I.E.I.I.E.E.I.I.I.I.I.Y.I.Q.^.Q.E.E.E.E.Q.Q.~.U.U.U.U.U.U.Q.Q.U.U.U.U.U.U.Q.Q.Q.Q.U.U.U.Q.~.~.Q.U.Q.~.^._._._._._._._._.(.(.(.",
"L k f L L k y h T R I L U U L U R R T L E E E R I R I U l XuX' fXV v [ / P h z V Z.G a y l [ 7XCXHXJXHXHXCXb ! {.{.T.{._ _ {.T.W.W.T.T.W.I.I.U.U.I.I.E.W.I.I.Q.Q.E.E.E.Q.Q.~.~.~.U.U.U.U.Q.Q.Q.Q.U.U.U.U.U.Q.~.~.~.U.U.U.U.U.Q.~.Q.Q.Q.~.~._._._.'.`._._._._._.",
"L k f L L k 0 h T T I E U U L T T T U L h h E U R R E U W R.{ D pXF z l L U ^ p F fXD i P W Y ~ n CXHXHXHXFX8Xl W ~ ~ l ^ b b ^ ^ / [ T.W._.U.^.U.U.Q.E.W.W.~.^.E.E.E.~.~.Q.~.~.~.~.~.~.~.~.Q.Q.Q.Q.U.U.U.U.U.U.~.Q.U.U.U.U.U.U.^.~.~.Q.~.~._._.`.`.`.`._._.|.|.",
"k k f L L k 0 h U T L h I U I T T T U k h k E U Q I E U k ` m ' hXV z k U I Q d V fX( j L U W W z VXLX8X XuX( z b x d ` X X` n n b b ! {.W.~.I.Q.Q.Q.E.W.].~.I.~.~.~.~.~.~.~.Q.~.~.~.~.~.~.~.~.~.Q.Q.Q.U.U.U.U.U.Q.U.U.~.~.Q.Q.Q.Q.Q.Q.Q.~._._._._._._._._.|.|.",
"k k f L L k q j U T L h U U I T R T U k h h E I E R I I k b p ' Z.V z k ! T U p H Z.c k U L U W n CXCXn z = c c v 7X8X` 8XPX} c R.tX` n b / {.].W.~.~.E.W.E.~.^.~.~.~.~.^.~.~.~.~.~.E.E.~.~.~.~.~.~.~.~.~.~.U.U.~.U.~.~.~.~.~.Q.U.Q.~.~.E.~.~./././.(.(.(.(.(.(.",
"h k h P L k q j U T L h U U I T R T U h h h E E I R I E k d p ' dXV x P U L L z J fXv l L L P j n IX` m = = 7 ' HXLXKXCX7XKXtXrXLXKXJXqXv n ^ {.T.T.T.W.].E.Q.^.~.~.~.~.^.^.~.~.E.E.E.E.E.~.~.~.~.~.~.~.~.~.U.U.~.~.~.~.U.U.U.U.Q.Q.~.~.E.~.~.E.~.~.~./././.(.(.",
"j k k P Y k q h U U k h U R I R R T U h h h E E R R E I Y d d ' Z.V p L ! ! Y = H Z.v j h ! l b n iXtX7Xa p t.LXZX0XHXPXKXKXPXLXLXCXbXAXVXn m 8XVXeX[.T.W.W.^.^.~.~.~.^.^.^.^.~.E.E.E.E.E.~.U.U.~.~.~.~.~.~.~.~.~.~.~.U.I.I.I.~.~.Q.Q.~.~.~.~.E.~.~.~./.(.(.(.(.",
"j k k U P k 0 y L U k h U R I R T Q T E f E E E E I I I f k z ` Z.V d U T L P z >.B.c j l l l } IXKXKX8Xp ` t.` t.' G ] tXIXIXwX] ' z.t.` { c n PXKXLXUX+X].E.~.~.~.~.~.^.^.^.~.Q.E.E.E.E.U.U.U.~.~.~.~.~.~.~.~.~.~.I.I.I.I.~.~.~.Q.Q.Q.~.~.~.~.~.~.~./.(.(.(.(.",
"j k R.~ k k q q Y T k f Q T I I I Q I L E L E E R I I E f d x ` dXV d T T T U z -.Z.c b s b CXLXKXLXKX} z 7 7 z.hXSXSXz.AXbXmXbX0XJXmXmX` 1 n b iXKXLXLXLXDX@XT.].W.E.(.U.|.^.^.~.~.W.E.~.~.~.~.~.~.U.~.~.~.~.~.~.U.I.I.I.U.~.~.Q.~.~.~.~.E.~.~.~.~.~.~.~.~.^.^.",
"j ~ DX[ W q s q Q I f h Q L R R R R I I L f E I I I I L P d x ` dXV d R R T Y z -.Z.7 0 ` GXLXKXJXLXJX` 7 7 7 t.hXMXmXJXLXJXJXJXJXSXSXmX' n z b 7XKXKXPXKXLXPXBX].T.W./.^.^.U.|.~.~.~.~.~.~.~.^.~.U.U.~.~.~.~.~.~.~.U.U.I.U.~.~.~.~.~.~.E.E.~.~.~.~.~.~.~.~.^.^.",
"r x DXIX~ s $ b L U L L Y Q L I I I T L f L U E T T L k k d x ` dXV x T R U L p 9.Z.7 | KXKXLXLXJXSXZXD v 7 7 D mXMXmXSXZXZXCXZXAXZXdXmXG v n n 7XKXLXKXKXKXLXKXDX[.T.]./.I.}.U.~.~.~.~.~.~.~.U.~.U.U.~.~.~.~.~.~.~.~.~.~.~.~.~.~.~.~.~.~.~.~.U.~.~.~.~.^.^.^.^.",
"r b CXPX X[ iX[ Y U L k P [.~ k U T U L f f f L I U U k f d x ` dXV z T T U L z 9.x.D LXHXJXJXZXqXqXmXD @ 7 7 9 ] mXbXJXKXKXKXLXJXJXMXv.9 7 7 7 } HXKXKXHXLXJXLXKXDX[.T.W.(.~.^.Q.Q.E.E.E.~.U.U.~.U.U.U.~.U.~.~.~.~.~.~.~.~.~.~.~.~.~.~.~.~.~.U.~.~.~.~.~.^.^.^.",
"d x VXKXPXGXCX` P U L Y ~ BX| l k P k k k P w k h L L P j d d ( dXZ z P ! L k z 9.B.mXJXJXSX9Xz.t.D 5 5 5 7 7 9 hXmXv.mXLXKXKXKXJXSXv.mX] v c v D t.xXZXJXJXJXLXPXKXUXT.W.E.~.~.Q.Q.E.E.E.E.Q.Q.~.I.I.~.~.E.E.~.~.~.~.~.~.~.~.~.~.~.~.I.~.~.U.U.~.~.~.~.^.^.^.(.",
"x 8XGXPXHXHXtXb k U U k l CXtXd b ~ | {.j q k f P / h k k d d ( dXF < k ! L k z 7.zXSXJXSXt.] V 3 3 X 5 @ 2 c 7 z.v.bXSXAXKXLXLXZXmXhXMX' 7 n 7 9 3 8 ] qXZXJXLXLXKXPX@X].W.I.^.~.~.~.E.E.~.~.~.E.I.E.~.~.E.E.~.~.~.~.~.~.~.~.~.~.~.~.~.~.U.U.U.~.~.~.~.^.^.(.(.",
"uXGXHXLXJXAX} & W Q g g ~ DXCX` [ VXDX[ s j s y ^ eX~ j j d l ( pXF 7 k ! L L z 7.nXJXAX] D 3 3 3 X ' ] 7 1 = 9 t.SXSXMX9XZXJXJXxXmXSXSXJ v v 7 9 ] 9 9 5 ' 9XxXJXHXGXDX{.'.).~.~.~.E.E.E.E.~.~.~.~.~.~.~.E.I.~.~.~.~.~.~.I.I.~.~.~.~.~.~.~.U.~.U.U.~.~.^.(.(.(.",
"iXFXPXLXLXLXyX( k W k ~ b CXGXFXPXGXtXl l s 0 j ^ DX` d d d x D pXF z P T L P z ' AXAXz.5 X 3 9.] 9 5 v.5 G ` 9 J hXhXhXmX9X' ] qXhXhXMX] 9 D 9 G z.5 ] t.8 8 G wXHXPXIX[.T.W.].~.~.~.~.E.E.~.~.~.~.~.~.~.E.E.~.I.~.~.~.~.I.I.I.~.~.~.~.U.U.U.~.U.U.U.~.^.(.(.(.",
"d n } LXLXCXVX[ W W d ` tXHXAXAXHXIX^ x j l w s ` GX7X7 n } ~ D dXH p k ! P k l ` xX8X2 @ 5 7 gXbXhXhXv.hXmXSXmXMX9.J 5 5 V 9 9 9 V G dXhXSXSXdXhXl.MXMXdXV J V 9 wXLXFXtXR.T.T.W.~.^.~./.E.E.~.~.~.~.~.~.~.~.~.~.~.~.~.~.~.~.~.~.~.~.).~.~.~.^.^.^.^.~.^.(.(.(.",
"d d [ LXVX( ^ ~ k ^ 7XFXLXHXJXHXAX} x l k w l i ` GXCX8XbX Xx v Z.H z k ! P d d i . & @ . 2 7 v z.v.V dXmXdXZ.mXSXSXSXbXt.` 7 D ] bXJXSXSXSXMXSXSXl.hXMXhXmXMXV 5 v xXxX} ^ ! {.W.~.^.U.).E.E.E.~.~.E.E.~.~.~.E.~.~.~.~.~.~.~.~.~.~.).).).).(.(.(.(.(.(.(.(.(.(.",
"f ~ ` PXR.l l j Y ~ { uXFXLXJXHXFXuX~ W f f d a } HXZXxXyXn d n Z.H 7 j P l j r p & o @ @ @ o 7 X 9 D ] V 5 hXbXqXv.] G D ` n ` G ' 0X9XmXmXz.G 9.9.3 8 ] hXgX9XwXv D z > $ l ! W.~.^.U.).E.~.E.~.~.~.~.~.~.~.~.~.~.~.~.~.~.~.^.~.)./.(././.(.(.(.(.(.(.(.(.(.(.",
"g W ^ DX( l l q L W r b ` CXLXLXPXPXR.k k ^ | 8XCXHXbXxX{ < d v Z.J 7 j l d r * . > 2 o . @ 2 = 7 X X 5 D 5 5 5 9 9 9 @ 7 7 2 v 7 7 v 9 v V D G qXgXxXD 3 3 ' z.D 9 2 > 1 # d u Y.W.~.~.).E.W.~.~.~.~.~.~.~.~.~.~.~.~.~.~.~.~.^.~.)././././.(.(.(.(.(.(.(.(.(.(.",
"U U / eXP P l f L L d r b CXPXR. XUXDX/ k ~ ` 7XCXZXZXyXv p k v B.-.o d l i * * & o o 2 @ . & . < & o 7 o 7 2 @ @ @ 7 2 v < z < z 7 2 7 9 7 5 9 ] z.ZXCX` 7 5 X @ @ & . o # % u _ W.E.E.E.E.E././././././.~.~.~.~.~.~.~.~.~.~.~././.(.(./.(.(.(.(.(.(.(.(.(.(.(.",
"f U ~ / L U k f U Y k x d DXVX~ x W {.[ f d 2 7 t.ZXZXxX} x k z x.-.3 d a $ & & . 2 o . @ . # p # , & . > & o z 7 2 2 2 o & < & < . > 7 > 7 7 7 v ' m 7 @ 2 . @ . + . > . > % y _ W.E.E.E.E.~./././.(.(././.~.~.(.(.~./.~.~.~.~.(.(.(.(.(.(.(.(.(.(.(.(.(.(.(.(.",
"I T P P ^ ~ k k L U q l ~ DX{.W W Q Q ~ d * * o { HXxXVXCX^ q z x.>.5 i , # & & > o > . + + > . # . * * . < & . o o 2 7 & 2 2 2 > > < > 2 7 o o @ . @ . o . . + . . # & . . ; u / ].W././././.(././.(.(././././.(.(.(.(.(.(././.(.(.(.(.(.(.(.(.(.(.(.(.(.(.}.}.",
"E Q L P Q Q P f f L k k ^ BXU ~ P W T Y j i * * XFX` b 7XR.l 7 l.>.7 , # # < o o . > . - - - $ # # . & , . & . o o o . . o . o o . . . . . o o o @ . . . . 2 . + . - . > > . w _ ].W./././.~.(./.(.(.(.(././.(.(.(.(.(.(.(.(.(.(./.(.(.).).(.(.(.(.(.(.(.(.(.}.",
"I U U U T Q k h L L h P Q / T L U T T U j 0 0 r 7XuXd r d ^ l < r.0.5 ; - - - & . > # # - + % # . . + . # # . . . . . . . . . . & # . . o o o o . o o . . . . . + . # # . > ; w _ ].]./.E.(.(.}.(.(.(.(./././././.(.(.(.(.(.(.(././././.(.(.(.(._.(.(.(.(.(.(.(.",
"L U U U T Q k q k L k P U Q U U U T T U k q q r X( j d 0 t y 7 r.0.@ ; + - - & . > & . . - - , - + + + . . + > . . . . . . . . . . . . o o o o o o o . . . . . . . % . . . - t / ].].]./.`.(.|.(.(._._.(.(.(.(.(.(.(.(.(.(.(.(.(.(.(.(.(.(.(.(.(.(.(.(.(.(.(.(.",
"L U U T T Q k q k k k P I I I T R R T U L f q f / L W q w s s 2 0.r.X ; % - # # > > . # > + . . . . . @ @ o . o o o o o o o o o o o o o o o X X o o o o o . # # - . # > o . # w ^ ].].'./.`.(.}.(._.`.`.`._._._.`.`.`.`._.(.(.(._._.(.(.(.(.(.(.(.(.(.(.(.(.(.(.",
"L U I T R Q j q k k h L I I R T R R T U L f q f E T U f j j 0 7 0.l.X ; % - # . . # . . . . o @ X X X X X X X 3 3 3 3 3 o o 3 3 o o 3 3 3 3 3 3 3 3 X X o X o o . . . & o o - % ! ].].'./.(.(.}.`.`.`.`.`.`.`.`.`.`.`.`.`.`.`.(.`.`._.(.(.(.(.(.(.(.(.(.(.(.(.(.",
"k U U T R Q k q l k f L T I T T R R T U k f 0 q I R E L q q q 7 >.l.X , % + . . . & = o @ X X X 3 4 N N N V V V V N N N N N N N N N V V F F H H H H F V 8 3 3 3 o . & o . . > % P ].].'./.'._.}.`.`.`.`.`.`.`.`.`.`.`.`.`.`.`.`.`.`.(.(.(._._.`.(.(.(.(.(.(._._.",
"k P U T R Q k q k k k L T I T T R R T U k q 0 q I I T f w j j 6 >.r.3 - . + + . > . X @ X X 3 N F 6.r.y.y.y.y.y.y.r.r.0.6.7.6.7.7.6.6.0.r.y.B.B.y.y.x.x.y.7.V 3 X . & & @ . # % h ].].'.'.'.`.}.`.`.`.`.`.`.`.`.`.`.`.`.`.`.`.`.`.`.`.`.`.`.`.`._._._._.(._._.}.",
"k P U T Q Q k q f l k L T E T T R R R U j 0 0 y k E I L k j q 7 0.<.3 # . + . . o o X X 3 9 ' c.Z.A.aXaXaXaXjXjXjXA.A.A.S.B.S.B.S.S.A.A.G.K.K.G.A.C.C.Z.A.Z.r.' 9 o X o o @ - ; u ].].'.'.'.`.`.`.`.`.`.`.`.`.`.`.`.`.`.`.`.`.`.`.`.`.`.}.}.`.`.`.`.`._._._.}.}.",
"d W U R R Q k q f k g L T I Q R T R R U j 0 0 y k L I I f f f 6 r.>.3 # . . . . X 7 7 8 G t.pXaXaXA.A.fXzXzXzXlXzXjXzXlXzXzXzXzXkXzXkXzXlXlXzXjXK.K.K.A.A.K.Z.c.t.G 5 3 X o . t u '.'.'.'.'.'.`.`.`.`.`.`.`.`.`.`.`.`.`.`.`.`.`.`.`.}.`.}.}.`.`.`.`.`.`.}.}.}.}.",
"` b U Q R Q k q q l L L L T T R T T R U k 0 0 t j U I E L f f 7 r.r.X . . @ o o v ' F H y.Z.fXfXK.jXjXzXzXzXzXlXcXlXzXlXzXzXnXcXzXlXlXlXzXnXnXcXlXjXzXA.jXA.J.B.c.t.-.D 3 X & % K '.'.].'.'.oX`.oX`.`.`.`.`.`.`.`.`.`.`.`.`.`.`.`.`.`.`.`.`.`.`.}.}.`.}.}.}.#X#X",
"xXz W Q I _ ~ y j j f U I I U U U R T U k q 0 t k U I L L f f = 0.l.X @ @ . . 9 ' ' H 0.B.A.fXfXjXjXzXzXzXlXcXcXzXzXlXnXcXzXzXzXcXcXlXcXzXzXzXzXzXcXjXA.G.A.C.B.Z.y.e.-.D o @ % K '.'./.'.'.'.oXoXoX`.`.`.`.`.`.`.`.`.`.`.`.`.`.`.`.`.`.`.`.}.}.}.}.}.}.}.}.}.}.",
"HX7Xx ^ eXBXM $ l x Y U R R I R I I T U k q 0 y k U I I k j j = 0.l.X > o o 7 D ' H 7.y.B.A.fXfXjXzXzXzXzXzXjXjXzXzXlXnXlXzXzXzXjXzXzXsXsXD.B.e.x.x.S.A.B.B.S.Z.Z.c.l.e.' 7 @ , ! [.'.`.'.'.oX#XoXoXoXoXoXoXoXoXoXoXoXoXoX`.`.`.`.}.}.#X#X}.#X#X}.}.}.#X}.#X}.}.",
"AXZX{ CXPX| d 0 ` R.d Y U R I U E L R U k q q y L U U U k h l o r.l.X > . o v D H H r.x.l.B.A.A.B.B.B.e.e.e.B.S.jXzXzXlXzXzXcXzXfXjXjXD.D.a.q.e.r.e.Z.A.jXA.Z.Z.Z.c.e.e.9.G 5 # ^ [.'.`.'.'.'.'.oXoXoXoX#X#XoXoX#XoXoXoXoXoXoXoX#X#X#X#X#X#X#X#X}.}.#X#X#X#X#X#X",
"ZXHXHXGXVXb i i ` FX^ W Y g ~ P k L U I k q q q L U I U k q y @ >.l.X $ & 7 9 F ' 7.r.e.x.Z.A.A.A.V.a.a.a.f.B.A.A.jXzXzXzXzXlXzXzXfXA.D.D.8.*.=.*.6.r.B.fXaXfXc.c.Z.B.9.t.` v 7 ^ @XoX'.'.#X%X}.#XoXoXoX#X#X#XoXoXoXoXoX#X#X#XoX#X#X#X#X#X#X}.}.#X#X}.}.#X#X$X$X",
"HXAXZXFX{ x b # { FXrXx x {.eX^ k L U L k q 0 q f L E E f 0 t @ >.<.X , & 7 G 7.7.9.r.x.Z.Z.S.S.B.e.0.6.6.0.0.0.B.A.jXK.A.jXlXzXjXfXA.g.i.:.%.*.O.Z F Z Z F F F H ' -.7.t.` v c [ @X@X'.oX=X#X#XoXoXoX#X#X#X#XoX#XoXoX#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X;X;X",
"ZXAXZXGX` b & & ` ZXHX} uXGX8Xl k L U L j r r 0 r W Y f q 0 i 5 w.>.5 , . 9 7.9.-.-.9.c.c.x.B.B.x.r.9.7.7.6.r.y.r.B.A.jXG.jXlXlXzXK.B.e.0.=.C N Z &.6.*.&.H N V t.' V F ' { v v [ @XoX'.oX#X`.#X#X#XoX#X#X#X#XoX#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X$X",
"AXJXHXFXIX^ x = ] ZXAXAXCXVXb j k L U Y d i & & x ^ ~ f q q i X 0.<.5 & 7 D 9.9.-.J H ' G V V N 4 8 N N N N V Z $.r.G.lXlXzXlXnXlXK.b.0.C C 6.B.r.y.y.S.r.c.SX9.8 V A D ` ' D D R.eX+X%X$XoXoX$X#X#X#X#X#X#X#XoX&X#X#X#X#X#X#X#X#X#X#X#X%X%X#X#X#X#X#X#X#X#X#X#X",
"gXAXCXFXPXuXz D bXAXSXZXCX{ b k L L Y W r ` n v 7XtXx j w r r 3 0.>.8 2 9 ] y.9.H F 8 D D V N 0.cXaXy.r.r.B.S.*.O.Z <.n.kXkXL.L.F.p.;.0.jXy.9.V N N F F r.r.c.MXD V ' F D ' u.' XR.+X%X$X$XoX#X#X#X#X#X#X#X#X#X#X#X%X&X&X%X#X#X#X#X#X%X%X%X#X#X%X#X#X#X#X#X;X;X",
"z.ZX` ( { eX{ n CXZXZXZXxXm x k L U Y W r ` } z.iX` r w r r 0 3 0.>.8 D G 9.r.-.F V 3 8 3 N r.y.y.7.F V V N &.B.a.w.p.p.F.F.F.b.p.,.,.q.0.6.V H N V V N C $.H C H F V N V H r.9XgXrX[.*X&XOX&X=X%X%X%X#X#X#X#X#X&X&X&X&X&X&X%X%X%X%X%X%X%X%X%X#X#X#X#X%X%X;X=X=X",
"z.xXx ~ f x x z t.ZXAXAXCX} x Y U T U k p v ] ] } z r r 0 r s 6 r.r.8 D 9.r.6.C V V D D 8 7.9.r.' V N N N N Z F 0.;.;.s.n.p.a.p.3.1.p.p.;.Z O.V 4 N N N B o.o.%.0.-.H H &.r.f.6.y.yX@X:X'.oX-X&X&X&X&X&X&X&X&X&X&X&X&X&X&X&X&X&X&X&X&X&X%X%X%X%X#X%X%X%X&X=X=X=X",
"} } k W U k 0 & v CXAXCXGXCX~ f L T U k z D t.] 9 o p d w r d = >.l.N D c.e.0.6.F H V F V H F 6.V N V 4 4 N &.6.C O.%.s.i.F.zXkXF.n.M.s.;.i.8.=.&.O.F O.%.%.;.q.B.e.b.w.;.;.q.o.Z 0XeX:X[.@X-X'.&X&X&X&X&X&X&X&X&X&X&X&X&X&X&X&X&X&X&X&X&X&X&X&X%X=X=X=X=X=X%X%X",
"[ ( Q L I U r * 7 CX8Xv } iXR.j L T U k r ( } t.{ . r r s 0 r 6 >.w.N ' y.$.$.=.6.6.6.r.6.$.O.O.O.C O.O.=.=.=.w.d.n.n.M.:.G.lXlXL.,.H.H.D.d.q.g.a.q.0.,.q.d.s.p.a.b.C.w.<.;.d.s.Z t. X[.:X;X;X=X&X&X&X&X&X&X&X&X=X=X=X=X&X&X&X&X&X&X&X=X=X=X=X=X&X=X=X=X=X;X;X%X",
"~ k L T T U 0 $ b CX` x x x ^ k L L U P d ( ' } 7X` r r s s 0 o 0.w.4 7.6.O.8.8.0.6.B.B.0.;.o.o.o.o.:.s.G.V.s.V.s.H.kXF.%.G.lXlXlXF.H.L.D.s.H.G.kXzXsXD.s.D.F.n.V.V.V.w.<.n.V.a.O.y. X[.:X;X;X2X=X=X=X=X=X=X=X=X=X=X=X=X=X&X&X&X&X&X=X=X=X=X=X=X%X=X=X=X=X=X=X=X",
"f k Y U L k 0 $ b 7Xj W Q L k q L U U Y r p 2 7 [ } d d 0 s t o r.r.4 F &.o.q.8.=.;.a.C.w.w.:.O.O.;.d.sXsXkXH.s.i.L.lXs.q.kXlXzXlXK.a.kXkXV.a.G.L.H.D.M.p.D.F.V.A.A.A.B.q.D.kXV.%.y.eXoX@X*X$X=X=X=X=X=X=X=X=X-X=X=X=X=X=X=X&X&X&X&X=X=X=X=X=X=X=X=X=X=X=X=X=X=X",
"k j L k P P q 0 x ^ Y Q R R L k h U U k j * # * p x k l q 0 t @ >.w.8 V 6.C a.g.q.%.w.n.p.p.d.q.:.i.i.V.s.i.q.d.lXkXH.,.V.lXlXlXzXlXs.G.K.lXkXn.n.i.p.i.p.p.G.C.B.V.B.B.a.q.V.A.=..X@X*XoX-X=X$X=X=X=X=X>X-X-X>X-X-X-X-X-X=X=X&X-X-X-X=X=X=X&X&X=X=X=X=X=X=X=X=X",
"j k L U U k q 0 j j Y U U L L L Y W L k y w ; $ 0 q h k q y y o >.w.4 8 r.O.V.s.8.%.d.s.n.p.n.q.%.i.p.a.a.f.sXlXlXzXF.q.kXlXnXcXnXnXjXB.zXlXlXnXlXkXG.V.V.G.L.F.V.n.b.b.8.o.i.D.r.t.@X+X@X*X-X>X>X-X*X*X*X-X*X*X*X*X-X>X-X*X&X*X-X-X-X-X=X=X&X&X=X=X=X=X=X=X=X=X",
"j k L U U k q 0 j f k Y Y k k L U W L k y u t 0 w j f f k y s 3 0.l.3 V r.=.D.s.:.%.i.i.n.n.V.q.,.s.G.kXlXnXnXcXlXnXb.C.zXlXcXnXnXlXnXS.G.zXlXlXlXlXzXK.K.K.F.V.V.n.,.C.d.o.;.S.c.8XeX:X@X;X-X=X>X-X*X*X*X-X*X*X>X-X-X-X-X*X*X*X-X-X-X-X=X=X=X=X=X=X=X=X=X-X-X=X",
"j k L L P j 0 0 j k L P Y k k L U U L h y 0 r r f f f f k w i 5 >.w.V 9XcXe.V.V.%.q.s.i.p.n.b.w.:.,.L.nXnXnXnXnXnXlXq.jXlXlXcXnXnXlXlXjXq.sXzXzXlXlXK.F.G.F.V.n.V.p.w.C.sX8.q.f.9X8X+X*X*X-X2X-X>X-X-X-X-X-X-X-X>X-X-X-X-X-X*X>X-X-X-X-X-X=X=X=X=X=X-X-X-X-X-X-X",
"h k L L P j 0 0 j k Y U P Y k Y Y U k h y w r r j r j f y t r X C C.c.hXcXA.K.p.w.A.C.q.<.p.a.b.p.i.F.lXcXnXlXzXjXq.q.G.kXlXcXlXnXnXnXzX0.:.s.H.G.G.G.K.kXkXF.n.a.e.b.C.kXD.q.y.0XeX+X*X*X-X-X=X>X>X-X-X-X-X-X-X>X-X-X-X>X-X-X>X-X-X-X-X-X-X-X-X-X-X-X-X-X-X-X-X",
"h k L L k s $ $ y j L Y Y L L Y Y U k h y 0 r r r f k r r d & 9 w.C.c.hXcXA.G.n.C.jXG.q.<.p.w.b.D.i.p.D.kXkXV.i.;.:.D.H.kXlXnXcXcXlXlXlXsXi.8.8.:.;.a.G.G.G.F.a.a.a.l.C.kXs.q.S.8X@X:X>X-X-X>X=X>X-X-X-X-X>X-X-X-X-X*X>X>X-X-X-X-X-X-X-X-X-X-X-X-X-X-X-X-X-X-X-X",
"k k L L l t $ $ y k L Y L L L Y Y P k h y 0 r r r f f s r # 7 t.J.P.c.hXMXfXC.G.C.K.K.a.q.i.w.p.F.M.i.:.%.;.;.:.o.s.kXH.kXzXcXlXlXlXzXzXsXH.8.H.H.H.sXkXD.V.V.a.b.e.B.A.G.q.V.B.8X@X*X-X-X-X2X2X>X>X-X-X>X>X>X-X-X-X-X>X>X-X-X-X-X-X-X-X-X-X-X-X-X-X-X-X-X-X-X-X",
"k L L L l t % $ y k L I P P L L L Y h h y y s q r r r s * & ' hXK.J.x.hXcXx.B.K.C.A.J.b.i.i.p.b.a.F.D.s.V.H.V.i.:.H.D.V.G.sXzXzXjXG.sXV.s.s.:.q.H.kXkXH.D.n.n.V.b.e.B.A.b.V.G.c.eX:X>X-X&X*X*X&X>X>X-X-X>X>X>X>X>X-X>X>X>X-X-X-X-X-X-X-X-X-X-X-X-X-X-X-X-X-X-X-X",
"k k L k j t # ; y k L I L L L k g L f h y y s 0 q r s r * D wXgXJ.P.x.cXMXl.b.C.jXA.C.e.q.i.a.b.p.n.V.H.sXkXV.%.o.;.O.%.q.q.f.B.B.f.8.:.Z O.B O.s.G.H.D.H.V.V.C.B.e.B.0.C.K.s.pXeX&X=X-X,X,X<X*X>X>X-X-X>X>X>X>X>X>X>X>X>X-X-X>X-X-X-X-X-X-X-X-X-X-X-X-X-X-X-X-X",
"f P k f ~ 0 $ ; j w I T L L f L Y P k y y y y % 0 w y i ` 0XgXhXP.P.x.cXhXl.x.A.b.b.%.w.p.i.a.n.p.n.V.D.G.V.:.o.V.q.Z Z C o.$.$.$.O.=.o.%.g.:.B :.s.g.s.V.n.V.C.V.B.e.q.q.w.A.9X+X-X=X>X*X*X*X,X*X>X>X>X>X-X>X>X<X>X>X-X-X*X*X*X-X-X-X-X-X-X-X-X-X-X-X-X-X2X3X3X",
"d j f W s s $ % s k g L L k h g g k f y t t t t w y t | tXwXgXfXP.P.B.cXhXc.x.A.C.B.C.K.V.i.p.n.n.a.F.V.V.i.o.i.G.G.V.V.V.0.o.N C 8.g.V.g.D.i.Z B ;.d.s.s.s.V.C.B.C.kXG.K.C.fX0X+X=X=X-X*X*X*X:X*X>X>X>X>X-X>X>X>X>X>X-X*X*X*X*X-X-X-X-X-X-X-X-X-X-X-X-X-X-X>X>X",
"p d W / d $ i i 0 k P U P k L k g f q q i i $ i $ d 8XiXrXgXgXfXP.P.B.cXhXZ.x.S.jXA.jXkXsXa.p.n.F.n.n.V.i.O.:.D.H.D.H.V.H.H.g.q.a.V.s.V.s.a.V.q.B o.:.,.i.s.V.C.V.C.jXzXC.fXgX8X+X*X-X>X,X*X:X,X*X>X>X>X-X-X>X>X>X-X-X*X*X*X*X*X*X*X-X-X-X-X-X-X-X-X-X-X-X-X-X>X",
"( m X[ d s $ $ l q j k k k h f g k j 0 i , & * x 7XiXtXyXqXgXdXP.P.B.cXhXZ.r.e.jXzXjXlXjXa.b.V.C.n.a.p.%.o.s.D.H.H.G.H.G.G.sXH.sXH.V.V.D.V.H.s.O.B O.;.a.V.s.b.b.A.zXjXK.dX0X8X+X*X-X>X,X:X:X,X*X>X>X>X-X-X-X>X-X-X-X*X*X*X*X*X-X*X-X-X-X-X-X-X-X-X-X-X-X-X-X-X",
"qX0X8Xx r r * $ 0 s j j j f g k g j j r * & 6 D XrXuXyXyXgXgXfXP.P.B.zXhXZ.9.=.A.jXjXzXr.e.V.b.F.b.n.<.o.:.s.D.G.sXsXkXsXkXlXzXzXsXsXD.H.G.G.V.s.O.o.:.i.s.V.C.B.B.zXjXB.c.7XeX+X*X-X>X>X*X*X-X-X>X>X>X-X-X-X>X-X-X*X*X*X*X-X-X-X*X*X*X-X-X-X-X-X-X-X-X-X-X-X-X",
"mXqX} z p z $ * b ` b j l w k k h f s i = 6 A u.rXrXyXxXqXqXxXfXJ.P.B.zXhXZ.-.H >.B.A.0.N -.e.C.C.C.n.:.o.p.d.H.G.sXG.kXsXkXlXzXzXkXsXsXsXG.G.V.V.i.o.d.n.V.F.b.B.*.r.B.e.9XeX@X+X*X-X-X-X*X*X-X-X-X>X>X-X-X-X>X-X-X-X-X-X-X-X-X*X*X*X*X-X-X*X*X-X-X-X-X-X-X-X-X",
"hXqXD z z e * ( R.[ d $ d s q q h h j r 6 c A u..XvXxXyXqXqXxXdXP.P.x.hXzXZ.7.H -.0.0.Z 4 H w.V.V.G.V.,.:.s.s.D.s.a.q.d.i.d.H.H.g.s.i.s.s.a.s.D.s.V.;.s.H.F.G.V.e.C F 6.fXwX+X+X+X+X-X-X-X*X-X-X-X-X>X-X-X*X-X>X>X>X-X-X-X-X>X>X*X*X*X*X*X*X*X*X-X-X-X-X-X-X-X-X",
"bXqX( z = p & ` } p d d 0 s q k q h j $ 6 c A &.y.gXxXxXyXqXgXhXP.P.B.fXzXZ.9.H H =.x.9.V V e.C.C.F.kXs.i.d.s.d.%.o.o.o.o.o.:.:.o.o.o.o.o.o.o.:.q.d.D.F.kXF.n.B.B.C *.c.c.z.eX+X:X:X-X5X-X-X>X>X-X-X>X-X-X-X-X>X>X>X>X-X-X>X>X>X-X*X*X*X*X*X-X*X*X-X-X-X-X-X-X-X",
"ZXxX.Xz z = . ` n x d s l q q h f j s r = A S S r.9XgXyXxXqXgXfXC.F.m.fXfXe.6.H F V D D V N 0.b.V.G.L.L.i.i.:.O.o.%.:.;.8.8.+.+.:.d.H.D.s.D.n.a.i.s.n.L.L.F.S.Z.e.H -.y.qXtX@X,X*X-X5X5X5X>X>X>X>X,X,X,X,X,X-X-X<X>X,X*X>X>X*X*X-X-X-X>X-X-X-X*X*X-X-X-X-X-X-X2X",
"0XyXuX( = p . { ^ * d j j h y s r j s r 6 A S B 6.pXgXyXgXgXgXfXF.F.m.jXZ.0.>.;.Z N 3 9 v V =.b.b.n.G.L.V.p.a.p.s.s.g.H.sXsXH.sXsXH.G.sXH.D.s.n.V.p.F.L.F.n.A.B.9.H 9.c.yXtX+X+X-X2X5X5X5X-X>X>X>X>X,X,X>X>X-X-X>X,X,X*X*X*X*X>X-X-X-X-X-X-X-X-X-X-X-X-X-X-X-X>X",
"` = { } z & < 7X{ * d r q j 0 s s r y r 6 A S B ..g.gXgXgXgXgXfXF.P.B.fXZ.>.;.,.<.Z N N N N F w.p.n.L.F.n.p.M.n.s.n.n.V.H.sXsXH.H.D.H.V.d.p.n.F.F.p.F.L.n.n.B.x.-.H 9.0XtXeX+X<X2X>X>X>X*X,X>X>X>X>X>X>X>X-X-X-X*X*X,X*X*X*X*X,X-X-X-X-X-X-X-X-X-X-X-X-X-X-X-X>X",
"z p r z r * & } Xx d r r s 0 s s s t * 6 N S B B r.gXgXgXgXhXfXP.P.B.J.Z.0.,.<.3.3.<.Z N N Z 0.a.b.V.F.M.3.n.M.s.p.p.p.i.q.8.;.:.:.8.q.p.D.F.V.V.a.M.M.M.a.B.r.J -.t.8X7X@X:X<X2X>X>X,X,X,X>X>X<X>X>X>X>X>X-X-X*X*X*X*X*X*X*X*X-X-X-X-X-X-X-X-X-X-X-X-X-X-X-X-X",
"i d r s r * $ < } b d r d r 0 d y s i & 3 N B B B O.aXgXgXgXhXA.P.P.l.J.Z.0.,.<.3.2.k.k.<.Z N *.w.p.p.F.n.p.n.n.F.D.n.d.,.;.;.;.;.:.;.<.p.n.n.p.V.V.n.n.s.a.y.7.7.9.} 8XeX:X<X-X<X>X>X>X>X>X<X5X<X<X>X>X>X>X>X>X<X>X*X*X*X*X*X*X-X-X-X-X-X-X-X-X-X-X-X-X-X-X-X-X",
"y q j q $ r * * b x d r d q i i t i = 6 N B B B B ..h.aXgXgXhXZ.P.m.b.J.A.>.<.<.3.2.2.2.3.1.B *.q.n.p.p.,.n.D.s.p.s.s.a.V.G.G.G.D.V.V.a.p.p.p.n.C.V.s.D.i.a.y.H 9.t.} eX+X1X<X>X>X>X,X>X>X<X3X5X<X<X<X>X>X>X>X>X<X>X>X*X>X>X>X*X>X-X-X-X-X-X-X-X-X-X-X-X-X-X-X-X",
"l w h l s $ . & $ i s q j q y 0 t * 6 A B B B B B .+.aXaXaXgXZ.P.m.b.K.A.<.,.3.4.2.2.2.4.1...O.d.w.q.a.n.n.D.F.F.D.V.H.zXzXzXzXzXL.kXkXL.L.L.L.C.a.n.d.s.q.B.' } 8XeX+X+X,X,X,X,X,X,X>X2X3X3X3X<X<X<X<X>X<X<X<X<X<X>X>X>X>X>X>X>X>X>X-X-X-X-X-X-X-X-X-X-X-X-X-X",
"y l h s $ $ * & i 0 j f h q t ; , o 4 A S .B B B .. .g.aXaXgXZ.m.P.m.G.C.q.3.4.4.2.2.2.4.4.o.o.s.q.p.p.V.a.n.V.s.D.D.D.L.lXlXlXzXH.kXkXL.L.F.L.B.b.a.;.a.e.c.t.} eXeX+X>X>X,X,X,X,X,X2X2X3X3X3X<X<X<X<X<X<X<X<X>X>X<X>X>X>X>X<X>X>X>X>X-X-X-X-X-X-X-X-X-X-X-X-X",
"y h j s r $ # # i t q y h q t , = c A S . .B B .X...+.aXpXpXx.k.N.m.n.n.,.p.4.4.2.2.2.4.3.Z B e.b.q.w.p.b.p.n.n.M.n.n.F.F.L.kXH.G.kXzXkXL.C.C.e.0.;.q.q.r.y.t. XeX:X1X3X2X1X,X,X,X,X>X2X2X3X3X<X<X<X<X<X<X<X<X>X>X<X<X>X>X>X>X<X>X>X>X>X-X-X-X-X-X-X-X-X-X-X-X",
"y s s s r $ # # $ d l q q q i = c A S ..X.X.B B .X...@.f.S.aXe.k.N.k.p.p.<.3.4.3.2.2.2.3.,.Z F A.b.;.;.w.p.,.p.n.n.n.p.n.n.F.V.V.D.G.A.F.F.n.e.0.$.%.o.%.x.y.D 7X[.+X5X3X3X-X>X,X,X,X,X>X>X<X3X<X<X<X<X<X<X<X<X<X<X<X>X>X>X>X<X<X>X>X>X-X-X-X-X-X-X-X-X-X-X-X-X",
"r s s i r $ # # ; d j q r d = 6 A S S ..X.X. .X. .....B :.S.aXe.k.N.k.<.<.<.3.<.2.2.2.k.<.$.8 gXaXB.r.%.$.%.%.;.a.n.b.a.s.e.e.q.e.g.B.f.a.a.q.=.F C Z C r.Z.t.o ^ R.+X5X3X6X6X2X>X,X,X,X>X>X<X<X<X<X<X<X<X<X<X<X<X<X<X>X>X>X<X<X<X<X>X>X-X-X-X-X-X-X-X-X-X-X-X-X",
"r s s r r $ # - ; i q r r 6 6 A S S ....X.X. . . ..... .o.g.S.b.k.N.k.<.<.1.3.3.k.2.2.3.;.4 9 AXc.S.f.*.Z N Z C =.0.q.0.=.$.&.=.6.6.6.=.=.%.%.o.Z N Z r.B.Z.t.X < M T.:X5X3X3X4X3X<X,X,X,X,X<X<X<X<X<X<X<X<X<X<X<X<X<X>X>X>X>X<X<X<X>X>X-X-X-X-X-X-X-X-X-X-X-X-X",
"s s s r r $ # - ; i r r 6 6 N S S .X.X.X.X. .B . . . .B i.V.b.k.N.k.<.<.:.3.3.3.4.k.<.4 X t.bX9.S.f.e.$.N N N Z F F Z V N V F F V N N Z Z C o.Z Z 0.B.B.Z.t.@ > $ y {.NX3X3X-X3X3X<X,X1X1X1X<X<X<X<X<X<X<X<X<X<X<X<X>X>X>X>X>X<X<X>X>X>X-X-X-X-X-X-X-X-X-X-X-X",
"j j j r 0 $ # ; i p p 6 8 A S .. .X.X.X.X. . .B B B ..B X.:.D.,.k.N.k.1.:.%.p.<.,.<.-.8 X X wXgXH S.f.f.r.C C Z Z Z N 4 4 8 8 4 4 4 4 4 N C $.$.F 6.f.a.Z.Z.t.o . - $ y [ +X1X6X3X4X4X<X1X1X<X>X<X<X<X<X<X<X<X<X<X<X<X<X<X>X>X>X<X<X<X>X>X>X>X-X>X>X>X>X2X2X2X2X",
"j j j q 0 $ # ; , * c c A S .. . .X.X.X.X. .B B B B .. .X.+.D.;.k.N.N.1.1.%.w.%.0.V 3 3 = & xXgXZ A.B.e.r.*.&.&.&.H V N 8 8 3 3 3 4 N V F =.6.*.*.0.e.B.B.pXz.X < # # ; % ^ @X,X>X3X4X3X1X<X>X>X<X<X<X<X<X<X<X<X1X<X<X<X<X<X>X>X<X<X<X<X>X>X>X>X>X>X>X>X2X2X2X2X",
"j j j q 0 $ # - , z ( ) S S ..X. .X.X.X.X.X.B B B S B ..X.X.g.;.m.N.P.1.%.$.r.H 3 3 @ o * . CXmXV C.B.e.e.;.$.6.=.7.F V N 8 8 8 4 N V F &.6.6.*.6.f.e.B.B.mXt.o # , & . - + h _ @X:X1XNX1X<X4X4X<X<X<X<X<X3X<X<X4X4X4X4X1X<X>X>X<X<X<X<X<X<X3X3X>X>X>X>X>X2X2X2X",
"s s d w r $ # . 7 } vXXXS ..X.X.X.X.X.X.X.B B B B B B X.....f.;.m.L.P.-.Z N 3 X o o # # # & CXZXF e.B.e.e.;.;.=.6.6.&.F Z V N 8 N N Z C =.6.*.*.0.f.e.B.hXZX} ; # # & & # # ; u w ! {.+XNX,X,X4X1X<X>X<X4X4X3X>X3X4X4X1X<X<X<X<X<X<X<X3X3X3X3X<X>X*X*X*X-X<X5X5X",
"i r s r s $ . v 8XxXgXy.*...X.X. . .X.X.X. .B B B B X.X. .B y.r.0.>.Z 3 X X . . # # # # > @ CXZXF 0.A.e.0.q.;.=.;.=.O.O.C Z V V F C O.%.6.;.%.;.e.e.B.Z.SXAX| % # # # # # # # # ; t u h _ +XNX+X<X<X<X<X<X<X4X4X4X4X4X4X<X<X<X<X<X<X<X3X3X3X3X<X<X,X,X,X<X5X5X5X",
"i r s s 0 # 2 } yXxX9Xy.g.O.....B ....X.X. .B B B B .X...S F V 3 3 X X X @ & # # # # # & @ CXZXV -.B.C.a.<.;.;.;.*.O.&.F F F H &.O.$.=.8.=.%.0.q.e.B.hXAXAX` # # # # # # # # # ; % t l h u / [.1X1X1X1X1X1X<X<X1X>X>X4X4X<X1X4X<X<X3X3X3X3X3X>X<X<X,X,X<X<X5X5X",
"d r s t % # 7 qXyXgX9Xc.aXr.@...B B ... .B B B B B S B N 4 3 X X o o o . . # - # # # & = X xXHXD F S.V.w.w.;.<.;.=.$.&.F F F =.=.*.*.;.0.;.;.q.e.e.pXAXZXGXn # # # # # # . # # > , i t t y h h ^ T.+X+X+X1X1X<X4X1X4X4X4X>X>X>X<X<X3X3X3X3X3X2X<X<X,X,X<X<X5X5X",
"d r 0 t ; , 7 qXxXgXqX9XgXaX8... . .....S B N 8 8 8 4 3 3 3 . . # # $ ; $ ; ; # # . . & = o tXHXhXV e.V.a.q.<.w.0.6.*.*.&.$.*.8.;.;.;.8.0.;.;.w.b.r.mXHXGXFX= > # # # # # # # # & # # i d i r t h w h {.eXNX1X1X,X,X1X1X1X<X>X4X<X<X3X3X3X3X3X3X<X>X,X,X<X<X5X5X",
"r r y t ; 2 7 9XqXxXqXqXxXcXaXO.S S B N N 4 3 X @ X o o o & % % $ $ $ % % # - . . . o > & o XFXHXt.&.f.a.,.w.<.q.0.=.&.*.*.=.8.;.:.;.0.q.;.;.e.0.pXAXZXPXVXo & # # # # # # # # & & < < , * , a i d y y l / T.+XNX1X1X1X,X,X1X4X<X<X3X3X3X2X2X2X>X>X>X-X5X<X5X5X",
"r s y 0 # o 9 gXgXxXqXqXxXbXcX7.N 8 4 3 X @ . + - . . . . . # # . # > > > + + + . . . & # # | CXHXZXF 0.s.,.<.w.w.;.=.&.=.*.;.8.;.:.;.w.q.;.0.r.r.ZXAXGXGX} = o # # # # # # # # & & & < < < , < , i i i t y h h ^ _ ].+X1XNX1X,X1X<X<X<X3X3X2X>X2X>X-X-X5X5X5X5X",
"s q q 0 , o 9 9XgXgXxXyXxXCXgXD 3 o o @ + + + . - . # - > # , . > . . . @ . . . . . . # # , | IXHXHXt.F e.a.,.<.w.;.=.&.=.%.;.8.;.;.;.q.;.=.=.-.hXAXHXGXGX` o & . # # . . . # # & < < * , < < > < < , , i d s y h l h h ! [ @XNX1X1X1X1X<X<X>X-X3X2X>X-X5X-X5X-X",
"j q s 0 * o 9 9XgXxXxXyXtX X7 o o o . + . . . . & # # - - - - + + . . . o o . . > . # . # # ` IXFXLXZXF =.e.p.<.w.q.=.*.*.o.;.;.;.=.,.q.;.*.F c.ZXJXHXGXFX< & . . . # . . # # # # # * , i i , , < < < p i i i i s d l l y q l ! T.NX1X,X,X1X*X<X>X>X2X2X-X-X5X5X",
"y y r r = o 5 9XqXbXwX7 2 2 . 2 & & & > # # & o & & # + + . . . . . . . o & & & # # , > - # n GXFXHXJXmX&.r.w.p.w.e.=.$.=.C $.$.$.=.%.w.;.C 9.ZXHXHXLXGX7X& $ # . . # . . . # # * , , , i i i i < < < < < p p p a i i d d s s j h ! T.NX1X,X,X*X<X>X2X2X-X-X5X5X",
"0 s r r p & @ qXbXyX7 2 o & > & & = & & & # & # . . . . o o o o . . . # # & * * * * * , # & z FXPXHXLXAXZX6.a.s.e.e.6.*.=.C $.*.$.O.0.0.$.7.AXHXKXPXPXGXD . $ # . . . # # # # # * ; ; ; i i i i , < p p < < < < z < < p a p i r y h L _ @XeX1X,X<X<X3X>X-X-X-X-X",
"i i $ r * & o wXxX` o < . # # # = = & & & & # - . . . . o o o o # # # # $ $ $ * * * * & . & 2 tXFXLXPXLXJXcX6.g.A.e.r.0.%.C *.&.$.&.e.0.H SXHXKXKXLXPXCX2 . # # & # # # . # # - # $ ; ; i t i i i i i i < < < < 7 < < < < < < p d s y h h ^ [.1X:X5X5X5X-X-X-X-X",
"p = * * * & o 0XyXo 7 . $ 1 # > & & & & & > - - . . . . o o o o # # # $ $ $ $ $ $ # * * & & & ` FXGXKXKXLXJXpX6.B.B.b.e.=.$.r.&.&.y.r.H SXLXLXKXKXKXLX X& & & & > # # # # - - - # - ; i i i i i i i i i , , < < < < < < < < p a a x i t y h / NX:X5X5X5X-X-X-X-X",
"v = * i # & = 0X' o . 1 , . & > - - & & & > & - . . . . . . . . # # # # # # # # # # & & , & < < xXFXPXKXKXLXJXc.r.B.A.B.e.0.y.0.r.y.7.AXLXLXKXKXKXKXPXn , > & > > > # # - - > > > , , i i i i i i i i i i , p < p , < , p i , , & & z i t y u [.:X<X5X5X5X-X-X-X",
"{ 7 & , # # = 8Xv o & . - > > . + - & & & & & & # # # # . . . . . o o . . . . + + + + . - # p * t.GXPXKXKXKXJXJXx.e.B.B.x.e.y.e.e.c.AXLXLXKXKXKXKXKXtX# , > > > , ; # # - > > > > > > , , , i i i i i i i i i p i i , i i i t $ 7 o 7 > $ s u _ :X5X5X5X5X-X-X-X",
"yXD & > # # o } o X > > - # o @ + + o o & & & & . # # # # # # . o o o o . . . . + . . . - # , p ` GXGXKXKXLXLXJXAXx.Z.fXMXnXcXcXcX9XAXKXKXKXKXKXKXLX| , , # < & , ; # - - > > > > > > > , , , i i i i i i i i i , i i i * t i i = 2 & > a % y K :X5X5X5X-X-X-X-X",
"xX8X3 o o o 2 n o o > > > > & & > - # & o . & # . . . # # # # . # o & o . . . . . . # # # & , , m IXGXLXKXKXKXKXLXJXHXAXAXZXxXwXD 5 D FXKXKXKXKXLXCX, , 1 > . > > > > > > > , , , , , , , , , , i i i i i i , , , , , , , , , , # > , , > - $ q @X:X5X3X-X>X*X>X",
"xXqXG X o o = z & & o > > & & # & # # . # # # # . . # # # # # . . # # - # # # # # # # # # # & & < tXPXPXLXKXKXKXKX8X` ` n v z < < < & m CXKXKXKXKX} , - > > > > > > > > , , , , , , , i , , , , i i i i i i , , i i i i , , * $ $ ; , , > & # 0 {.:X3X-X>X,X*X<X",
"gXgXt.D X 3 7 & & & o & & & # # & > # # # . . . - - - # # # . . . . # # # # # # # # # # # # # & < } FXPXKXLXKXLX} < . a > & < > & > z > m IXKXLXGXb 1 , > < > > , , , , , , , , , , i i i i , , p i p p p p i i p p i , , * $ $ $ , , , & & - t ! 1X3X*X>X*X>X>X",
"gXgXgXt.3 7 7 & & & o & & & # # # - # # # # # . > > > # . . . # . . # # # # # # . # # # # # # & > ` FXKXKXKXLX{ > & 1 1 < a < & 1 o . . a n GXKX8X< , 1 > < > > , , , , , , , , p p p p i , , , i i i p p p i i i i , , * * * $ $ , * * & & - t h +X*X<X*X*X3X-X",
"hX9XhX9Xv 3 o o o o o & & & & & # # # # # # . . > > # # . . . . . . - - # # # # . # # # # # # & , b CXPXLXKX| # & , , . > , > 1 > 2 < < > & ` GXn , > , > > < > , , , , , , , , a a p p p , , , , i i i i i i i i i , , , , * $ , * * * & # # $ q {.+X<X*X>X3X-X",
"hXpXgX0X' 7 X o o o o & & & & & # # # - > # # . > > # # # # . . . # - # # # . . # # # # # # # # & a R.PXKX| , , , # & & & > & > & & & & . z # ` 1 # 1 > > > 1 > < < , , , , , , p p p p p i , , , i i i i i i i i i i i , , , * * * * * & # # $ q ! 1X:X,X<X-X3X",
"fXpXpXc.t.5 o o o o & # # # & & # . > > > > . . # . . . - # # . . . # # # # # # # # # # # # # & # a ` PX} , # # & . > > . > > # > 2 < & a . , 1 a > z , > , < > , , , , , , , , p p p p i , , , i i i i i i i p i i i , i , , * $ $ * * & # # $ t u [.+X,X<X-X3X",
"hXpXpX9Xt.3 7 . o . # # # # # # # # - > > > . . . . . . - - # . . . # . # # # # . # # # # # > , , # z | & , > . # # > # . # . > & & < > # a , 1 , , , , & 1 > > , , , , , , , , p p p p i , , , , , , , , , , i , * , * , , , , $ $ * & & # # # % u _ +X,X-X3X3X",
"fXpXpX9Xt.9 X o o . . # # # & > # . . . . # # - > # . # # # . . . . . . # # # # # # # # # # # > , , a # - 1 . > 1 > > > < . > m 7 z m , , , , < , , , , , , , > , , , , , , i p p i , i p p , , , , , * * , , i , , , , , , , * $ $ $ # & & & - ; u P +X1X5X3X2X",
"fXpXpX9Xt.5 X o o . # # # # # # # - . . # # > > # . . # # # # . . . . . # # # # # # # # # # # & , , 1 > # # . > # # # 8XtXCXCXCXCXCXiX7Xa > , > , , , , , , , > , > > > , , , , , , , i p p < , , , , * * , , i i , , , , , * * $ $ $ # # # # # # % y [.:X<X3X3X",
"fXdXpX9Xt.3 X o o # # - # # # . # - - - # > > > - # . . . # # # # # . . # # # # # # # # # # # # # ; , , & , 1 & 1 , | CXPXKXKXKXKXKXPXIX| 1 > # > > > , , , , > , , > > & > , , , , , p p p , , , , , * * , , , i , , , * * * $ , , ; - # # # # # % % [ +X1X3X3X",
"fXdXpX9Xt.3 o o o # # # # # # . - - - - # > > > # . . . . # - # # # # # # # # # # & # # # # # # # ; ; , > , 1 # > 1 uXIXKXKXLXKXKXKXPXIX} 1 # , > > > , , , > > , , > > , , , , , , , p p i , , , , , * * * , , , , , * * * * $ , # # # # # # # # # % ^ :X1X2X3X",
"fXdX9X9Xt.X o o o # . . # # # # . . . # # # # # - # . . . # # # # - - - # # # # # # # # # # # # , # # , , > < a < { CXLXKXKXKXKXKXKXPXGX( > , ; , , , , , , , < , , , , , , , , , i p p i , & # , , , * $ $ * , , , * * * * * $ # # # # # $ $ # o . $ P NX1X3X2X",
"fXdXpX9X' X o o # # . . # # # & . . # # # # # # > > # . . # # # - - # - # # # # # # # # # # # # # ; , , a 1 > > n tXFXKXKXKXKXKXKXPXGXtX, , a # , , , , , , , < , , , , , , , , p p p i , * # # , , * * $ $ * , , * * * * * * # # # . # # $ * # o . ; u +X1X3X2X",
"fXpXpX9XG X o o # # # # # # # # . . . . - # # # > > - . # . . . . . # # # # # # # # # # # # # # # , , # , , & m 7XCXLXKXKXKXKXKXKXPXCX` , , 1 , , , 1 1 , , , , , < , , , , i i p p i , * $ & & , , , , * $ * , , , * * * * $ # # # . . # $ * & o . # w [.1X<X3X",
"fXpXpX9XD o o & # # # # # # # # . . # @ > @ - . > > - # # . . . . . # # # # # # # # # # # # # & , # , a a # m 7XCXGXKXKXLXKXKXKXPXCXuX< 1 a , i 1 1 1 1 , > > , 1 1 1 < 1 , p a p p < , $ # & * , , , , , ; , , , , , * & & # # # # . # # $ $ # & & # y {.1X<X3X",
"Z.pXpX9XD @ & . > & # . . # # # . . > . 1 @ . @ > . . > . . . . . 2 & . # # > . > > & # # # # & , # a , a , 7XCXGXKXLXKXKXKXKXLXCXVX( z a 1 , i a 1 , , > > > > 1 - 1 , , 1 , , , & = o , # * = < & , $ , , ; , , - ; & & & # # $ # & # # # $ $ ; # $ t [ NX4X3X",
"aXpXpX9X9 X & # & & & o . . # # > . . 1 . . 1 . @ @ o 2 . o > > . . . & , < . 1 & & & # # # # # # , , , i M R.CXFXKXKXKXKXKXPXIXrX} z < < a , 1 1 1 , < , > > > < > < a 1 , a < < = = = . & = 6 7 & a # - % ; , , # # # & & & - # # # & # # $ $ $ $ $ 0 _ NX4X3X",
"fXZ.9X9X5 X & # # # & o . . # # . o 8X8X8X} } | ` ` n m 2 > o . 2 & > & . < . # # & # # # # # # # 1 # , , a ` iXIXPXLXKXKXLXCX7X} n , , , a , , < < < < < > > , , a < < , , < = 6 6 6 c 6 c 6 6 6 6 , ; t : & & = * & & & & . # # # # & # # $ $ $ ; * 0 ^ NX4X4X",
"fXZ.9X9XX o & # # . o o # . # # 7 . 8XxXKXKXLX7X7X7XrXuXiXxXtXrX8X X{ ` & . 1 > # & # # # # # # # , # , , , a 7XVXHXKXLXLXFXrX{ ` i i a i a i , i < p < < , , , * x p p p < 1 ` D ) ) ) ) ) ) ) ) u.2 + + ; o = & & o > * # # - # # # o # # $ $ $ ; * 0 ! NX3X4X",
"dXZ.9Xz.X o & # . # o o & # # # . z 8XCXLXKXCXt.8X7X7XrXtXiXxXxXiXiXtXtX{ > . . & & > # # # # # & # , a i i , | iXFXLXHXZX7X} } n t i i a $ i p i p p p p , , , d r * < z & o 8X5.5.5.5.5.5.5.5.5.vX7 + ; , = = 2 > @ > $ % . . # # # & # # $ $ # ; # $ l +X<X3X",
"pXpXpXt.X o o # # . o o . # # # . v tXxXIXFXCXrX8XtXuXiXiXxXiXxXiXuXtXuX7X` > & & & & & # # - > , # , , i a , m uXVXZXFXtX X8X8Xi t i i a * p p p p p i i 1 , , ; i x = < < 6 0X5.5.5.5.5.5.5.5.5.vXv @ = = 8 3 3 3 X 2 , % # . # # # & & & ; ; ; - , $ h [.1X4X",
"pXpXpX' o o . # & & o o . . # # < D yXCXCXVXFX7X8XuXVXCXCXCXVXCXCXVXxXiXVX7X` . & & & & # & > > , , , , , i a & 8XCXFXxX8XrXVX` , i p , p p p p i a i i , 1 , , 1 > 6 7 { rXu.u.u.) .Xu.u.u.XXu..X7X' t.u.7.O.@.O.7.9.] ( r o o # # & & > > ; ; ; , < , y {.1X4X",
"pXpX9XD o o . & > o o o . . # # & ` XuXFXFX7Xt.8XrXrXrXtXuXuXiXiXuXyXtXuXtX7Xz & & & & # # > > # , # a i * a < ` VXxXrX8X8X7X= < * < * * p , , i a i 1 , , , > @ 5 A ' yXPXvXu..X} uX X.Xu.vX{ BX} ] cXXXh.+.j.+.h.pX9X' = o = & - & > > > ; ; - # , $ y [ 1X4X",
"dX9X9X9 o > # # . . o o . . # # & n { } } } X7X7X7X7XrXtXtXuXuXtXtXtXtXrXrX7Xz # & # & # # # # > , ; ; , , , < & uXyX} ` 8Xv & < * * * * , , , i , , , < < < 7 4 V O.u.vXPXvXu..X{ VX X7Xt.vX} BXR.9.aXg.h.j.#.#.h.g.pX' z & > & > > > > $ ; ; ; > , $ y / 1X4X",
"dXpX9X9 @ > . # # . o o o o & > . & & # . . < z m n n ` ` { } } 8X7X8X7X7X7X7Xz # # # # # # # # > > , , , * , * < ' 8X` D { & = , , , , , , , , ; ; , < = 6 6 8 Z *.8.8.5.h.5.u. XrX8X X X7Xu.t.8X} ' 8.5.j.#.#.#.#.+.r.A & . > # > > > , ; ; ; ; > > , y ^ NXNX",
"pXpXz.5 @ > . - . . o o o . # # # # . # & & # # & & & # . . # & & & 2 z m n n & # . # # # # # > > & # # $ * * & = 7 ` D ( c & & # - - - , , 1 1 1 1 , 2 6 c A F 0.G.G.D.+.j.5.XXrXVX` 7XiXGX( { uXrXu.5.j.#.#.#.#.#.+.h.c * # > # & > > , , ; ; ; - > i t h NX4X",
"pXpXt.X @ > . > . . . o . . # # . . . # # - # # # # # # # # # & & . o & . . # & # # . # # # # & > & # # $ $ # & & & 7 ( D . < & # - - - - ; 1 1 1 1 , = 6 c D F e.K.A.B.+.+.5.u.rXVX| 7XrXCX' t.uX.Xu.5.j.+.+.+.+.+.+.y.v ; . . # , > > , ; ; ; ; > > i t u NX4X",
"pX9X] X o & . # # . o o o . # # - > > . . . . # . . # # # # # # o o 2 . # < & . # # # # # # # # & & # # # # # # & & > m < > # , - - - - - - , , 1 , > 2 7 c A A F H F D A A A A ( ( a M b ` c v n m c 4 N N 4 4 N 4 N N O . - , ; , > & & $ ; ; ; , , , t u NX1X",
"pX9X' X o & . . . . o o o o . # # + - > > > > # # > > > > > > & > & < . . & . # # # # # # # # # & & & # # # # - > & > & . < # - - - - - > , < 1 , ; - > < 7 6 6 6 7 o 7 6 6 7 z z < i a z < , 1 < < 6 c o 2 @ @ 7 3 3 X = . - - ; , * & * $ ; ; ; , , * t u @X1X",
"pX9XD X o . . . . . . . . . . . > . . . . . # & # & # # . . . . . . > & , . . > # # # # # # # # & # # # # - - , & , > > # - , # - - , * < < p < , ; ; - , > < < p < z z a < = 7 z z a , p x d - , < < z < , , p = < z = = o . # , * * * * $ ; ; ; - , # t u {.NX",
"pXc.D X o . # # . . o o o o # & # . @ > - > & & & & & & & & & & > . > . > # , # # # # . # # # # & . # # # ; ; , , . # > . - , , , , , < z 6 = * # ; : : : , 1 1 i i p p ; a 7 7 < < < z z = i i , 1 < & , p i r r r * * o & & > $ * , , * , ; ; ; # , # i q _ NX"
};

11174
Tests/images/hopper_rgb.xpm Normal file

File diff suppressed because it is too large Load Diff

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

BIN
Tests/images/op_index.qoi Normal file

Binary file not shown.

BIN
Tests/images/p_4_planes.pcx Normal file

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.9 KiB

Binary file not shown.

View File

@ -14,3 +14,23 @@
fun:_TIFFReadEncodedTileAndAllocBuffer
...
}
{
<python_alloc_possible_leak>
Memcheck:Leak
match-leak-kinds: all
fun:malloc
fun:_PyMem_RawMalloc
fun:PyObject_Malloc
...
}
{
<python_realloc_possible_leak>
Memcheck:Leak
match-leak-kinds: all
fun:malloc
fun:_PyMem_RawRealloc
fun:PyMem_Realloc
...
}

View File

@ -10,8 +10,9 @@ import pytest
from PIL import Image, features
from Tests.helper import skip_unless_feature
if sys.platform.startswith("win32"):
pytest.skip("Fuzzer is linux only", allow_module_level=True)
if sys.platform.startswith("win32") or sys.platform == "ios":
pytest.skip("Fuzzer doesn't run on Windows or iOS", allow_module_level=True)
libjpeg_turbo_version = features.version("libjpeg_turbo")
if libjpeg_turbo_version is not None:
version = packaging.version.parse(libjpeg_turbo_version)

View File

@ -188,5 +188,5 @@ class TestEnvVars:
),
)
def test_warnings(self, var: dict[str, str]) -> None:
with pytest.warns(UserWarning):
with pytest.warns(UserWarning, match=list(var)[0]):
Image._apply_env_variables(var)

View File

@ -9,9 +9,9 @@ from PIL import _deprecate
"version, expected",
[
(
12,
"Old thing is deprecated and will be removed in Pillow 12 "
r"\(2025-10-15\)\. Use new thing instead\.",
13,
"Old thing is deprecated and will be removed in Pillow 13 "
r"\(2026-10-15\)\. Use new thing instead\.",
),
(
None,
@ -47,25 +47,24 @@ def test_unknown_version() -> None:
],
)
def test_old_version(deprecated: str, plural: bool, expected: str) -> None:
expected = r""
with pytest.raises(RuntimeError, match=expected):
_deprecate.deprecate(deprecated, 1, plural=plural)
def test_plural() -> None:
expected = (
r"Old things are deprecated and will be removed in Pillow 12 \(2025-10-15\)\. "
r"Old things are deprecated and will be removed in Pillow 13 \(2026-10-15\)\. "
r"Use new thing instead\."
)
with pytest.warns(DeprecationWarning, match=expected):
_deprecate.deprecate("Old things", 12, "new thing", plural=True)
_deprecate.deprecate("Old things", 13, "new thing", plural=True)
def test_replacement_and_action() -> None:
expected = "Use only one of 'replacement' and 'action'"
with pytest.raises(ValueError, match=expected):
_deprecate.deprecate(
"Old thing", 12, replacement="new thing", action="Upgrade to new thing"
"Old thing", 13, replacement="new thing", action="Upgrade to new thing"
)
@ -78,16 +77,16 @@ def test_replacement_and_action() -> None:
)
def test_action(action: str) -> None:
expected = (
r"Old thing is deprecated and will be removed in Pillow 12 \(2025-10-15\)\. "
r"Old thing is deprecated and will be removed in Pillow 13 \(2026-10-15\)\. "
r"Upgrade to new thing\."
)
with pytest.warns(DeprecationWarning, match=expected):
_deprecate.deprecate("Old thing", 12, action=action)
_deprecate.deprecate("Old thing", 13, action=action)
def test_no_replacement_or_action() -> None:
expected = (
r"Old thing is deprecated and will be removed in Pillow 12 \(2025-10-15\)"
r"Old thing is deprecated and will be removed in Pillow 13 \(2026-10-15\)"
)
with pytest.warns(DeprecationWarning, match=expected):
_deprecate.deprecate("Old thing", 12)
_deprecate.deprecate("Old thing", 13)

View File

@ -19,7 +19,7 @@ def test_check() -> None:
assert features.check_codec(codec) == features.check(codec)
for feature in features.features:
if "webp" in feature:
with pytest.warns(DeprecationWarning):
with pytest.warns(DeprecationWarning, match="webp"):
assert features.check_feature(feature) == features.check(feature)
else:
assert features.check_feature(feature) == features.check(feature)
@ -49,27 +49,12 @@ def test_version() -> None:
test(codec, features.version_codec)
for feature in features.features:
if "webp" in feature:
with pytest.warns(DeprecationWarning):
with pytest.warns(DeprecationWarning, match="webp"):
test(feature, features.version_feature)
else:
test(feature, features.version_feature)
def test_webp_transparency() -> None:
with pytest.warns(DeprecationWarning):
assert (features.check("transp_webp") or False) == features.check_module("webp")
def test_webp_mux() -> None:
with pytest.warns(DeprecationWarning):
assert (features.check("webp_mux") or False) == features.check_module("webp")
def test_webp_anim() -> None:
with pytest.warns(DeprecationWarning):
assert (features.check("webp_anim") or False) == features.check_module("webp")
@skip_unless_feature("libjpeg_turbo")
def test_libjpeg_turbo_version() -> None:
version = features.version("libjpeg_turbo")
@ -95,10 +80,9 @@ def test_check_codecs(feature: str) -> None:
def test_check_warns_on_nonexistent() -> None:
with pytest.warns(UserWarning) as cm:
with pytest.warns(UserWarning, match="Unknown feature 'typo'."):
has_feature = features.check("typo")
assert has_feature is False
assert str(cm[-1].message) == "Unknown feature 'typo'."
def test_supported_modules() -> None:

View File

@ -303,11 +303,11 @@ def test_apng_chunk_errors() -> None:
assert isinstance(im, PngImagePlugin.PngImageFile)
assert not im.is_animated
with pytest.warns(UserWarning):
with Image.open("Tests/images/apng/chunk_multi_actl.png") as im:
im.load()
assert isinstance(im, PngImagePlugin.PngImageFile)
assert not im.is_animated
with pytest.warns(UserWarning, match="Invalid APNG"):
im = Image.open("Tests/images/apng/chunk_multi_actl.png")
assert isinstance(im, PngImagePlugin.PngImageFile)
assert not im.is_animated
im.close()
with Image.open("Tests/images/apng/chunk_actl_after_idat.png") as im:
assert isinstance(im, PngImagePlugin.PngImageFile)
@ -330,18 +330,20 @@ def test_apng_chunk_errors() -> None:
def test_apng_syntax_errors() -> None:
with pytest.warns(UserWarning):
with Image.open("Tests/images/apng/syntax_num_frames_zero.png") as im:
assert isinstance(im, PngImagePlugin.PngImageFile)
assert not im.is_animated
with pytest.raises(OSError):
im.load()
with pytest.warns(UserWarning, match="Invalid APNG"):
im = Image.open("Tests/images/apng/syntax_num_frames_zero.png")
assert isinstance(im, PngImagePlugin.PngImageFile)
assert not im.is_animated
with pytest.raises(OSError):
im.load()
im.close()
with pytest.warns(UserWarning):
with Image.open("Tests/images/apng/syntax_num_frames_zero_default.png") as im:
assert isinstance(im, PngImagePlugin.PngImageFile)
assert not im.is_animated
im.load()
with pytest.warns(UserWarning, match="Invalid APNG"):
im = Image.open("Tests/images/apng/syntax_num_frames_zero_default.png")
assert isinstance(im, PngImagePlugin.PngImageFile)
assert not im.is_animated
im.load()
im.close()
# we can handle this case gracefully
with Image.open("Tests/images/apng/syntax_num_frames_low.png") as im:
@ -354,11 +356,12 @@ def test_apng_syntax_errors() -> None:
im.seek(im.n_frames - 1)
im.load()
with pytest.warns(UserWarning):
with Image.open("Tests/images/apng/syntax_num_frames_invalid.png") as im:
assert isinstance(im, PngImagePlugin.PngImageFile)
assert not im.is_animated
im.load()
with pytest.warns(UserWarning, match="Invalid APNG"):
im = Image.open("Tests/images/apng/syntax_num_frames_invalid.png")
assert isinstance(im, PngImagePlugin.PngImageFile)
assert not im.is_animated
im.load()
im.close()
@pytest.mark.parametrize(

View File

@ -77,8 +77,8 @@ class TestUnsupportedAvif:
def test_unsupported(self, monkeypatch: pytest.MonkeyPatch) -> None:
monkeypatch.setattr(AvifImagePlugin, "SUPPORTED", False)
with pytest.warns(UserWarning):
with pytest.raises(UnidentifiedImageError):
with pytest.raises(UnidentifiedImageError):
with pytest.warns(UserWarning, match="AVIF support not installed"):
with Image.open(TEST_AVIF_FILE):
pass
@ -233,7 +233,7 @@ class TestFileAvif:
with Image.open(out_gif) as reread:
reread_value = reread.convert("RGB").getpixel((1, 1))
difference = sum([abs(original_value[i] - reread_value[i]) for i in range(3)])
assert difference <= 3
assert difference <= 6
def test_save_single_frame(self, tmp_path: Path) -> None:
temp_file = tmp_path / "temp.avif"
@ -254,7 +254,9 @@ class TestFileAvif:
assert_image(im, "RGBA", (64, 64))
# image has 876 transparent pixels
assert im.getchannel("A").getcolors()[0] == (876, 0)
colors = im.getchannel("A").getcolors()
assert colors is not None
assert colors[0] == (876, 0)
def test_save_transparent(self, tmp_path: Path) -> None:
im = Image.new("RGBA", (10, 10), (0, 0, 0, 0))

View File

@ -7,9 +7,8 @@ import pytest
from PIL import BlpImagePlugin, Image
from .helper import (
assert_image_equal,
assert_image_equal_tofile,
assert_image_similar,
assert_image_similar_tofile,
hopper,
)
@ -52,18 +51,16 @@ def test_save(tmp_path: Path) -> None:
im = hopper("P")
im.save(f, blp_version=version)
with Image.open(f) as reloaded:
assert_image_equal(im.convert("RGB"), reloaded)
assert_image_equal_tofile(im.convert("RGB"), f)
with Image.open("Tests/images/transparent.png") as im:
f = tmp_path / "temp.blp"
im.convert("P").save(f, blp_version=version)
with Image.open(f) as reloaded:
assert_image_similar(im, reloaded, 8)
assert_image_similar_tofile(im, f, 8)
im = hopper()
with pytest.raises(ValueError):
with pytest.raises(ValueError, match="Unsupported BLP image mode"):
im.save(f)

View File

@ -190,9 +190,9 @@ def test_rle8() -> None:
# Signal end of bitmap before the image is finished
with open("Tests/images/bmp/g/pal8rle.bmp", "rb") as fp:
data = fp.read(1063) + b"\x01"
with Image.open(io.BytesIO(data)) as im:
with pytest.raises(ValueError):
im.load()
with Image.open(io.BytesIO(data)) as im:
with pytest.raises(ValueError):
im.load()
def test_rle4() -> None:
@ -214,9 +214,9 @@ def test_rle4() -> None:
def test_rle8_eof(file_name: str, length: int) -> None:
with open(file_name, "rb") as fp:
data = fp.read(length)
with Image.open(io.BytesIO(data)) as im:
with pytest.raises(ValueError):
im.load()
with Image.open(io.BytesIO(data)) as im:
with pytest.raises(ValueError):
im.load()
def test_offset() -> None:

View File

@ -380,21 +380,28 @@ def test_palette() -> None:
assert_image_equal_tofile(im, "Tests/images/transparent.gif")
def test_unsupported_header_size() -> None:
with pytest.raises(OSError, match="Unsupported header size 0"):
with Image.open(BytesIO(b"DDS " + b"\x00" * 4)):
pass
def test_unsupported_bitcount() -> None:
with pytest.raises(OSError):
with pytest.raises(OSError, match="Unsupported bitcount 24 for 131072"):
with Image.open("Tests/images/unsupported_bitcount.dds"):
pass
@pytest.mark.parametrize(
"test_file",
"test_file, message",
(
"Tests/images/unimplemented_dxgi_format.dds",
"Tests/images/unimplemented_pfflags.dds",
("Tests/images/unimplemented_dxgi_format.dds", "Unimplemented DXGI format 93"),
("Tests/images/unimplemented_pixel_format.dds", "Unimplemented pixel format 0"),
("Tests/images/unimplemented_pfflags.dds", "Unknown pixel format flags 8"),
),
)
def test_not_implemented(test_file: str) -> None:
with pytest.raises(NotImplementedError):
def test_not_implemented(test_file: str, message: str) -> None:
with pytest.raises(NotImplementedError, match=message):
with Image.open(test_file):
pass
@ -511,3 +518,20 @@ def test_save_dx10_bc5(tmp_path: Path) -> None:
im = hopper("L")
with pytest.raises(OSError, match="only RGB mode can be written as BC5"):
im.save(out, pixel_format="BC5")
@pytest.mark.parametrize(
"pixel_format, mode",
(
("DXT1", "RGBA"),
("DXT3", "RGBA"),
("DXT5", "RGBA"),
("BC2", "RGBA"),
("BC3", "RGBA"),
("BC5", "RGB"),
),
)
def test_save_large_file(tmp_path: Path, pixel_format: str, mode: str) -> None:
im = hopper(mode).resize((440, 440))
# should not error in valgrind
im.save(tmp_path / "img.dds", pixel_format=pixel_format)

View File

@ -15,6 +15,7 @@ from .helper import (
is_win32,
mark_if_feature_version,
skip_unless_feature,
timeout_unless_slower_valgrind,
)
HAS_GHOSTSCRIPT = EpsImagePlugin.has_ghostscript()
@ -398,7 +399,7 @@ def test_emptyline() -> None:
assert image.format == "EPS"
@pytest.mark.timeout(timeout=5)
@timeout_unless_slower_valgrind(5)
@pytest.mark.parametrize(
"test_file",
["Tests/images/eps/timeout-d675703545fee17acab56e5fec644c19979175de.eps"],

View File

@ -7,7 +7,12 @@ import pytest
from PIL import FliImagePlugin, Image, ImageFile
from .helper import assert_image_equal, assert_image_equal_tofile, is_pypy
from .helper import (
assert_image_equal,
assert_image_equal_tofile,
is_pypy,
timeout_unless_slower_valgrind,
)
# created as an export of a palette image from Gimp2.6
# save as...-> hopper.fli, default options.
@ -189,7 +194,7 @@ def test_seek() -> None:
"Tests/images/timeout-bff0a9dc7243a8e6ede2408d2ffa6a9964698b87.fli",
],
)
@pytest.mark.timeout(timeout=3)
@timeout_unless_slower_valgrind(3)
def test_timeouts(test_file: str) -> None:
with open(test_file, "rb") as f:
with Image.open(f) as im:

View File

@ -100,6 +100,18 @@ def test_l_mode_after_rgb() -> None:
assert im.mode == "RGB"
def test_l_mode_transparency_after_rgb() -> None:
with Image.open("Tests/images/no_palette_with_transparency_after_rgb.gif") as im:
expected = im.convert("RGB")
d = ImageDraw.Draw(expected)
d.rectangle([(0, 0), (64, 128)], fill="#000")
im.seek(1)
assert im.mode == "RGB"
assert_image_equal(im, expected)
def test_palette_not_needed_for_second_frame() -> None:
with Image.open("Tests/images/palette_not_needed_for_second_frame.gif") as im:
im.seek(1)
@ -224,6 +236,7 @@ def test_optimize_if_palette_can_be_reduced_by_half() -> None:
out = BytesIO()
im.save(out, "GIF", optimize=optimize)
with Image.open(out) as reloaded:
assert reloaded.palette is not None
assert len(reloaded.palette.palette) // 3 == colors
@ -540,7 +553,9 @@ def test_dispose_background_transparency() -> None:
img.seek(2)
px = img.load()
assert px is not None
assert px[35, 30][3] == 0
value = px[35, 30]
assert isinstance(value, tuple)
assert value[3] == 0
@pytest.mark.parametrize(
@ -1229,7 +1244,9 @@ def test_removed_transparency(tmp_path: Path) -> None:
im.putpixel((x, 0), (x, 0, 0))
im.info["transparency"] = (255, 255, 255)
with pytest.warns(UserWarning):
with pytest.warns(
UserWarning, match="Couldn't allocate palette entry for transparency"
):
im.save(out)
with Image.open(out) as reloaded:
@ -1251,7 +1268,7 @@ def test_rgb_transparency(tmp_path: Path) -> None:
im = Image.new("RGB", (1, 1))
im.info["transparency"] = b""
ims = [Image.new("RGB", (1, 1))]
with pytest.warns(UserWarning):
with pytest.warns(UserWarning, match="should be converted to RGBA images"):
im.save(out, save_all=True, append_images=ims)
with Image.open(out) as reloaded:
@ -1359,6 +1376,7 @@ def test_palette_save_all_P(tmp_path: Path) -> None:
# Assert that the frames are correct, and each frame has the same palette
assert_image_equal(im.convert("RGB"), frames[0].convert("RGB"))
assert im.palette is not None
assert im.global_palette is not None
assert im.palette.palette == im.global_palette.palette
im.seek(1)
@ -1422,7 +1440,9 @@ def test_getdata(monkeypatch: pytest.MonkeyPatch) -> None:
def test_lzw_bits() -> None:
# see https://github.com/python-pillow/Pillow/issues/2811
with Image.open("Tests/images/issue_2811.gif") as im:
assert im.tile[0][3][0] == 11 # LZW bits
args = im.tile[0][3]
assert isinstance(args, tuple)
assert args[0] == 11 # LZW bits
# codec error prepatch
im.load()
@ -1477,7 +1497,11 @@ def test_saving_rgba(tmp_path: Path) -> None:
with Image.open(out) as reloaded:
reloaded_rgba = reloaded.convert("RGBA")
assert reloaded_rgba.load()[0, 0][3] == 0
px = reloaded_rgba.load()
assert px is not None
value = px[0, 0]
assert isinstance(value, tuple)
assert value[3] == 0
@pytest.mark.parametrize("params", ({}, {"disposal": 2, "optimize": False}))

View File

@ -93,19 +93,11 @@ def test_sizes() -> None:
with Image.open(TEST_FILE) as im:
assert isinstance(im, IcnsImagePlugin.IcnsImageFile)
for w, h, r in im.info["sizes"]:
wr = w * r
hr = h * r
with pytest.warns(DeprecationWarning):
im.size = (w, h, r)
im.load()
assert im.mode == "RGBA"
assert im.size == (wr, hr)
# Test using load() with scale
im.size = (w, h)
im.load(scale=r)
assert im.mode == "RGBA"
assert im.size == (wr, hr)
assert im.size == (w * r, h * r)
# Check that we cannot load an incorrect size
with pytest.raises(ValueError):

View File

@ -99,6 +99,7 @@ def test_getpixel(tmp_path: Path) -> None:
reloaded.load()
reloaded.size = (32, 32)
assert reloaded.load() is not None
assert reloaded.getpixel((0, 0)) == (18, 20, 62)
@ -233,7 +234,7 @@ def test_save_append_images(tmp_path: Path) -> None:
def test_unexpected_size() -> None:
# This image has been manually hexedited to state that it is 16x32
# while the image within is still 16x16
with pytest.warns(UserWarning):
with pytest.warns(UserWarning, match="Image was not the expected size"):
with Image.open("Tests/images/hopper_unexpected.ico") as im:
assert im.size == (16, 16)

View File

@ -1,11 +1,8 @@
from __future__ import annotations
import sys
from io import BytesIO, StringIO
from io import BytesIO
import pytest
from PIL import Image, IptcImagePlugin
from PIL import Image, IptcImagePlugin, TiffImagePlugin, TiffTags
from .helper import assert_image_equal, hopper
@ -23,6 +20,9 @@ def test_open() -> None:
assert im.tile == [("iptc", (0, 0, 1, 1), 25, "raw")]
assert_image_equal(im, expected)
with Image.open(f) as im:
assert im.load() is not None
def test_getiptcinfo_jpg_none() -> None:
# Arrange
@ -75,13 +75,19 @@ def test_getiptcinfo_zero_padding() -> None:
def test_getiptcinfo_tiff() -> None:
# Arrange
expected = {(1, 90): b"\x1b%G", (2, 0): b"\xcf\xc0"}
with Image.open("Tests/images/hopper.Lab.tif") as im:
# Act
iptc = IptcImagePlugin.getiptcinfo(im)
# Assert
assert iptc == {(1, 90): b"\x1b%G", (2, 0): b"\xcf\xc0"}
assert iptc == expected
# Test with LONG tag type
with Image.open("Tests/images/hopper.Lab.tif") as im:
im.tag_v2.tagtype[TiffImagePlugin.IPTC_NAA_CHUNK] = TiffTags.LONG
iptc = IptcImagePlugin.getiptcinfo(im)
assert iptc == expected
def test_getiptcinfo_tiff_none() -> None:
@ -92,35 +98,3 @@ def test_getiptcinfo_tiff_none() -> None:
# Assert
assert iptc is None
def test_i() -> None:
# Arrange
c = b"a"
# Act
with pytest.warns(DeprecationWarning):
ret = IptcImagePlugin.i(c)
# Assert
assert ret == 97
def test_dump(monkeypatch: pytest.MonkeyPatch) -> None:
# Arrange
c = b"abc"
# Temporarily redirect stdout
mystdout = StringIO()
monkeypatch.setattr(sys, "stdout", mystdout)
# Act
with pytest.warns(DeprecationWarning):
IptcImagePlugin.dump(c)
# Assert
assert mystdout.getvalue() == "61 62 63 \n"
def test_pad_deprecation() -> None:
with pytest.warns(DeprecationWarning):
assert IptcImagePlugin.PAD == b"\0\0\0\0"

View File

@ -26,12 +26,12 @@ from .helper import (
assert_image_equal_tofile,
assert_image_similar,
assert_image_similar_tofile,
cjpeg_available,
djpeg_available,
hopper,
is_win32,
mark_if_feature_version,
skip_unless_feature,
timeout_unless_slower_valgrind,
)
ElementTree: ModuleType | None
@ -129,30 +129,26 @@ class TestFileJpeg:
def test_cmyk(self) -> None:
# Test CMYK handling. Thanks to Tim and Charlie for test data,
# Michael for getting me to look one more time.
f = "Tests/images/pil_sample_cmyk.jpg"
with Image.open(f) as im:
# the source image has red pixels in the upper left corner.
c, m, y, k = (x / 255.0 for x in im.getpixel((0, 0)))
def check(im: ImageFile.ImageFile) -> None:
cmyk = im.getpixel((0, 0))
assert isinstance(cmyk, tuple)
c, m, y, k = (x / 255.0 for x in cmyk)
assert c == 0.0
assert m > 0.8
assert y > 0.8
assert k == 0.0
# the opposite corner is black
c, m, y, k = (
x / 255.0 for x in im.getpixel((im.size[0] - 1, im.size[1] - 1))
)
cmyk = im.getpixel((im.size[0] - 1, im.size[1] - 1))
assert isinstance(cmyk, tuple)
k = cmyk[3] / 255.0
assert k > 0.9
with Image.open("Tests/images/pil_sample_cmyk.jpg") as im:
# the source image has red pixels in the upper left corner.
check(im)
# roundtrip, and check again
im = self.roundtrip(im)
c, m, y, k = (x / 255.0 for x in im.getpixel((0, 0)))
assert c == 0.0
assert m > 0.8
assert y > 0.8
assert k == 0.0
c, m, y, k = (
x / 255.0 for x in im.getpixel((im.size[0] - 1, im.size[1] - 1))
)
assert k > 0.9
check(self.roundtrip(im))
def test_rgb(self) -> None:
def getchannels(im: JpegImagePlugin.JpegImageFile) -> tuple[int, ...]:
@ -614,6 +610,24 @@ class TestFileJpeg:
None
)
]
for quality in range(101):
qtable_from_qtable_quality = self.roundtrip(
im,
qtables={0: standard_l_qtable, 1: standard_chrominance_qtable},
quality=quality,
).quantization
qtable_from_quality = self.roundtrip(im, quality=quality).quantization
if features.check_feature("libjpeg_turbo"):
assert qtable_from_qtable_quality == qtable_from_quality
else:
assert qtable_from_qtable_quality[0] == qtable_from_quality[0]
assert (
qtable_from_qtable_quality[1][1:] == qtable_from_quality[1][1:]
)
# list of qtable lists
assert_image_similar(
im,
@ -716,14 +730,6 @@ class TestFileJpeg:
img.load_djpeg()
assert_image_similar_tofile(img, TEST_FILE, 5)
@pytest.mark.skipif(not cjpeg_available(), reason="cjpeg not available")
def test_save_cjpeg(self, tmp_path: Path) -> None:
with Image.open(TEST_FILE) as img:
tempfile = str(tmp_path / "temp.jpg")
JpegImagePlugin._save_cjpeg(img, BytesIO(), tempfile)
# Default save quality is 75%, so a tiny bit of difference is alright
assert_image_similar_tofile(img, tempfile, 17)
def test_no_duplicate_0x1001_tag(self) -> None:
# Arrange
tag_ids = {v: k for k, v in ExifTags.TAGS.items()}
@ -749,10 +755,13 @@ class TestFileJpeg:
# Act
# Shouldn't raise error
fn = "Tests/images/sugarshack_bad_mpo_header.jpg"
with pytest.warns(UserWarning, Image.open, fn) as im:
# Assert
assert im.format == "JPEG"
with pytest.warns(UserWarning, match="malformed MPO file"):
im = Image.open("Tests/images/sugarshack_bad_mpo_header.jpg")
# Assert
assert im.format == "JPEG"
im.close()
@pytest.mark.parametrize("mode", ("1", "L", "RGB", "RGBX", "CMYK", "YCbCr"))
def test_save_correct_modes(self, mode: str) -> None:
@ -1033,7 +1042,7 @@ class TestFileJpeg:
with pytest.raises(ValueError):
im.save(f, xmp=b"1" * 65505)
@pytest.mark.timeout(timeout=1)
@timeout_unless_slower_valgrind(1)
def test_eof(self, monkeypatch: pytest.MonkeyPatch) -> None:
# Even though this decoder never says that it is finished
# the image should still end when there is no new data
@ -1064,10 +1073,16 @@ class TestFileJpeg:
for marker in b"\xff\xd8", b"\xff\xd9":
assert marker in data[1]
assert marker in data[2]
# DHT, DQT
for marker in b"\xff\xc4", b"\xff\xdb":
# DQT
markers = [b"\xff\xdb"]
if features.check_feature("libjpeg_turbo"):
# DHT
markers.append(b"\xff\xc4")
for marker in markers:
assert marker in data[1]
assert marker not in data[2]
# SOF0, SOS, APP0 (JFIF header)
for marker in b"\xff\xc0", b"\xff\xda", b"\xff\xe0":
assert marker not in data[1]
@ -1091,14 +1106,6 @@ class TestFileJpeg:
assert im._repr_jpeg_() is None
def test_deprecation(self) -> None:
with Image.open(TEST_FILE) as im:
assert isinstance(im, JpegImagePlugin.JpegImageFile)
with pytest.warns(DeprecationWarning):
assert im.huffman_ac == {}
with pytest.warns(DeprecationWarning):
assert im.huffman_dc == {}
@pytest.mark.skipif(not is_win32(), reason="Windows only")
@skip_unless_feature("jpg")

View File

@ -457,8 +457,8 @@ def test_comment() -> None:
# Test an image that is truncated partway through a codestream
with open("Tests/images/comment.jp2", "rb") as fp:
b = BytesIO(fp.read(130))
with Image.open(b) as im:
pass
with Image.open(b) as im:
pass
def test_save_comment(card: ImageFile.ImageFile) -> None:

View File

@ -81,7 +81,7 @@ class TestFileLibTiff(LibTiffTestCase):
s = io.BytesIO()
with open(test_file, "rb") as f:
s.write(f.read())
s.seek(0)
s.seek(0)
with Image.open(s) as im:
assert im.size == (500, 500)
self._assert_noerr(tmp_path, im)
@ -256,19 +256,7 @@ class TestFileLibTiff(LibTiffTestCase):
im.save(out, tiffinfo=new_ifd)
@pytest.mark.parametrize(
"libtiff",
(
pytest.param(
True,
marks=pytest.mark.skipif(
not getattr(Image.core, "libtiff_support_custom_tags", False),
reason="Custom tags not supported by older libtiff",
),
),
False,
),
)
@pytest.mark.parametrize("libtiff", (True, False))
def test_custom_metadata(
self, monkeypatch: pytest.MonkeyPatch, tmp_path: Path, libtiff: bool
) -> None:
@ -724,8 +712,7 @@ class TestFileLibTiff(LibTiffTestCase):
with Image.open(out) as reloaded:
assert isinstance(reloaded, TiffImagePlugin.TiffImageFile)
if Image.core.libtiff_support_custom_tags:
assert reloaded.tag_v2[34665] == 125456
assert reloaded.tag_v2[34665] == 125456
def test_crashing_metadata(
self, monkeypatch: pytest.MonkeyPatch, tmp_path: Path
@ -777,19 +764,7 @@ class TestFileLibTiff(LibTiffTestCase):
assert icc_libtiff is not None
assert icc == icc_libtiff
@pytest.mark.parametrize(
"libtiff",
(
pytest.param(
True,
marks=pytest.mark.skipif(
not getattr(Image.core, "libtiff_support_custom_tags", False),
reason="Custom tags not supported by older libtiff",
),
),
False,
),
)
@pytest.mark.parametrize("libtiff", (True, False))
def test_write_icc(
self, monkeypatch: pytest.MonkeyPatch, tmp_path: Path, libtiff: bool
) -> None:
@ -898,8 +873,8 @@ class TestFileLibTiff(LibTiffTestCase):
assert im.mode == "RGB"
assert im.size == (128, 128)
assert im.format == "TIFF"
im2 = hopper()
assert_image_similar(im, im2, 5)
with hopper() as im2:
assert_image_similar(im, im2, 5)
except OSError:
captured = capfd.readouterr()
if "LZMA compression support is not configured" in captured.err:
@ -1050,12 +1025,12 @@ class TestFileLibTiff(LibTiffTestCase):
with open("Tests/images/old-style-jpeg-compression.tif", "rb") as fp:
data = fp.read()
# Set EXIF Orientation to 2
data = data[:102] + b"\x02" + data[103:]
# Set EXIF Orientation to 2
data = data[:102] + b"\x02" + data[103:]
with Image.open(io.BytesIO(data)) as im:
im = im.transpose(Image.Transpose.FLIP_LEFT_RIGHT)
assert_image_equal_tofile(im, "Tests/images/old-style-jpeg-compression.png")
with Image.open(io.BytesIO(data)) as im:
im = im.transpose(Image.Transpose.FLIP_LEFT_RIGHT)
assert_image_equal_tofile(im, "Tests/images/old-style-jpeg-compression.png")
def test_open_missing_samplesperpixel(self) -> None:
with Image.open(

View File

@ -32,7 +32,7 @@ class TestFileLibTiffSmall(LibTiffTestCase):
s = BytesIO()
with open(test_file, "rb") as f:
s.write(f.read())
s.seek(0)
s.seek(0)
with Image.open(s) as im:
assert im.size == (128, 128)
self._assert_noerr(tmp_path, im)

View File

@ -27,6 +27,6 @@ def test_valid_file() -> None:
# Assert
assert im.format == "MCIDAS"
assert im.mode == "I"
assert im.mode == "I;16B"
assert im.size == (1800, 400)
assert_image_equal_tofile(im, saved_file)

View File

@ -156,6 +156,7 @@ def test_reload_exif_after_seek() -> None:
def test_mp(test_file: str) -> None:
with Image.open(test_file) as im:
mpinfo = im._getmp()
assert mpinfo is not None
assert mpinfo[45056] == b"0100"
assert mpinfo[45057] == 2
@ -165,6 +166,7 @@ def test_mp_offset() -> None:
# in APP2 data, in contrast to normal 8
with Image.open("Tests/images/sugarshack_ifd_offset.mpo") as im:
mpinfo = im._getmp()
assert mpinfo is not None
assert mpinfo[45056] == b"0100"
assert mpinfo[45057] == 2
@ -181,6 +183,7 @@ def test_mp_no_data() -> None:
def test_mp_attribute(test_file: str) -> None:
with Image.open(test_file) as im:
mpinfo = im._getmp()
assert mpinfo is not None
for frame_number, mpentry in enumerate(mpinfo[0xB002]):
mpattr = mpentry["Attribute"]
if frame_number:
@ -293,16 +296,18 @@ def test_save_all() -> None:
assert_image_similar(im, im_reloaded, 30)
im = Image.new("RGB", (1, 1))
im2 = Image.new("RGB", (1, 1), "#f00")
im_reloaded = roundtrip(im, save_all=True, append_images=[im2])
for colors in (("#f00",), ("#f00", "#0f0")):
append_images = [Image.new("RGB", (1, 1), color) for color in colors]
im_reloaded = roundtrip(im, save_all=True, append_images=append_images)
assert_image_equal(im, im_reloaded)
assert isinstance(im_reloaded, MpoImagePlugin.MpoImageFile)
assert im_reloaded.mpinfo is not None
assert im_reloaded.mpinfo[45056] == b"0100"
assert_image_equal(im, im_reloaded)
assert isinstance(im_reloaded, MpoImagePlugin.MpoImageFile)
assert im_reloaded.mpinfo is not None
assert im_reloaded.mpinfo[45056] == b"0100"
im_reloaded.seek(1)
assert_image_similar(im2, im_reloaded, 1)
for im_expected in append_images:
im_reloaded.seek(im_reloaded.tell() + 1)
assert_image_similar(im_reloaded, im_expected, 1)
# Test that a single frame image will not be saved as an MPO
jpg = roundtrip(im, save_all=True)
@ -312,10 +317,24 @@ def test_save_all() -> None:
def test_save_xmp() -> None:
im = Image.new("RGB", (1, 1))
im2 = Image.new("RGB", (1, 1), "#f00")
def roundtrip_xmp() -> list[Any]:
im_reloaded = roundtrip(im, xmp=b"Default", save_all=True, append_images=[im2])
xmp = [im_reloaded.info["xmp"]]
im_reloaded.seek(1)
return xmp + [im_reloaded.info["xmp"]]
# Use the save parameters for all frames by default
assert roundtrip_xmp() == [b"Default", b"Default"]
# Specify a value for the first frame
im.encoderinfo = {"xmp": b"First frame"}
assert roundtrip_xmp() == [b"First frame", b"Default"]
del im.encoderinfo
# Specify value for the second frame
im2.encoderinfo = {"xmp": b"Second frame"}
im_reloaded = roundtrip(im, xmp=b"First frame", save_all=True, append_images=[im2])
assert roundtrip_xmp() == [b"Default", b"Second frame"]
assert im_reloaded.info["xmp"] == b"First frame"
im_reloaded.seek(1)
assert im_reloaded.info["xmp"] == b"Second frame"
# Test that encoderinfo is unchanged
assert im2.encoderinfo == {"xmp": b"Second frame"}

View File

@ -37,6 +37,11 @@ def test_sanity(tmp_path: Path) -> None:
im.save(f)
def test_p_4_planes() -> None:
with Image.open("Tests/images/p_4_planes.pcx") as im:
assert im.getpixel((0, 0)) == 3
def test_bad_image_size() -> None:
with open("Tests/images/pil184.pcx", "rb") as fp:
data = fp.read()

View File

@ -13,7 +13,12 @@ import pytest
from PIL import Image, PdfParser, features
from .helper import hopper, mark_if_feature_version, skip_unless_feature
from .helper import (
hopper,
mark_if_feature_version,
skip_unless_feature,
timeout_unless_slower_valgrind,
)
def helper_save_as_pdf(tmp_path: Path, mode: str, **kwargs: Any) -> str:
@ -339,8 +344,7 @@ def test_pdf_append_to_bytesio() -> None:
assert len(f.getvalue()) > initial_size
@pytest.mark.timeout(1)
@pytest.mark.skipif("PILLOW_VALGRIND_TEST" in os.environ, reason="Valgrind is slower")
@timeout_unless_slower_valgrind(1)
@pytest.mark.parametrize("newline", (b"\r", b"\n"))
def test_redos(newline: bytes) -> None:
malicious = b" trailer<<>>" + newline * 3456

View File

@ -100,11 +100,11 @@ class TestFilePng:
assert im.format == "PNG"
assert im.get_format_mimetype() == "image/png"
for mode in ["1", "L", "P", "RGB", "I", "I;16", "I;16B"]:
for mode in ["1", "L", "P", "RGB", "I;16", "I;16B"]:
im = hopper(mode)
im.save(test_file)
with Image.open(test_file) as reloaded:
if mode in ("I", "I;16B"):
if mode == "I;16B":
reloaded = reloaded.convert(mode)
assert_image_equal(reloaded, im)
@ -801,6 +801,16 @@ class TestFilePng:
with Image.open("Tests/images/truncated_end_chunk.png") as im:
assert_image_equal_tofile(im, "Tests/images/hopper.png")
def test_deprecation(self, tmp_path: Path) -> None:
test_file = tmp_path / "out.png"
im = hopper("I")
with pytest.warns(DeprecationWarning, match="Saving I mode images as PNG"):
im.save(test_file)
with Image.open(test_file) as reloaded:
assert_image_equal(im, reloaded.convert("I"))
@pytest.mark.skipif(is_win32(), reason="Requires Unix or macOS")
@skip_unless_feature("zlib")

View File

@ -288,14 +288,16 @@ def test_non_integer_token(tmp_path: Path) -> None:
pass
def test_header_token_too_long(tmp_path: Path) -> None:
@pytest.mark.parametrize("data", (b"P3\x0cAAAAAAAAAA\xee", b"P6\n 01234567890"))
def test_header_token_too_long(tmp_path: Path, data: bytes) -> None:
path = tmp_path / "temp.ppm"
with open(path, "wb") as f:
f.write(b"P6\n 01234567890")
f.write(data)
with pytest.raises(ValueError, match="Token too long in file header: 01234567890"):
with pytest.raises(ValueError) as e:
with Image.open(path):
pass
assert "Token too long in file header: " in repr(e)
def test_truncated_file(tmp_path: Path) -> None:

View File

@ -1,10 +1,12 @@
from __future__ import annotations
from pathlib import Path
import pytest
from PIL import Image, QoiImagePlugin
from .helper import assert_image_equal_tofile
from .helper import assert_image_equal_tofile, hopper
def test_sanity() -> None:
@ -28,3 +30,28 @@ def test_invalid_file() -> None:
with pytest.raises(SyntaxError):
QoiImagePlugin.QoiImageFile(invalid_file)
def test_op_index() -> None:
# QOI_OP_INDEX as the first chunk
with Image.open("Tests/images/op_index.qoi") as im:
assert im.getpixel((0, 0)) == (0, 0, 0, 0)
def test_save(tmp_path: Path) -> None:
f = tmp_path / "temp.qoi"
im = hopper()
im.save(f, colorspace="sRGB")
assert_image_equal_tofile(im, f)
for path in ("Tests/images/default_font.png", "Tests/images/pil123rgba.png"):
with Image.open(path) as im:
im.save(f)
assert_image_equal_tofile(im, f)
im = hopper("P")
with pytest.raises(ValueError, match="Unsupported QOI image mode"):
im.save(f)

View File

@ -1,5 +1,6 @@
from __future__ import annotations
from io import BytesIO
from pathlib import Path
import pytest
@ -71,6 +72,15 @@ def test_invalid_file() -> None:
SgiImagePlugin.SgiImageFile(invalid_file)
def test_unsupported_image_mode() -> None:
with open("Tests/images/hopper.rgb", "rb") as fp:
data = fp.read()
data = data[:3] + b"\x03" + data[4:]
with pytest.raises(ValueError, match="Unsupported SGI image mode"):
with Image.open(BytesIO(data)):
pass
def roundtrip(img: Image.Image, tmp_path: Path) -> None:
out = tmp_path / "temp.sgi"
img.save(out, format="sgi")
@ -109,3 +119,11 @@ def test_unsupported_mode(tmp_path: Path) -> None:
with pytest.raises(ValueError):
im.save(out, format="sgi")
def test_unsupported_number_of_bytes_per_pixel(tmp_path: Path) -> None:
im = hopper()
out = tmp_path / "temp.sgi"
with pytest.raises(ValueError, match="Unsupported number of bytes per pixel"):
im.save(out, bpc=3)

View File

@ -190,7 +190,9 @@ def test_save_id_section(tmp_path: Path) -> None:
# Save with custom id section greater than 255 characters
id_section = b"Test content" * 25
with pytest.warns(UserWarning):
with pytest.warns(
UserWarning, match="id_section has been trimmed to 255 characters"
):
im.save(out, id_section=id_section)
with Image.open(out) as test_im:
@ -220,12 +222,16 @@ def test_horizontal_orientations() -> None:
with Image.open("Tests/images/rgb32rle_top_right.tga") as im:
px = im.load()
assert px is not None
assert px[90, 90][:3] == (0, 0, 0)
value = px[90, 90]
assert isinstance(value, tuple)
assert value[:3] == (0, 0, 0)
with Image.open("Tests/images/rgb32rle_bottom_right.tga") as im:
px = im.load()
assert px is not None
assert px[90, 90][:3] == (0, 255, 0)
value = px[90, 90]
assert isinstance(value, tuple)
assert value[:3] == (0, 255, 0)
def test_save_rle(tmp_path: Path) -> None:

View File

@ -14,6 +14,7 @@ from PIL import (
ImageFile,
JpegImagePlugin,
TiffImagePlugin,
TiffTags,
UnidentifiedImageError,
)
from PIL.TiffImagePlugin import RESOLUTION_UNIT, X_RESOLUTION, Y_RESOLUTION
@ -26,6 +27,7 @@ from .helper import (
hopper,
is_pypy,
is_win32,
timeout_unless_slower_valgrind,
)
ElementTree: ModuleType | None
@ -47,25 +49,10 @@ class TestFileTiff:
assert im.size == (128, 128)
assert im.format == "TIFF"
hopper("1").save(filename)
with Image.open(filename):
pass
hopper("L").save(filename)
with Image.open(filename):
pass
hopper("P").save(filename)
with Image.open(filename):
pass
hopper("RGB").save(filename)
with Image.open(filename):
pass
hopper("I").save(filename)
with Image.open(filename):
pass
for mode in ("1", "L", "P", "RGB", "I", "I;16", "I;16L"):
hopper(mode).save(filename)
with Image.open(filename):
pass
@pytest.mark.skipif(is_pypy(), reason="Requires CPython")
def test_unclosed_file(self) -> None:
@ -234,7 +221,7 @@ class TestFileTiff:
assert isinstance(im, JpegImagePlugin.JpegImageFile)
# Should not raise struct.error.
with pytest.warns(UserWarning):
with pytest.warns(UserWarning, match="Corrupt EXIF data"):
im._getexif()
def test_save_rgba(self, tmp_path: Path) -> None:
@ -694,16 +681,21 @@ class TestFileTiff:
assert im.tag_v2[278] == 256
im = hopper()
im.encoderinfo = {"tiffinfo": {278: 100}}
im2 = Image.new("L", (128, 128))
im2.encoderinfo = {"tiffinfo": {278: 256}}
im.save(outfile, save_all=True, append_images=[im2])
im3 = im2.copy()
im3.encoderinfo = {"tiffinfo": {278: 300}}
im.save(outfile, save_all=True, tiffinfo={278: 200}, append_images=[im2, im3])
with Image.open(outfile) as im:
assert isinstance(im, TiffImagePlugin.TiffImageFile)
assert im.tag_v2[278] == 128
assert im.tag_v2[278] == 100
im.seek(1)
assert im.tag_v2[278] == 256
assert im.tag_v2[278] == 200
im.seek(2)
assert im.tag_v2[278] == 300
def test_strip_raw(self) -> None:
infile = "Tests/images/tiff_strip_raw.tif"
@ -899,6 +891,29 @@ class TestFileTiff:
assert description[0]["format"] == "image/tiff"
assert description[3]["BitsPerSample"]["Seq"]["li"] == ["8", "8", "8"]
def test_getxmp_undefined(self, tmp_path: Path) -> None:
tmpfile = tmp_path / "temp.tif"
im = Image.new("L", (1, 1))
ifd = TiffImagePlugin.ImageFileDirectory_v2()
ifd.tagtype[700] = TiffTags.UNDEFINED
with Image.open("Tests/images/lab.tif") as im_xmp:
ifd[700] = im_xmp.info["xmp"]
im.save(tmpfile, tiffinfo=ifd)
with Image.open(tmpfile) as im_reloaded:
if ElementTree is None:
with pytest.warns(
UserWarning,
match="XMP data cannot be read without defusedxml dependency",
):
assert im_reloaded.getxmp() == {}
else:
assert "xmp" in im_reloaded.info
xmp = im_reloaded.getxmp()
description = xmp["xmpmeta"]["RDF"]["Description"]
assert description[0]["format"] == "image/tiff"
def test_get_photoshop_blocks(self) -> None:
with Image.open("Tests/images/lab.tif") as im:
assert isinstance(im, TiffImagePlugin.TiffImageFile)
@ -988,7 +1003,7 @@ class TestFileTiff:
with pytest.raises(OSError):
im.load()
@pytest.mark.timeout(6)
@timeout_unless_slower_valgrind(6)
@pytest.mark.filterwarnings("ignore:Truncated File Read")
def test_timeout(self, monkeypatch: pytest.MonkeyPatch) -> None:
with Image.open("Tests/images/timeout-6646305047838720") as im:
@ -1001,10 +1016,10 @@ class TestFileTiff:
"Tests/images/oom-225817ca0f8c663be7ab4b9e717b02c661e66834.tif",
],
)
@pytest.mark.timeout(2)
@timeout_unless_slower_valgrind(2)
def test_oom(self, test_file: str) -> None:
with pytest.raises(UnidentifiedImageError):
with pytest.warns(UserWarning):
with pytest.warns(UserWarning, match="Corrupt EXIF data"):
with Image.open(test_file):
pass

View File

@ -300,7 +300,7 @@ def test_empty_metadata() -> None:
head = f.read(8)
info = TiffImagePlugin.ImageFileDirectory(head)
# Should not raise struct.error.
with pytest.warns(UserWarning):
with pytest.warns(UserWarning, match="Corrupt EXIF data"):
info.load(f)
@ -481,7 +481,7 @@ def test_too_many_entries() -> None:
ifd.tagtype[277] = TiffTags.SHORT
# Should not raise ValueError.
with pytest.warns(UserWarning):
with pytest.warns(UserWarning, match="Metadata Warning"):
assert ifd[277] == 4

View File

@ -33,8 +33,8 @@ class TestUnsupportedWebp:
monkeypatch.setattr(WebPImagePlugin, "SUPPORTED", False)
file_path = "Tests/images/hopper.webp"
with pytest.warns(UserWarning):
with pytest.raises(OSError):
with pytest.raises(OSError):
with pytest.warns(UserWarning, match="WEBP support not installed"):
with Image.open(file_path):
pass
@ -219,6 +219,7 @@ class TestFileWebp:
# Save P mode GIF with background
with Image.open("Tests/images/chi.gif") as im:
original_value = im.convert("RGB").getpixel((1, 1))
assert isinstance(original_value, tuple)
# Save as WEBP
im.save(out_webp, save_all=True)
@ -230,6 +231,7 @@ class TestFileWebp:
with Image.open(out_gif) as reread:
reread_value = reread.convert("RGB").getpixel((1, 1))
assert isinstance(reread_value, tuple)
difference = sum(abs(original_value[i] - reread_value[i]) for i in range(3))
assert difference < 5

View File

@ -44,6 +44,18 @@ def test_load_zero_inch() -> None:
pass
def test_load_unsupported_wmf() -> None:
b = BytesIO(b"\xd7\xcd\xc6\x9a\x00\x00" + b"\x01" * 10)
with pytest.raises(SyntaxError, match="Unsupported WMF file format"):
WmfImagePlugin.WmfStubImageFile(b)
def test_load_unsupported() -> None:
b = BytesIO(b"\x01\x00\x00\x00")
with pytest.raises(SyntaxError, match="Unsupported file format"):
WmfImagePlugin.WmfStubImageFile(b)
def test_render() -> None:
with open("Tests/images/drawing.emf", "rb") as fp:
data = fp.read()

View File

@ -1,5 +1,7 @@
from __future__ import annotations
from io import BytesIO
import pytest
from PIL import Image, XpmImagePlugin
@ -17,7 +19,45 @@ def test_sanity() -> None:
assert im.format == "XPM"
# large error due to quantization->44 colors.
assert_image_similar(im.convert("RGB"), hopper("RGB"), 60)
assert_image_similar(im.convert("RGB"), hopper(), 23)
def test_bpp2() -> None:
with Image.open("Tests/images/hopper_bpp2.xpm") as im:
assert_image_similar(im.convert("RGB"), hopper(), 11)
def test_rgb() -> None:
with Image.open("Tests/images/hopper_rgb.xpm") as im:
assert im.mode == "RGB"
assert_image_similar(im, hopper(), 16)
def test_truncated_header() -> None:
data = b"/* XPM */"
with pytest.raises(SyntaxError, match="broken XPM file"):
with XpmImagePlugin.XpmImageFile(BytesIO(data)):
pass
def test_cannot_read_color() -> None:
with open(TEST_FILE, "rb") as fp:
data = fp.read().split(b"#")[0]
with pytest.raises(ValueError, match="cannot read this XPM file"):
with Image.open(BytesIO(data)):
pass
with pytest.raises(ValueError, match="cannot read this XPM file"):
with Image.open(BytesIO(data + b"invalid")):
pass
def test_not_enough_image_data() -> None:
with open(TEST_FILE, "rb") as fp:
data = fp.read().split(b"/* pixels */")[0]
with Image.open(BytesIO(data)) as im:
with pytest.raises(ValueError, match="not enough image data"):
im.load()
def test_invalid_file() -> None:

View File

@ -30,10 +30,10 @@ from .helper import (
assert_image_similar_tofile,
assert_not_all_same,
hopper,
is_big_endian,
is_win32,
mark_if_feature_version,
skip_unless_feature,
timeout_unless_slower_valgrind,
)
ElementTree: ModuleType | None
@ -49,19 +49,10 @@ except ImportError:
PrettyPrinter = None
# Deprecation helper
def helper_image_new(mode: str, size: tuple[int, int]) -> Image.Image:
if mode.startswith("BGR;"):
with pytest.warns(DeprecationWarning):
return Image.new(mode, size)
else:
return Image.new(mode, size)
class TestImage:
@pytest.mark.parametrize("mode", Image.MODES + ["BGR;15", "BGR;16", "BGR;24"])
@pytest.mark.parametrize("mode", Image.MODES)
def test_image_modes_success(self, mode: str) -> None:
helper_image_new(mode, (1, 1))
Image.new(mode, (1, 1))
@pytest.mark.parametrize("mode", ("", "bad", "very very long"))
def test_image_modes_fail(self, mode: str) -> None:
@ -140,8 +131,8 @@ class TestImage:
monkeypatch.setattr(Image, "WARN_POSSIBLE_FORMATS", True)
im = io.BytesIO(b"")
with pytest.warns(UserWarning):
with pytest.raises(UnidentifiedImageError):
with pytest.raises(UnidentifiedImageError):
with pytest.warns(UserWarning, match="opening failed"):
with Image.open(im):
pass
@ -159,6 +150,10 @@ class TestImage:
with pytest.raises(AttributeError):
im.mode = "P" # type: ignore[misc]
def test_empty_path(self) -> None:
with pytest.raises(FileNotFoundError):
Image.open("")
def test_invalid_image(self) -> None:
im = io.BytesIO(b"")
with pytest.raises(UnidentifiedImageError):
@ -572,10 +567,7 @@ class TestImage:
i = Image.new("RGB", [1, 1])
assert isinstance(i.size, tuple)
@pytest.mark.timeout(0.75)
@pytest.mark.skipif(
"PILLOW_VALGRIND_TEST" in os.environ, reason="Valgrind is slower"
)
@timeout_unless_slower_valgrind(0.75)
@pytest.mark.parametrize("size", ((0, 100000000), (100000000, 0)))
def test_empty_image(self, size: tuple[int, int]) -> None:
Image.new("RGB", size)
@ -673,6 +665,7 @@ class TestImage:
im_remapped = im.remap_palette(list(range(256)))
assert_image_equal(im, im_remapped)
assert im.palette is not None
assert im_remapped.palette is not None
assert im.palette.palette == im_remapped.palette.palette
# Test illegal image mode
@ -975,6 +968,11 @@ class TestImage:
assert tag not in exif.get_ifd(0x8769)
assert exif.get_ifd(0xA005)
def test_exif_from_xmp_bytes(self) -> None:
im = Image.new("RGB", (1, 1))
im.info["xmp"] = b'\xff tiff:Orientation="2"'
assert im.getexif()[274] == 2
def test_empty_xmp(self) -> None:
with Image.open("Tests/images/hopper.gif") as im:
if ElementTree is None:
@ -991,7 +989,7 @@ class TestImage:
im = Image.new("RGB", (1, 1))
im.info["xmp"] = (
b'<?xpacket begin="\xef\xbb\xbf" id="W5M0MpCehiHzreSzNTczkc9d"?>\n'
b'<x:xmpmeta xmlns:x="adobe:ns:meta/" />\n<?xpacket end="w"?>\x00\x00'
b'<x:xmpmeta xmlns:x="adobe:ns:meta/" />\n<?xpacket end="w"?>\x00\x00 '
)
if ElementTree is None:
with pytest.warns(
@ -1004,7 +1002,7 @@ class TestImage:
def test_get_child_images(self) -> None:
im = Image.new("RGB", (1, 1))
with pytest.warns(DeprecationWarning):
with pytest.warns(DeprecationWarning, match="Image.Image.get_child_images"):
assert im.get_child_images() == []
@pytest.mark.parametrize("size", ((1, 0), (0, 1), (0, 0)))
@ -1134,39 +1132,29 @@ class TestImage:
assert len(caplog.records) == 0
assert im.fp is None
def test_deprecation(self) -> None:
with pytest.warns(DeprecationWarning):
assert not Image.isImageType(None)
class TestImageBytes:
@pytest.mark.parametrize("mode", Image.MODES + ["BGR;15", "BGR;16", "BGR;24"])
@pytest.mark.parametrize("mode", Image.MODES)
def test_roundtrip_bytes_constructor(self, mode: str) -> None:
im = hopper(mode)
source_bytes = im.tobytes()
if mode.startswith("BGR;"):
with pytest.warns(DeprecationWarning):
reloaded = Image.frombytes(mode, im.size, source_bytes)
else:
reloaded = Image.frombytes(mode, im.size, source_bytes)
reloaded = Image.frombytes(mode, im.size, source_bytes)
assert reloaded.tobytes() == source_bytes
@pytest.mark.parametrize("mode", Image.MODES + ["BGR;15", "BGR;16", "BGR;24"])
@pytest.mark.parametrize("mode", Image.MODES)
def test_roundtrip_bytes_method(self, mode: str) -> None:
im = hopper(mode)
source_bytes = im.tobytes()
reloaded = helper_image_new(mode, im.size)
reloaded = Image.new(mode, im.size)
reloaded.frombytes(source_bytes)
assert reloaded.tobytes() == source_bytes
@pytest.mark.parametrize("mode", Image.MODES + ["BGR;15", "BGR;16", "BGR;24"])
@pytest.mark.parametrize("mode", Image.MODES)
def test_getdata_putdata(self, mode: str) -> None:
if is_big_endian() and mode == "BGR;15":
pytest.xfail("Known failure of BGR;15 on big-endian")
im = hopper(mode)
reloaded = helper_image_new(mode, im.size)
reloaded = Image.new(mode, im.size)
reloaded.putdata(im.getdata())
assert_image_equal(im, reloaded)

View File

@ -123,10 +123,6 @@ class TestImageGetPixel:
bands = Image.getmodebands(mode)
if bands == 1:
return 1
if mode in ("BGR;15", "BGR;16"):
# These modes have less than 8 bits per band,
# so (1, 2, 3) cannot be roundtripped.
return (16, 32, 49)
return tuple(range(1, bands + 1))
def check(self, mode: str, expected_color_int: int | None = None) -> None:
@ -191,11 +187,6 @@ class TestImageGetPixel:
def test_basic(self, mode: str) -> None:
self.check(mode)
@pytest.mark.parametrize("mode", ("BGR;15", "BGR;16", "BGR;24"))
def test_deprecated(self, mode: str) -> None:
with pytest.warns(DeprecationWarning):
self.check(mode)
def test_list(self) -> None:
im = hopper()
assert im.getpixel([0, 0]) == (20, 20, 70)
@ -218,7 +209,7 @@ class TestImageGetPixel:
class TestImagePutPixelError:
IMAGE_MODES1 = ["LA", "RGB", "RGBA", "BGR;15"]
IMAGE_MODES1 = ["LA", "RGB", "RGBA"]
IMAGE_MODES2 = ["L", "I", "I;16"]
INVALID_TYPES = ["foo", 1.0, None]
@ -234,11 +225,6 @@ class TestImagePutPixelError:
(
("L", (0, 2), "color must be int or single-element tuple"),
("LA", (0, 3), "color must be int, or tuple of one or two elements"),
(
"BGR;15",
(0, 2),
"color must be int, or tuple of one or three elements",
),
(
"RGB",
(0, 2, 5),
@ -329,3 +315,6 @@ int main(int argc, char* argv[])
process = subprocess.Popen(["embed_pil.exe"], env=env)
process.communicate()
assert process.returncode == 0
def teardown_method(self) -> None:
os.remove("embed_pil.c")

View File

@ -1,6 +1,6 @@
from __future__ import annotations
from typing import TYPE_CHECKING, Any
from typing import Any
import pytest
from packaging.version import parse as parse_version
@ -13,6 +13,7 @@ numpy = pytest.importorskip("numpy", reason="NumPy not installed")
im = hopper().resize((128, 100))
TYPE_CHECKING = False
if TYPE_CHECKING:
import numpy.typing as npt
@ -47,7 +48,7 @@ def test_toarray() -> None:
with pytest.raises(OSError):
numpy.array(im_truncated)
else:
with pytest.warns(DeprecationWarning):
with pytest.warns(DeprecationWarning, match="__array_interface__"):
numpy.array(im_truncated)
@ -101,7 +102,8 @@ def test_fromarray_strides_without_tobytes() -> None:
with pytest.raises(ValueError):
wrapped = Wrapper({"shape": (1, 1), "strides": (1, 1)})
Image.fromarray(wrapped, "L")
with pytest.warns(DeprecationWarning, match="'mode' parameter"):
Image.fromarray(wrapped, "L")
def test_fromarray_palette() -> None:
@ -110,7 +112,8 @@ def test_fromarray_palette() -> None:
a = numpy.array(i)
# Act
out = Image.fromarray(a, "P")
with pytest.warns(DeprecationWarning, match="'mode' parameter"):
out = Image.fromarray(a, "P")
# Assert that the Python and C palettes match
assert out.palette is not None

View File

@ -203,7 +203,10 @@ def test_trns_RGB(tmp_path: Path) -> None:
assert "transparency" not in im_rgba.info
assert im_rgba.getpixel((0, 0)) == (0, 0, 0, 0)
im_p = pytest.warns(UserWarning, im.convert, "P", palette=Image.Palette.ADAPTIVE)
with pytest.warns(
UserWarning, match="Couldn't allocate palette entry for transparency"
):
im_p = im.convert("P", palette=Image.Palette.ADAPTIVE)
assert "transparency" not in im_p.info
im_p.save(f)

View File

@ -1,7 +1,5 @@
from __future__ import annotations
import pytest
from .helper import hopper
@ -10,10 +8,3 @@ def test_sanity() -> None:
type_repr = repr(type(im.getim()))
assert "PyCapsule" in type_repr
with pytest.warns(DeprecationWarning):
assert isinstance(im.im.id, int)
with pytest.warns(DeprecationWarning):
ptrs = dict(im.im.unsafe_ptrs)
assert ptrs.keys() == {"image8", "image32", "image"}

View File

@ -78,16 +78,6 @@ def test_mode_F() -> None:
assert list(im.getdata()) == target
@pytest.mark.parametrize("mode", ("BGR;15", "BGR;16", "BGR;24"))
def test_mode_BGR(mode: str) -> None:
data = [(16, 32, 49), (32, 32, 98)]
with pytest.warns(DeprecationWarning):
im = Image.new(mode, (1, 2))
im.putdata(data)
assert list(im.getdata()) == data
def test_array_B() -> None:
# shouldn't segfault
# see https://github.com/python-pillow/Pillow/issues/1008

View File

@ -70,6 +70,7 @@ def test_quantize_no_dither() -> None:
converted = image.quantize(dither=Image.Dither.NONE, palette=palette)
assert converted.mode == "P"
assert converted.palette is not None
assert palette.palette is not None
assert converted.palette.palette == palette.palette.palette

View File

@ -462,7 +462,7 @@ class TestCoreResampleBox:
im.resize((32, 32), resample, (20, 20, 20, 100))
im.resize((32, 32), resample, (20, 20, 100, 20))
with pytest.raises(TypeError, match="must be sequence of length 4"):
with pytest.raises(TypeError, match="must be (sequence|tuple) of length 4"):
im.resize((32, 32), resample, (im.width, im.height)) # type: ignore[arg-type]
with pytest.raises(ValueError, match="can't be negative"):

View File

@ -324,7 +324,7 @@ class TestImageResize:
im = hopper(mode)
assert im.resize((20, 20), Image.Resampling.BICUBIC) == im.resize((20, 20))
@pytest.mark.parametrize("mode", ("1", "P", "BGR;15", "BGR;16"))
@pytest.mark.parametrize("mode", ("1", "P"))
def test_default_filter_nearest(self, mode: str) -> None:
im = hopper(mode)
assert im.resize((20, 20), Image.Resampling.NEAREST) == im.resize((20, 20))

View File

@ -48,6 +48,7 @@ class TestImageTransform:
im.size, Image.Transform.AFFINE, [1, 0, 0, 0, 1, 0]
)
assert im.palette is not None
assert transformed.palette is not None
assert im.palette.palette == transformed.palette.palette
def test_extent(self) -> None:

View File

@ -54,10 +54,6 @@ def skip_missing() -> None:
def test_sanity() -> None:
# basic smoke test.
# this mostly follows the cms_test outline.
with pytest.warns(DeprecationWarning):
v = ImageCms.versions() # should return four strings
assert v[0] == "1.0.0 pil"
assert list(map(type, v)) == [str, str, str, str]
# internal version number
version = features.version_module("littlecms2")
@ -677,12 +673,6 @@ def test_auxiliary_channels_isolated() -> None:
assert_image_equal(test_image.convert(dst_format[2]), reference_image)
def test_long_modes() -> None:
p = ImageCms.getOpenProfile("Tests/icc/sGrey-v2-nano.icc")
with pytest.warns(DeprecationWarning):
ImageCms.buildTransform(p, p, "ABCDEFGHI", "ABCDEFGHI")
@pytest.mark.parametrize("mode", ("RGB", "RGBA", "RGBX"))
def test_rgb_lab(mode: str) -> None:
im = Image.new(mode, (1, 1))
@ -703,15 +693,14 @@ def test_cmyk_lab() -> None:
def test_deprecation() -> None:
with pytest.warns(DeprecationWarning):
assert ImageCms.DESCRIPTION.strip().startswith("pyCMS")
with pytest.warns(DeprecationWarning):
assert ImageCms.VERSION == "1.0.0 pil"
with pytest.warns(DeprecationWarning):
assert isinstance(ImageCms.FLAGS, dict)
profile = ImageCmsProfile(ImageCms.createProfile("sRGB"))
with pytest.warns(DeprecationWarning):
ImageCms.ImageCmsTransform(profile, profile, "RGBA;16B", "RGB")
with pytest.warns(DeprecationWarning):
ImageCms.ImageCmsTransform(profile, profile, "RGB", "RGBA;16B")
with pytest.warns(
DeprecationWarning, match="ImageCms.ImageCmsProfile.product_name"
):
profile.product_name
with pytest.warns(
DeprecationWarning, match="ImageCms.ImageCmsProfile.product_info"
):
profile.product_info
with pytest.raises(AttributeError):
profile.this_attribute_does_not_exist

View File

@ -783,9 +783,10 @@ def test_rectangle_I16(bbox: Coords) -> None:
draw = ImageDraw.Draw(im)
# Act
draw.rectangle(bbox, outline=0xFFFF)
draw.rectangle(bbox, outline=0xCDEF)
# Assert
assert im.getpixel((X0, Y0)) == 0xCDEF
assert_image_equal_tofile(im, "Tests/images/imagedraw_rectangle_I.tiff")
@ -1731,8 +1732,3 @@ def test_incorrectly_ordered_coordinates(xy: tuple[int, int, int, int]) -> None:
draw.rectangle(xy)
with pytest.raises(ValueError):
draw.rounded_rectangle(xy)
def test_getdraw() -> None:
with pytest.warns(DeprecationWarning):
ImageDraw.getdraw(None, [])

View File

@ -151,11 +151,6 @@ class TestImageFile:
# Despite multiple tiles, assert only one tile caused a read of maxblock size
assert reads.count(im.decodermaxblock) == 1
def test_raise_oserror(self) -> None:
with pytest.warns(DeprecationWarning):
with pytest.raises(OSError):
ImageFile.raise_oserror(1)
def test_raise_typeerror(self) -> None:
with pytest.raises(TypeError):
parser = ImageFile.Parser()

View File

@ -11,7 +11,6 @@ from pathlib import Path
from typing import Any, BinaryIO
import pytest
from packaging.version import parse as parse_version
from PIL import Image, ImageDraw, ImageFont, features
from PIL._typing import StrOrBytesPath
@ -267,6 +266,23 @@ def test_render_multiline_text_align(
assert_image_similar_tofile(im, f"Tests/images/multiline_text{ext}.png", 0.01)
def test_render_multiline_text_justify_anchor(
font: ImageFont.FreeTypeFont,
) -> None:
im = Image.new("RGB", (280, 240))
draw = ImageDraw.Draw(im)
for xy, anchor in (((0, 0), "la"), ((140, 80), "ma"), ((280, 160), "ra")):
draw.multiline_text(
xy,
"hey you you are awesome\nthis looks awkward\nthis\nlooks awkward",
font=font,
anchor=anchor,
align="justify",
)
assert_image_equal_tofile(im, "Tests/images/multiline_text_justify_anchor.png")
def test_unknown_align(font: ImageFont.FreeTypeFont) -> None:
im = Image.new(mode="RGB", size=(300, 100))
draw = ImageDraw.Draw(im)
@ -674,16 +690,6 @@ def test_complex_font_settings() -> None:
def test_variation_get(font: ImageFont.FreeTypeFont) -> None:
version = features.version_module("freetype2")
assert version is not None
freetype = parse_version(version)
if freetype < parse_version("2.9.1"):
with pytest.raises(NotImplementedError):
font.get_variation_names()
with pytest.raises(NotImplementedError):
font.get_variation_axes()
return
with pytest.raises(OSError):
font.get_variation_names()
with pytest.raises(OSError):
@ -746,14 +752,6 @@ def _check_text(font: ImageFont.FreeTypeFont, path: str, epsilon: float) -> None
def test_variation_set_by_name(font: ImageFont.FreeTypeFont) -> None:
version = features.version_module("freetype2")
assert version is not None
freetype = parse_version(version)
if freetype < parse_version("2.9.1"):
with pytest.raises(NotImplementedError):
font.set_variation_by_name("Bold")
return
with pytest.raises(OSError):
font.set_variation_by_name("Bold")
@ -773,14 +771,6 @@ def test_variation_set_by_name(font: ImageFont.FreeTypeFont) -> None:
def test_variation_set_by_axes(font: ImageFont.FreeTypeFont) -> None:
version = features.version_module("freetype2")
assert version is not None
freetype = parse_version(version)
if freetype < parse_version("2.9.1"):
with pytest.raises(NotImplementedError):
font.set_variation_by_axes([100])
return
with pytest.raises(OSError):
font.set_variation_by_axes([500, 50])
@ -1175,15 +1165,15 @@ def test_oom(test_file: str) -> None:
def test_raqm_missing_warning(monkeypatch: pytest.MonkeyPatch) -> None:
monkeypatch.setattr(ImageFont.core, "HAVE_RAQM", False)
with pytest.warns(UserWarning) as record:
with pytest.warns(
UserWarning,
match="Raqm layout was requested, but Raqm is not available. "
"Falling back to basic layout.",
):
font = ImageFont.truetype(
FONT_PATH, FONT_SIZE, layout_engine=ImageFont.Layout.RAQM
)
assert font.layout_engine == ImageFont.Layout.BASIC
assert str(record[-1].message) == (
"Raqm layout was requested, but Raqm is not available. "
"Falling back to basic layout."
)
@pytest.mark.parametrize("size", [-1, 0])
@ -1192,15 +1182,3 @@ def test_invalid_truetype_sizes_raise_valueerror(
) -> None:
with pytest.raises(ValueError):
ImageFont.truetype(FONT_PATH, size, layout_engine=layout_engine)
def test_freetype_deprecation(monkeypatch: pytest.MonkeyPatch) -> None:
# Arrange: mock features.version_module to return fake FreeType version
def fake_version_module(module: str) -> str:
return "2.9.0"
monkeypatch.setattr(features, "version_module", fake_version_module)
# Act / Assert
with pytest.warns(DeprecationWarning):
ImageFont.truetype(FONT_PATH, FONT_SIZE)

View File

@ -4,7 +4,11 @@ import pytest
from PIL import Image, ImageDraw, ImageFont
from .helper import assert_image_similar_tofile, skip_unless_feature
from .helper import (
assert_image_equal_tofile,
assert_image_similar_tofile,
skip_unless_feature,
)
FONT_SIZE = 20
FONT_PATH = "Tests/fonts/DejaVuSans/DejaVuSans.ttf"
@ -354,11 +358,27 @@ def test_combine_multiline(anchor: str, align: str) -> None:
d.line(((200, 0), (200, 400)), "gray")
bbox = d.multiline_textbbox((200, 200), text, anchor=anchor, font=f, align=align)
d.rectangle(bbox, outline="red")
d.multiline_text((200, 200), text, fill="black", anchor=anchor, font=f, align=align)
d.multiline_text((200, 200), text, "black", anchor=anchor, font=f, align=align)
assert_image_similar_tofile(im, path, 0.015)
def test_combine_multiline_ttb() -> None:
path = "Tests/images/test_combine_multiline_ttb.png"
f = ImageFont.truetype("Tests/fonts/NotoSans-Regular.ttf", 48)
text = "te\nxt"
im = Image.new("RGB", (400, 400), "white")
d = ImageDraw.Draw(im)
d.line(((0, 200), (400, 200)), "gray")
d.line(((200, 0), (200, 400)), "gray")
bbox = d.multiline_textbbox((200, 200), text, f, direction="ttb")
d.rectangle(bbox, outline="red")
d.multiline_text((200, 200), text, "black", f, direction="ttb")
assert_image_equal_tofile(im, path)
def test_anchor_invalid_ttb() -> None:
font = ImageFont.truetype(FONT_PATH, FONT_SIZE)
im = Image.new("RGB", (100, 100), "white")
@ -378,8 +398,3 @@ def test_anchor_invalid_ttb() -> None:
d.multiline_text((0, 0), "foo\nbar", anchor=anchor, direction="ttb")
with pytest.raises(ValueError):
d.multiline_textbbox((0, 0), "foo\nbar", anchor=anchor, direction="ttb")
# ttb multiline text does not support anchors at all
with pytest.raises(ValueError):
d.multiline_text((0, 0), "foo\nbar", anchor="mm", direction="ttb")
with pytest.raises(ValueError):
d.multiline_textbbox((0, 0), "foo\nbar", anchor="mm", direction="ttb")

View File

@ -7,7 +7,7 @@ import pytest
from PIL import Image, ImageDraw, ImageFont, _util, features
from .helper import assert_image_equal_tofile
from .helper import assert_image_equal_tofile, timeout_unless_slower_valgrind
fonts = [ImageFont.load_default_imagefont()]
if not features.check_module("freetype2"):
@ -72,7 +72,7 @@ def test_decompression_bomb() -> None:
font.getmask("A" * 1_000_000)
@pytest.mark.timeout(4)
@timeout_unless_slower_valgrind(4)
def test_oom() -> None:
glyph = struct.pack(
">hhhhhhhhhh", 1, 0, -32767, -32767, 32767, 32767, -32767, -32767, 32767, 32767

View File

@ -43,6 +43,7 @@ class TestImageGrab:
if (
sys.platform not in ("win32", "darwin")
and not shutil.which("gnome-screenshot")
and not shutil.which("grim")
and not shutil.which("spectacle")
):
with pytest.raises(OSError) as e:

View File

@ -4,7 +4,7 @@ from typing import Any
import pytest
from PIL import Image, ImageMath
from PIL import Image, ImageMath, _imagingmath
def pixel(im: Image.Image | int) -> str | int:
@ -55,11 +55,6 @@ def test_sanity() -> None:
)
def test_options_deprecated() -> None:
with pytest.warns(DeprecationWarning):
assert ImageMath.lambda_eval(lambda args: 1, images) == 1
def test_ops() -> None:
assert pixel(ImageMath.lambda_eval(lambda args: args["A"] * -1, **images)) == "I -1"
@ -505,3 +500,31 @@ def test_logical_not_equal() -> None:
)
== "I 1"
)
def test_reflected_operands() -> None:
assert pixel(ImageMath.lambda_eval(lambda args: 1 + args["A"], **images)) == "I 2"
assert pixel(ImageMath.lambda_eval(lambda args: 1 - args["A"], **images)) == "I 0"
assert pixel(ImageMath.lambda_eval(lambda args: 1 * args["A"], **images)) == "I 1"
assert pixel(ImageMath.lambda_eval(lambda args: 1 / args["A"], **images)) == "I 1"
assert pixel(ImageMath.lambda_eval(lambda args: 1 % args["A"], **images)) == "I 0"
assert pixel(ImageMath.lambda_eval(lambda args: 1 ** args["A"], **images)) == "I 1"
assert pixel(ImageMath.lambda_eval(lambda args: 1 & args["A"], **images)) == "I 1"
assert pixel(ImageMath.lambda_eval(lambda args: 1 | args["A"], **images)) == "I 1"
assert pixel(ImageMath.lambda_eval(lambda args: 1 ^ args["A"], **images)) == "I 0"
def test_unsupported_mode() -> None:
im = Image.new("RGB", (1, 1))
with pytest.raises(ValueError, match="unsupported mode: RGB"):
ImageMath.lambda_eval(lambda args: args["im"] + 1, im=im)
def test_bad_operand_type(monkeypatch: pytest.MonkeyPatch) -> None:
monkeypatch.delattr(_imagingmath, "abs_I")
with pytest.raises(TypeError, match="bad operand type for 'abs'"):
ImageMath.lambda_eval(lambda args: abs(args["I"]), I=I)
monkeypatch.delattr(_imagingmath, "max_F")
with pytest.raises(TypeError, match="bad operand type for 'max'"):
ImageMath.lambda_eval(lambda args: args["max"](args["I"], args["F"]), I=I, F=F)

View File

@ -35,16 +35,6 @@ def test_sanity() -> None:
assert pixel(ImageMath.unsafe_eval("int(float(A)+B)", **images)) == "I 3"
def test_eval_deprecated() -> None:
with pytest.warns(DeprecationWarning):
assert ImageMath.eval("1") == 1
def test_options_deprecated() -> None:
with pytest.warns(DeprecationWarning):
assert ImageMath.unsafe_eval("1", images) == 1
def test_ops() -> None:
assert pixel(ImageMath.unsafe_eval("-A", **images)) == "I -1"
assert pixel(ImageMath.unsafe_eval("+B", **images)) == "L 2"

View File

@ -361,16 +361,6 @@ class TestLibUnpack:
"RGB", "CMYK", 4, (250, 249, 248), (242, 241, 240), (234, 233, 233)
)
def test_BGR(self) -> None:
with pytest.warns(DeprecationWarning):
self.assert_unpack(
"BGR;15", "BGR;15", 3, (8, 131, 0), (24, 0, 8), (41, 131, 8)
)
self.assert_unpack(
"BGR;16", "BGR;16", 3, (8, 64, 0), (24, 129, 0), (41, 194, 0)
)
self.assert_unpack("BGR;24", "BGR;24", 3, (1, 2, 3), (4, 5, 6), (7, 8, 9))
def test_RGBA(self) -> None:
self.assert_unpack("RGBA", "LA", 2, (1, 1, 1, 2), (3, 3, 3, 4), (5, 5, 5, 6))
self.assert_unpack(

View File

@ -7,6 +7,7 @@ import sys
import pytest
@pytest.mark.skipif(sys.platform == "ios", reason="Processes not supported on iOS")
@pytest.mark.parametrize(
"args, report",
((["PIL"], False), (["PIL", "--report"], True), (["PIL.report"], True)),

View File

@ -1,7 +1,6 @@
from __future__ import annotations
import warnings
from typing import TYPE_CHECKING
import pytest
@ -9,6 +8,7 @@ from PIL import Image, _typing
from .helper import assert_deep_equal, assert_image, hopper, skip_unless_feature
TYPE_CHECKING = False
if TYPE_CHECKING:
import numpy
import numpy.typing as npt

View File

@ -162,3 +162,13 @@ def test_pickle_font_file(tmp_path: Path, protocol: int) -> None:
# Assert
helper_assert_pickled_font_images(font, unpickled_font)
def test_load_earlier_data() -> None:
im = pickle.loads(
b"\x80\x04\x95@\x00\x00\x00\x00\x00\x00\x00\x8c\x12PIL.PngImagePlugin"
b"\x94\x8c\x0cPngImageFile\x94\x93\x94)\x81\x94]\x94(}\x94\x8c\x01L\x94K\x01"
b"K\x01\x86\x94NC\x01\x00\x94eb."
)
assert im.mode == "L"
assert im.size == (1, 1)

View File

@ -1,6 +1,6 @@
from __future__ import annotations
from typing import Any # undone
from typing import Any, NamedTuple
import pytest
@ -10,30 +10,73 @@ from .helper import (
assert_deep_equal,
assert_image_equal,
hopper,
is_big_endian,
)
pyarrow = pytest.importorskip("pyarrow", reason="PyArrow not installed")
TYPE_CHECKING = False
if TYPE_CHECKING:
import pyarrow
else:
pyarrow = pytest.importorskip("pyarrow", reason="PyArrow not installed")
TEST_IMAGE_SIZE = (10, 10)
def _test_img_equals_pyarray(
img: Image.Image, arr: Any, mask: list[int] | None
img: Image.Image, arr: Any, mask: list[int] | None, elts_per_pixel: int = 1
) -> None:
assert img.height * img.width == len(arr)
assert img.height * img.width * elts_per_pixel == len(arr)
px = img.load()
assert px is not None
if elts_per_pixel > 1 and mask is None:
# have to do element-wise comparison when we're comparing
# flattened r,g,b,a to a pixel.
mask = list(range(elts_per_pixel))
for x in range(0, img.size[0], int(img.size[0] / 10)):
for y in range(0, img.size[1], int(img.size[1] / 10)):
if mask:
pixel = px[x, y]
assert isinstance(pixel, tuple)
for ix, elt in enumerate(mask):
pixel = px[x, y]
assert isinstance(pixel, tuple)
assert pixel[ix] == arr[y * img.width + x].as_py()[elt]
if elts_per_pixel == 1:
assert pixel[ix] == arr[y * img.width + x].as_py()[elt]
else:
assert (
pixel[ix]
== arr[(y * img.width + x) * elts_per_pixel + elt].as_py()
)
else:
assert_deep_equal(px[x, y], arr[y * img.width + x].as_py())
def _test_img_equals_int32_pyarray(
img: Image.Image, arr: Any, mask: list[int] | None, elts_per_pixel: int = 1
) -> None:
assert img.height * img.width * elts_per_pixel == len(arr)
px = img.load()
assert px is not None
if mask is None:
# have to do element-wise comparison when we're comparing
# flattened rgba in an uint32 to a pixel.
mask = list(range(elts_per_pixel))
for x in range(0, img.size[0], int(img.size[0] / 10)):
for y in range(0, img.size[1], int(img.size[1] / 10)):
pixel = px[x, y]
assert isinstance(pixel, tuple)
arr_pixel_int = arr[y * img.width + x].as_py()
arr_pixel_tuple = (
arr_pixel_int % 256,
(arr_pixel_int // 256) % 256,
(arr_pixel_int // 256**2) % 256,
(arr_pixel_int // 256**3),
)
if is_big_endian():
arr_pixel_tuple = arr_pixel_tuple[::-1]
for ix, elt in enumerate(mask):
assert pixel[ix] == arr_pixel_tuple[elt]
# really hard to get a non-nullable list type
fl_uint8_4_type = pyarrow.field(
"_", pyarrow.list_(pyarrow.field("_", pyarrow.uint8()).with_nullable(False), 4)
@ -55,14 +98,14 @@ fl_uint8_4_type = pyarrow.field(
("HSV", fl_uint8_4_type, [0, 1, 2]),
),
)
def test_to_array(mode: str, dtype: Any, mask: list[int] | None) -> None:
def test_to_array(mode: str, dtype: pyarrow.DataType, mask: list[int] | None) -> None:
img = hopper(mode)
# Resize to non-square
img = img.crop((3, 0, 124, 127))
assert img.size == (121, 127)
arr = pyarrow.array(img)
arr = pyarrow.array(img) # type: ignore[call-overload]
_test_img_equals_pyarray(img, arr, mask)
assert arr.type == dtype
@ -79,8 +122,8 @@ def test_lifetime() -> None:
img = hopper("L")
arr_1 = pyarrow.array(img)
arr_2 = pyarrow.array(img)
arr_1 = pyarrow.array(img) # type: ignore[call-overload]
arr_2 = pyarrow.array(img) # type: ignore[call-overload]
del img
@ -97,8 +140,8 @@ def test_lifetime2() -> None:
img = hopper("L")
arr_1 = pyarrow.array(img)
arr_2 = pyarrow.array(img)
arr_1 = pyarrow.array(img) # type: ignore[call-overload]
arr_2 = pyarrow.array(img) # type: ignore[call-overload]
assert arr_1.sum().as_py() > 0
del arr_1
@ -110,3 +153,94 @@ def test_lifetime2() -> None:
px = img2.load()
assert px # make mypy happy
assert isinstance(px[0, 0], int)
class DataShape(NamedTuple):
dtype: pyarrow.DataType
# Strictly speaking, elt should be a pixel or pixel component, so
# list[uint8][4], float, int, uint32, uint8, etc. But more
# correctly, it should be exactly the dtype from the line above.
elt: Any
elts_per_pixel: int
UINT_ARR = DataShape(
dtype=fl_uint8_4_type,
elt=[1, 2, 3, 4], # array of 4 uint8 per pixel
elts_per_pixel=1, # only one array per pixel
)
UINT = DataShape(
dtype=pyarrow.uint8(),
elt=3, # one uint8,
elts_per_pixel=4, # but repeated 4x per pixel
)
UINT32 = DataShape(
dtype=pyarrow.uint32(),
elt=0xABCDEF45, # one packed int, doesn't fit in a int32 > 0x80000000
elts_per_pixel=1, # one per pixel
)
INT32 = DataShape(
dtype=pyarrow.uint32(),
elt=0x12CDEF45, # one packed int
elts_per_pixel=1, # one per pixel
)
@pytest.mark.parametrize(
"mode, data_tp, mask",
(
("L", DataShape(pyarrow.uint8(), 3, 1), None),
("I", DataShape(pyarrow.int32(), 1 << 24, 1), None),
("F", DataShape(pyarrow.float32(), 3.14159, 1), None),
("LA", UINT_ARR, [0, 3]),
("LA", UINT, [0, 3]),
("RGB", UINT_ARR, [0, 1, 2]),
("RGBA", UINT_ARR, None),
("CMYK", UINT_ARR, None),
("YCbCr", UINT_ARR, [0, 1, 2]),
("HSV", UINT_ARR, [0, 1, 2]),
("RGB", UINT, [0, 1, 2]),
("RGBA", UINT, None),
("CMYK", UINT, None),
("YCbCr", UINT, [0, 1, 2]),
("HSV", UINT, [0, 1, 2]),
),
)
def test_fromarray(mode: str, data_tp: DataShape, mask: list[int] | None) -> None:
(dtype, elt, elts_per_pixel) = data_tp
ct_pixels = TEST_IMAGE_SIZE[0] * TEST_IMAGE_SIZE[1]
arr = pyarrow.array([elt] * (ct_pixels * elts_per_pixel), type=dtype)
img = Image.fromarrow(arr, mode, TEST_IMAGE_SIZE)
_test_img_equals_pyarray(img, arr, mask, elts_per_pixel)
@pytest.mark.parametrize(
"mode, data_tp, mask",
(
("LA", UINT32, [0, 3]),
("RGB", UINT32, [0, 1, 2]),
("RGBA", UINT32, None),
("CMYK", UINT32, None),
("YCbCr", UINT32, [0, 1, 2]),
("HSV", UINT32, [0, 1, 2]),
("LA", INT32, [0, 3]),
("RGB", INT32, [0, 1, 2]),
("RGBA", INT32, None),
("CMYK", INT32, None),
("YCbCr", INT32, [0, 1, 2]),
("HSV", INT32, [0, 1, 2]),
),
)
def test_from_int32array(mode: str, data_tp: DataShape, mask: list[int] | None) -> None:
(dtype, elt, elts_per_pixel) = data_tp
ct_pixels = TEST_IMAGE_SIZE[0] * TEST_IMAGE_SIZE[1]
arr = pyarrow.array([elt] * (ct_pixels * elts_per_pixel), type=dtype)
img = Image.fromarrow(arr, mode, TEST_IMAGE_SIZE)
_test_img_equals_int32_pyarray(img, arr, mask, elts_per_pixel)

View File

@ -23,11 +23,5 @@ def test_pyroma() -> None:
)
else:
# Should have a perfect score, but pyroma does not support PEP 639 yet.
assert rating == (
9,
[
"Your package does neither have a license field "
"nor any license classifiers."
],
)
# Should have a perfect score
assert rating == (10, [])

View File

@ -1,7 +1,7 @@
from __future__ import annotations
from pathlib import Path
from typing import TYPE_CHECKING, Union
from typing import Union
import pytest
@ -9,6 +9,7 @@ from PIL import Image, ImageQt
from .helper import assert_image_equal_tofile, assert_image_similar, hopper
TYPE_CHECKING = False
if TYPE_CHECKING:
import PyQt6
import PySide6

View File

@ -9,7 +9,7 @@ import pytest
from PIL import GifImagePlugin, Image, JpegImagePlugin
from .helper import cjpeg_available, djpeg_available, is_win32, netpbm_available
from .helper import djpeg_available, is_win32, netpbm_available
TEST_JPG = "Tests/images/hopper.jpg"
TEST_GIF = "Tests/images/hopper.gif"
@ -42,11 +42,6 @@ class TestShellInjection:
assert isinstance(im, JpegImagePlugin.JpegImageFile)
im.load_djpeg()
@pytest.mark.skipif(not cjpeg_available(), reason="cjpeg not available")
def test_save_cjpeg_filename(self, tmp_path: Path) -> None:
with Image.open(TEST_JPG) as im:
self.assert_save_filename_check(tmp_path, im, JpegImagePlugin._save_cjpeg)
@pytest.mark.skipif(not netpbm_available(), reason="Netpbm not available")
def test_save_netpbm_filename_bmp_mode(self, tmp_path: Path) -> None:
with Image.open(TEST_GIF) as im:

View File

@ -52,3 +52,17 @@ def test_tiff_crashes(test_file: str) -> None:
pytest.skip("test image not found")
except OSError:
pass
def test_tiff_mmap() -> None:
try:
with Image.open("Tests/images/crash_mmap.tif") as im:
im.seek(1)
im.load()
im.seek(0)
im.load()
except FileNotFoundError:
if on_ci():
raise
pytest.skip("test image not found")

Some files were not shown because too many files have changed in this diff Show More