Compare commits

...

182 Commits
12.0.0 ... main

Author SHA1 Message Date
Hugo van Kemenade
627d8743b7
Simplify test code (#9382) 2026-01-06 14:21:49 +02:00
Andrew Murray
dcd52ebf65 Simplified code 2026-01-06 09:56:56 +11:00
Andrew Murray
d6e0a8d174
[pre-commit.ci] pre-commit autoupdate (#9381) 2026-01-06 09:33:57 +11:00
pre-commit-ci[bot]
2210714a43
[pre-commit.ci] pre-commit autoupdate
updates:
- [github.com/astral-sh/ruff-pre-commit: v0.14.7 → v0.14.10](https://github.com/astral-sh/ruff-pre-commit/compare/v0.14.7...v0.14.10)
- [github.com/psf/black-pre-commit-mirror: 25.11.0 → 25.12.0](https://github.com/psf/black-pre-commit-mirror/compare/25.11.0...25.12.0)
- [github.com/pre-commit/mirrors-clang-format: v21.1.6 → v21.1.8](https://github.com/pre-commit/mirrors-clang-format/compare/v21.1.6...v21.1.8)
- [github.com/python-jsonschema/check-jsonschema: 0.35.0 → 0.36.0](https://github.com/python-jsonschema/check-jsonschema/compare/0.35.0...0.36.0)
- [github.com/zizmorcore/zizmor-pre-commit: v1.18.0 → v1.19.0](https://github.com/zizmorcore/zizmor-pre-commit/compare/v1.18.0...v1.19.0)
- [github.com/tox-dev/tox-ini-fmt: 1.7.0 → 1.7.1](https://github.com/tox-dev/tox-ini-fmt/compare/1.7.0...1.7.1)
2026-01-05 17:20:09 +00:00
Hugo van Kemenade
3d7801417a
Move from deprecated getdata to get_flattened_data (#9373) 2026-01-04 15:36:32 +02:00
Andrew Murray
a85d3b135d
Only update Python palette when loading an image if rawmode was different (#9309) 2026-01-04 06:20:56 +11:00
Andrew Murray
932aa68d2a
Add seven-day cooldown to Renovate (#9380) 2026-01-04 05:22:19 +11:00
Hugo van Kemenade
fe236d77a5 Add seven-day cooldown to Renovate 2026-01-03 11:32:19 +02:00
Andrew Murray
bc0e2c0e61
Remove add-imaging-libs option from setup.py (#9378)
Co-authored-by: Alexander Karpinsky <homm86@gmail.com>
2026-01-03 20:18:57 +11:00
Hugo van Kemenade
e66dd607f0
Update xorgproto to 2025.1 (#9379) 2026-01-03 10:56:55 +02:00
Hugo van Kemenade
d5d8a91597
Replace shell: cmd with shell: bash (#9359) 2026-01-03 10:12:48 +02:00
Andrew Murray
b8351fde41
Added type hints to map_metadata_keys() (#9337) 2026-01-03 17:08:17 +11:00
Andrew Murray
36cf82ae76 Updated xorgproto to 2025.1 2026-01-03 16:25:37 +11:00
renovate[bot]
525842215f
Update dependency mypy to v1.19.1 (#9374) 2026-01-03 13:59:38 +11:00
renovate[bot]
844b10f894
Update github-actions (#9375) 2026-01-03 13:55:50 +11:00
Andrew Murray
555fb8371c Move from deprecated getdata to get_flattened_data 2026-01-03 08:16:37 +11:00
Hugo van Kemenade
0a1d6c3c61
Remove Sphinx dependency from mypy (#9370) 2026-01-02 18:30:53 +02:00
mergify[bot]
00ec73dfd1
Fix unclosed file warning (#9371) 2026-01-02 12:33:25 +00:00
Andrew Murray
e924cfd181 Fix unclosed file warning 2026-01-02 21:32:22 +11:00
Hugo van Kemenade
2360d0df17 Revert "Use minimum supported Python version for Lint (#9364)"
This reverts commit 900636e7db.
2026-01-02 12:31:22 +02:00
Hugo van Kemenade
499b796556 Remove Sphinx dependency from mypy 2026-01-02 12:30:14 +02:00
Andrew Murray
5b677ca1c6 Assert palette is not None 2026-01-02 20:31:47 +11:00
Andrew Murray
b71109d435
Merge branch 'main' into load_palette 2026-01-02 20:21:23 +11:00
Andrew Murray
4337139f0c 12.2.0.dev0 version bump 2026-01-02 20:16:49 +11:00
Andrew Murray
46f45f674d 12.1.0 version bump 2026-01-02 17:03:05 +11:00
Hugo van Kemenade
c9ac097edb
Simplify band splitting (#9291) 2026-01-02 07:42:46 +02:00
Andrew Murray
3baedf2648
Deprecate getdata(), in favour of new get_flattened_data() (#9292) 2026-01-02 10:59:56 +11:00
Hugo van Kemenade
b51a036685
Specify APNG duration type when opening (#9368) 2026-01-01 23:28:16 +02:00
Hugo van Kemenade
8d08e31533
Add release notes for #9348 (#9369) 2026-01-01 20:24:18 +02:00
Andrew Murray
432707ea81 Added release notes for #9348 2026-01-02 04:18:15 +11:00
Andrew Murray
2d589107fb Specify APNG duration type when opening 2026-01-02 03:49:56 +11:00
Hugo van Kemenade
8dee8dd5ba
Add ImageFile context manager (#9367) 2026-01-01 15:50:26 +02:00
Hugo van Kemenade
b2d9bc3c76
Support saving APNG float durations (#9365) 2026-01-01 15:49:03 +02:00
Hugo van Kemenade
f130c10a9c
Allow 1 mode images in MorphOp (#9348) 2026-01-01 15:30:47 +02:00
Andrew Murray
ce11a0c499 Added ImageFile context manager 2026-01-01 20:31:22 +11:00
Andrew Murray
51b35d17e1 Added fp type hint 2026-01-01 20:31:22 +11:00
Andrew Murray
a868c29eb1
Assert fp is not None (#8617) 2026-01-01 20:01:38 +11:00
Andrew Murray
43f8efad79
Added release notes for #9350 (#9366)
Co-authored-by: Hugo van Kemenade <1324225+hugovk@users.noreply.github.com>
2026-01-01 18:41:34 +11:00
Andrew Murray
91f219fdcf Support saving float durations 2026-01-01 17:32:59 +11:00
Andrew Murray
900636e7db
Use minimum supported Python version for Lint (#9364) 2026-01-01 17:31:36 +11:00
Andrew Murray
d62955031b
Allow for duplicate font variation styles (#9362)
Co-authored-by: Hugo van Kemenade <1324225+hugovk@users.noreply.github.com>
2026-01-01 08:53:04 +11:00
Andrew Murray
2ebfe30ae3
Added return type to ImageFile _close_fp() (#9356) 2025-12-31 14:47:50 +02:00
Andrew Murray
19910ed03e
Call parent verify method (#9357) 2025-12-31 14:47:33 +02:00
Andrew Murray
6b892c495c Merge branch 'main' into imagemorph_get_on_pixels 2025-12-31 23:10:34 +11:00
Andrew Murray
0a9a47fb9b
Update ImageMorph documentation (#9349)
Co-authored-by: Andrew Murray <radarhere@users.noreply.github.com>
Co-authored-by: Hugo van Kemenade <1324225+hugovk@users.noreply.github.com>
2025-12-31 14:02:31 +02:00
Andrew Murray
15c9d11f35
Replace pre-commit with prek (#9360) 2025-12-31 14:32:39 +11:00
Hugo van Kemenade
81e80f7a50 Install and run tox/lint/mypy via uv 2025-12-29 19:35:41 +02:00
Hugo van Kemenade
72931475f2 Replace shell: cmd with shell: bash 2025-12-29 14:57:25 +02:00
Andrew Murray
79357a2718 Revert "Disable https://docs.zizmor.sh/audits/#obfuscation"
This reverts commit 9342e209b2.
2025-12-29 14:44:12 +02:00
Andrew Murray
3abb62ed29 Do not use cmd shell 2025-12-29 14:44:03 +02:00
Hugo van Kemenade
080afe1bf7 Replace pre-commit with prek 2025-12-28 23:46:02 +02:00
Andrew Murray
2ebb3e9964
Use different variables for Image and ImageFile instances (#9316) 2025-12-28 23:09:46 +02:00
Andrew Murray
a04c9806b1
Return LUT from LutBuilder build_default_lut() (#9350) 2025-12-28 23:03:47 +02:00
Andrew Murray
faa843e9c2
Simplify WebP code (#9329) 2025-12-28 23:01:23 +02:00
Andrew Murray
66e3d65a72
Update harfbuzz to 12.3.0 (#9355) 2025-12-28 17:04:44 +02:00
Andrew Murray
4be5b8a2fb
Use unsigned long for DWORD (#9352) 2025-12-28 07:34:57 +11:00
Andrew Murray
e85700fe48
Test PyQt6 on Python 3.14 on Windows (#9353) 2025-12-27 15:54:10 +02:00
Andrew Murray
a704711404 Allow 1 mode images in apply() and match() 2025-12-23 14:13:51 +11:00
Andrew Murray
9b7200d2b4 Allow 1 mode images in MorphOp get_on_pixels() 2025-12-23 12:50:26 +11:00
Andrew Murray
ca21683316
Cast to UINT32 before shifting bits (#9347) 2025-12-22 18:12:10 +11:00
Hugo van Kemenade
4cbef1667f
Revert "Pin docutils to 0.21 (#9344)" (#9346) 2025-12-22 08:09:20 +02:00
Andrew Murray
9dd756f9fe Revert "Pin docutils to 0.21 (#9344)"
This reverts commit 6df6cd4480.
2025-12-22 09:34:03 +11:00
Hugo van Kemenade
00e2198eeb
Test 32-bit Windows on Windows Server 2022 (#9345) 2025-12-21 23:36:12 +02:00
Andrew Murray
9d3555c37e Test Windows Server 2022 2025-12-21 22:39:19 +11:00
Andrew Murray
6df6cd4480
Pin docutils to 0.21 (#9344) 2025-12-20 23:51:05 +11:00
Hugo van Kemenade
205e52b1ee
Update xz to 5.8.2 (#9343) 2025-12-18 00:43:51 +02:00
Andrew Murray
6bf4313a68 Updated xz to 5.8.2 2025-12-18 07:44:40 +11:00
Hugo van Kemenade
8494b06c71
Correct variable type (#9335) 2025-12-11 18:38:35 +02:00
Andrew Murray
6a769da21b Corrected variable type 2025-12-11 23:27:29 +11:00
Andrew Murray
2c6fd36f10
Docs: update major bump cadence (#9334) 2025-12-11 20:02:54 +11:00
Hugo van Kemenade
c0b8c2f0a2
Updated libjpeg-turbo to 3.1.3 (#9333) 2025-12-11 09:47:37 +02:00
Hugo van Kemenade
79ae888d45 Docs: update major bump cadence 2025-12-11 09:29:54 +02:00
Andrew Murray
b3da65df94 Updated libjpeg-turbo to 3.1.3 2025-12-11 10:53:04 +11:00
Hugo van Kemenade
1f424efd25
Updated zlib-ng to 2.3.2 (#9324) 2025-12-10 23:13:28 +02:00
Andrew Murray
374957cefd
Fix ResourceWarnings in selftest.py (#9332) 2025-12-11 07:45:47 +11:00
Hugo van Kemenade
6d493aa817
Fix testing good P mode BMP images (#9319) 2025-12-10 22:04:23 +02:00
Hugo van Kemenade
33204aac4d
Add release notes for #9070 (#9320) 2025-12-10 21:59:36 +02:00
Hugo van Kemenade
4eb7cd6f29
Improve type hints (#9317) 2025-12-10 21:58:50 +02:00
Hugo van Kemenade
76532808f4 Fix ResourceWarning in selftest.py 2025-12-10 15:26:14 +02:00
Andrew Murray
3332c1d82e
Updated libpng to 1.6.53 (#9325) 2025-12-10 18:23:16 +11:00
Andrew Murray
b3d7263f74
Test Python 3.15 pre-release (#9331) 2025-12-07 22:28:13 +11:00
Hugo van Kemenade
a01fa7d08e Test Python 3.15 pre-release 2025-12-07 11:12:38 +02:00
Andrew Murray
db7a994ad6 Updated libpng to 1.6.53 2025-12-06 10:15:33 +11:00
Andrew Murray
07fee96880
[pre-commit.ci] pre-commit autoupdate (#9318) 2025-12-05 20:53:18 +11:00
Andrew Murray
fd1ddd6d56 Use consistent type 2025-12-03 22:46:42 +11:00
Andrew Murray
7c3ece07c9 Changed type so that im has fp attribute 2025-12-03 22:46:42 +11:00
Andrew Murray
61b1c3c841 Do not change variable type 2025-12-03 22:46:42 +11:00
Andrew Murray
46ac30aa80 Use different variables for Image and ImageFile instances 2025-12-03 22:46:42 +11:00
Andrew Murray
4024f0287d Assert image type 2025-12-03 22:46:42 +11:00
Andrew Murray
4d511d86ed Changed argument type to match use 2025-12-03 22:46:42 +11:00
Andrew Murray
fd3d44d2ef Updated zlib-ng to 2.3.2 2025-12-03 22:38:32 +11:00
Andrew Murray
24b1702360
Update actions/checkout action to v6 (#9323) 2025-12-03 20:09:44 +11:00
mergify[bot]
ae45187719
Update dependency mypy to v1.19.0 (#9322) 2025-12-03 08:47:56 +00:00
renovate[bot]
b633f49b9c
Update actions/checkout action to v6 2025-12-03 07:14:19 +00:00
renovate[bot]
7adecb792c
Update dependency mypy to v1.19.0 2025-12-03 07:14:12 +00:00
Andrew Murray
dbc5a4fc90 Added release notes for #9070 2025-12-02 22:26:23 +11:00
Andrew Murray
b3d9ba8e88 Removed unused files 2025-12-02 10:42:14 +11:00
Andrew Murray
47c6aae0ca Fixed testing good P mode BMP images 2025-12-02 10:40:43 +11:00
Andrew Murray
9342e209b2 Disable https://docs.zizmor.sh/audits/#obfuscation 2025-12-02 10:21:55 +11:00
pre-commit-ci[bot]
ce3e085751
[pre-commit.ci] pre-commit autoupdate
updates:
- [github.com/astral-sh/ruff-pre-commit: v0.14.3 → v0.14.7](https://github.com/astral-sh/ruff-pre-commit/compare/v0.14.3...v0.14.7)
- [github.com/psf/black-pre-commit-mirror: 25.9.0 → 25.11.0](https://github.com/psf/black-pre-commit-mirror/compare/25.9.0...25.11.0)
- [github.com/PyCQA/bandit: 1.8.6 → 1.9.2](https://github.com/PyCQA/bandit/compare/1.8.6...1.9.2)
- [github.com/pre-commit/mirrors-clang-format: v21.1.2 → v21.1.6](https://github.com/pre-commit/mirrors-clang-format/compare/v21.1.2...v21.1.6)
- [github.com/python-jsonschema/check-jsonschema: 0.34.1 → 0.35.0](https://github.com/python-jsonschema/check-jsonschema/compare/0.34.1...0.35.0)
- [github.com/zizmorcore/zizmor-pre-commit: v1.16.2 → v1.18.0](https://github.com/zizmorcore/zizmor-pre-commit/compare/v1.16.2...v1.18.0)
- [github.com/sphinx-contrib/sphinx-lint: v1.0.1 → v1.0.2](https://github.com/sphinx-contrib/sphinx-lint/compare/v1.0.1...v1.0.2)
- [github.com/tox-dev/pyproject-fmt: v2.11.0 → v2.11.1](https://github.com/tox-dev/pyproject-fmt/compare/v2.11.0...v2.11.1)
2025-12-01 17:26:21 +00:00
Andrew Murray
b0a5bc2a6b
Allow window ID to be passed to ImageGrab.grab() on macOS (#9070) 2025-12-01 20:42:09 +11:00
Andrew Murray
370da461cf
Updated libpng to 1.6.51 (#9305)
Co-authored-by: Hugo van Kemenade <1324225+hugovk@users.noreply.github.com>
2025-12-01 10:02:09 +11:00
Hugo van Kemenade
77e16b1030
Apply encoder options when saving multiple PNG frames (#9300) 2025-11-30 22:21:42 +02:00
Hugo van Kemenade
2150f088ed
Read all non-zero transparency from mode 1 PNG images as 255 (#9282) 2025-11-30 22:20:38 +02:00
Hugo van Kemenade
972bb4c39b
Test ImageFont.ImageFont, in case freetype2 is not supported (#9287) 2025-11-30 22:20:15 +02:00
Hugo van Kemenade
c9095cb02a
Support writing IFD, SIGNED_RATIONAL and InkNames TIFF tags (#9276) 2025-11-30 22:14:18 +02:00
Hugo van Kemenade
416f02338b
Remove unused modes (#9275) 2025-11-30 22:12:22 +02:00
Hugo van Kemenade
89795df94f
Use different variables for Image and ImageFile instances (#9268) 2025-11-30 22:11:22 +02:00
Hugo van Kemenade
93aa55cece
Updated brotli to 1.2.0 (#9284) 2025-11-30 22:08:30 +02:00
Hugo van Kemenade
8d7dc9db5b
Update libimagequant to 4.4.1 (#9301) 2025-11-30 22:07:42 +02:00
Hugo van Kemenade
4a733e5092
Correct allocating new color to RGBA palette (#9313) 2025-11-30 21:57:22 +02:00
Hugo van Kemenade
da76f6d99b
Close image on ImageFont exception (#9304) 2025-11-30 21:56:35 +02:00
Hugo van Kemenade
65c32ecca4
retina -> Retina 2025-11-30 21:55:59 +02:00
Hugo van Kemenade
5543e85ad2
Update zlib-ng to 2.3.1, except on manylinux2014 aarch64 (#9312) 2025-11-29 11:13:07 +02:00
Andrew Murray
37da2ba381 Corrected allocating new color to RGBA palette 2025-11-29 17:22:44 +11:00
Andrew Murray
8814d42fd9 Update zlib-ng to 2.3.1, except on manylinux2014 aarch64 2025-11-29 14:24:43 +11:00
Andrew Murray
d06c8b3591 Test drawing a new color onto a dirty palette 2025-11-27 13:12:42 +11:00
Andrew Murray
6a9960e8c1 Only update Python palette if rawmode was different to the mode 2025-11-25 23:40:34 +11:00
Hugo van Kemenade
ec40c546d7
Updated Ubuntu version (#9306) 2025-11-22 13:40:52 +02:00
Andrew Murray
7055937eb1 Updated Ubuntu version 2025-11-22 17:47:09 +11:00
Andrew Murray
cce73b1e89 Close image on ImageFont exception 2025-11-19 21:52:21 +11:00
Andrew Murray
88247a9ef3
Updated version 2025-11-19 21:31:27 +11:00
Andrew Murray
71b3e5c015
Updated harfbuzz to 12.2.0 (#9289) 2025-11-18 18:00:44 +11:00
Andrew Murray
75280b8b0f
Merge branch 'main' into brotli 2025-11-15 19:59:23 +11:00
Andrew Murray
6107b9e82d Update libimagequant to 4.4.1 2025-11-15 07:41:59 +11:00
Hugo van Kemenade
38c6c478e0
Reapply "Use macos-latest for iOS arm64 simulator" (#9259) 2025-11-14 14:17:40 +02:00
Andrew Murray
bc1237ef3d Update dependency cibuildwheel to v3.3.0 2025-11-14 21:22:58 +11:00
Andrew Murray
142c1320b2 Apply encoder options when saving multiple PNG frames 2025-11-14 20:08:49 +11:00
Andrew Murray
3b8fd040de
Escape period in pre-commit-config (#9036) 2025-11-10 22:35:49 +11:00
Andrew Murray
8fbb801275
Add Apache-2.0 notice to IcoImagePlugin (#8947) 2025-11-10 21:56:10 +11:00
Andrew Murray
921a470506 Simplified code 2025-11-06 18:24:10 +11:00
Hugo van Kemenade
b33a31524a
Add Fedora 43 (#9290) 2025-11-05 19:24:43 +02:00
Andrew Murray
18c7f87fe3 Added Fedora 43 2025-11-05 23:21:46 +11:00
Andrew Murray
e44ce2f00e Updated harfbuzz to 12.2.0 2025-11-05 19:09:49 +11:00
Andrew Murray
b59e031256
[pre-commit.ci] pre-commit autoupdate (#9288) 2025-11-04 08:03:42 +11:00
Hugo van Kemenade
666dd52478 Drop removed rule 2025-11-03 19:56:21 +02:00
pre-commit-ci[bot]
85d783fb52
[pre-commit.ci] pre-commit autoupdate
updates:
- [github.com/astral-sh/ruff-pre-commit: v0.13.3 → v0.14.3](https://github.com/astral-sh/ruff-pre-commit/compare/v0.13.3...v0.14.3)
- [github.com/python-jsonschema/check-jsonschema: 0.34.0 → 0.34.1](https://github.com/python-jsonschema/check-jsonschema/compare/0.34.0...0.34.1)
- [github.com/zizmorcore/zizmor-pre-commit: v1.14.2 → v1.16.2](https://github.com/zizmorcore/zizmor-pre-commit/compare/v1.14.2...v1.16.2)
- [github.com/sphinx-contrib/sphinx-lint: v1.0.0 → v1.0.1](https://github.com/sphinx-contrib/sphinx-lint/compare/v1.0.0...v1.0.1)
- [github.com/tox-dev/pyproject-fmt: v2.7.0 → v2.11.0](https://github.com/tox-dev/pyproject-fmt/compare/v2.7.0...v2.11.0)
- [github.com/tox-dev/tox-ini-fmt: 1.6.0 → 1.7.0](https://github.com/tox-dev/tox-ini-fmt/compare/1.6.0...1.7.0)
2025-11-03 17:20:17 +00:00
Andrew Murray
b3d9bd9950 Test ImageFont.ImageFont, in case freetype2 is not supported 2025-11-03 23:07:15 +11:00
Andrew Murray
1a27f958d7 Updated brotli to 1.2.0 2025-10-31 18:19:05 +11:00
Andrew Murray
dfd24ba615 Read all non-zero transparency from mode 1 images in the same way 2025-10-30 22:03:39 +11:00
Hugo van Kemenade
e36e67081a
Simplify code now that I;16* modes are the only IMAGING_TYPE_SPECIAL (#9263) 2025-10-27 15:19:54 +02:00
Hugo van Kemenade
b90a00eccb
Remove BytesIO from DdsImagePlugin (#9273) 2025-10-27 15:16:27 +02:00
Hugo van Kemenade
ce3323afa9
Update github-actions (#9277) 2025-10-27 07:23:50 +02:00
renovate[bot]
29c5ffe745
Update github-actions 2025-10-27 02:48:10 +00:00
Hugo van Kemenade
cc4ca5bf17
Added type hints (#9269) 2025-10-24 16:56:36 +03:00
Hugo van Kemenade
148a19eee4
Fix ZeroDivisionError in DdsImagePlugin (#9272) 2025-10-24 16:44:46 +03:00
Hugo van Kemenade
a63ba0e3b6
Correct __getitem__ return type (#9264) 2025-10-24 16:42:14 +03:00
Andrew Murray
82cdaa456c Support writing SIGNED_RATIONAL tag types 2025-10-24 03:55:45 +11:00
Andrew Murray
ddd4f00720 Support writing IFD tag types 2025-10-23 20:03:14 +11:00
Andrew Murray
b04d8792f5 Support writing InkNames 2025-10-23 08:53:00 +11:00
Andrew Murray
109ee1569d Removed I;32L rawmode 2025-10-22 22:24:15 +11:00
Andrew Murray
208bbe95f9 Remove I;32L and I;32B modes 2025-10-22 22:22:00 +11:00
Andrew Murray
b1e2f2e652 Improved coverage 2025-10-22 20:08:22 +11:00
Andrew Murray
7d6f2ce90b Removed BytesIO 2025-10-21 23:35:17 +11:00
Andrew Murray
e1f4352ce9 Fixed ZeroDivisionError 2025-10-21 23:11:18 +11:00
Andrew Murray
e90bb1559c Rearranged code 2025-10-20 21:00:29 +11:00
Andrew Murray
4b90888a7d Added type hints 2025-10-20 19:38:29 +11:00
Andrew Murray
51e3fe45bf Use different variables for Image and ImageFile instances 2025-10-20 19:18:00 +11:00
Hugo van Kemenade
76f04b46c5
Fix warnings (#9257) 2025-10-17 21:18:10 +03:00
Hugo van Kemenade
2d23257595
Update macOS tested Pillow versions (#9265) 2025-10-17 15:29:08 +03:00
Andrew Murray
03d48f4011 Updated macOS tested Pillow versions 2025-10-17 23:05:33 +11:00
Andrew Murray
e969fa7aea Correct __getitem__ return type 2025-10-17 06:14:02 +11:00
Andrew Murray
ae43b36030 Simplified code now that I;16* modes are the only IMAGING_TYPE_SPECIAL 2025-10-16 20:55:56 +11:00
Hugo van Kemenade
5122c8356d
Remove Fedora 41 (#9260) 2025-10-16 07:19:52 +03:00
Yan-Ke Guo
424168b69c
Merge branch 'main' into main 2025-10-16 09:46:04 +08:00
Andrew Murray
ae7d28eddb Removed Fedora 41 2025-10-16 12:03:13 +11:00
Andrew Murray
933df2450d Reapply "Use macos-latest for iOS arm64 simulator"
This reverts commit 592b2f820a.
2025-10-16 07:21:15 +11:00
Hugo van Kemenade
3620d48459 12.1.0.dev0 version bump 2025-10-15 21:28:16 +03:00
Andrew Murray
c680ff029f
Merge branch 'main' into main 2025-10-15 22:31:20 +11:00
Andrew Murray
7d89946688 Removed duplicate library 2025-10-15 22:21:51 +11:00
Andrew Murray
3eecafd62c Fixed warning 2025-10-15 22:19:38 +11:00
Andrew Murray
ca3528f46e
Document that macOS window value is a CGWindowID 2025-09-16 21:43:24 +10:00
Andrew Murray
610d564aea
Merge branch 'main' into main 2025-09-16 19:43:34 +10:00
Andrew Murray
53302c2281
Split versionadded info
Co-authored-by: Hugo van Kemenade <1324225+hugovk@users.noreply.github.com>
2025-09-16 19:43:03 +10:00
Andrew Murray
79914ec8a5 Check for scaling in macOS windows 2025-07-13 15:11:23 +08:00
Andrew Murray
7eaac3fcf0 Updated documentation 2025-07-13 15:11:23 +08:00
Yan-Ke Guo
1f7e9c3b51 Apply suggestions from code review
Co-authored-by: Andrew Murray <3112309+radarhere@users.noreply.github.com>
2025-07-13 15:11:23 +08:00
GUO YANKE
5ce88dbe53 feat(ImageGrab): enhance grab function to support window-based screenshot capturing on macOS 2025-07-13 15:11:23 +08:00
Andrew Murray
49efe40f28 Escape period 2025-06-30 22:19:14 +10:00
Andrew Murray
9f0614de7e
Merge branch 'main' into patch-1 2025-05-24 10:34:10 +10:00
Stefan
a666057989
HTTP -> HTTPS
Co-authored-by: Andrew Murray <3112309+radarhere@users.noreply.github.com>
2025-05-21 15:40:16 +02:00
Stefan
349cc44fd4
Add Apache-2.0 notice to IcoImagePlugin
Closes #8946.
2025-05-07 17:21:22 +02:00
161 changed files with 1216 additions and 1463 deletions

View File

@ -27,14 +27,13 @@ python3 -m pip install --upgrade wheel
python3 -m pip install coverage
python3 -m pip install defusedxml
python3 -m pip install ipython
python3 -m pip install numpy
python3 -m pip install olefile
python3 -m pip install -U pytest
python3 -m pip install -U pytest-cov
python3 -m pip install -U pytest-timeout
python3 -m pip install pyroma
# optional test dependency, only install if there's a binary package.
# fails on beta 3.14 and PyPy
# optional test dependencies, only install if there's a binary package.
python3 -m pip install --only-binary=:all: numpy || true
python3 -m pip install --only-binary=:all: pyarrow || true
# PyQt6 doesn't support PyPy3

View File

@ -1 +1 @@
cibuildwheel==3.2.1
cibuildwheel==3.3.0

View File

@ -1,4 +1,4 @@
mypy==1.18.2
mypy==1.19.1
arro3-compute
arro3-core
IceSpringPySideStubs-PyQt6
@ -9,7 +9,6 @@ packaging
pyarrow-stubs
pybind11
pytest
sphinx
types-atheris
types-defusedxml
types-olefile

View File

@ -6,6 +6,7 @@
"labels": [
"Dependency"
],
"minimumReleaseAge": "7 days",
"packageRules": [
{
"groupName": "github-actions",

View File

@ -44,13 +44,13 @@ jobs:
language: python
dry-run: false
- name: Upload New Crash
uses: actions/upload-artifact@v4
uses: actions/upload-artifact@v6
if: failure() && steps.build.outcome == 'success'
with:
name: artifacts
path: ./out/artifacts
- name: Upload Legacy Crash
uses: actions/upload-artifact@v4
uses: actions/upload-artifact@v6
if: steps.run.outcome == 'success'
with:
name: crash

View File

@ -32,7 +32,7 @@ jobs:
name: Docs
steps:
- uses: actions/checkout@v5
- uses: actions/checkout@v6
with:
persist-credentials: false
@ -49,7 +49,7 @@ jobs:
run: python3 .github/workflows/system-info.py
- name: Cache libimagequant
uses: actions/cache@v4
uses: actions/cache@v5
id: cache-libimagequant
with:
path: ~/cache-libimagequant

View File

@ -2,55 +2,31 @@ name: Lint
on: [push, pull_request, workflow_dispatch]
permissions: {}
env:
FORCE_COLOR: 1
permissions:
contents: read
PREK_COLOR: always
RUFF_OUTPUT_FORMAT: github
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
jobs:
build:
lint:
runs-on: ubuntu-latest
name: Lint
steps:
- uses: actions/checkout@v5
with:
persist-credentials: false
- name: pre-commit cache
uses: actions/cache@v4
with:
path: ~/.cache/pre-commit
key: lint-pre-commit-${{ hashFiles('**/.pre-commit-config.yaml') }}
restore-keys: |
lint-pre-commit-
- name: Set up Python
uses: actions/setup-python@v6
with:
python-version: "3.x"
cache: pip
cache-dependency-path: "setup.py"
- name: Build system information
run: python3 .github/workflows/system-info.py
- name: Install dependencies
run: |
python3 -m pip install -U pip
python3 -m pip install -U tox
- name: Lint
run: tox -e lint
env:
PRE_COMMIT_COLOR: always
- name: Mypy
run: tox -e mypy
- uses: actions/checkout@v6
with:
persist-credentials: false
- uses: actions/setup-python@v6
with:
python-version: "3.x"
- name: Install uv
uses: astral-sh/setup-uv@v7
- name: Lint
run: uvx --with tox-uv tox -e lint
- name: Mypy
run: uvx --with tox-uv tox -e mypy

View File

@ -26,9 +26,8 @@ python3 -m pip install -U pytest
python3 -m pip install -U pytest-cov
python3 -m pip install -U pytest-timeout
python3 -m pip install pyroma
python3 -m pip install numpy
# optional test dependency, only install if there's a binary package.
# fails on beta 3.14 and PyPy
# optional test dependencies, only install if there's a binary package.
python3 -m pip install --only-binary=:all: numpy || true
python3 -m pip install --only-binary=:all: pyarrow || true
# libavif

View File

@ -49,8 +49,8 @@ jobs:
debian-12-bookworm-amd64,
debian-13-trixie-x86,
debian-13-trixie-amd64,
fedora-41-amd64,
fedora-42-amd64,
fedora-43-amd64,
gentoo,
ubuntu-22.04-jammy-amd64,
ubuntu-24.04-noble-amd64,
@ -68,7 +68,7 @@ jobs:
name: ${{ matrix.docker }}
steps:
- uses: actions/checkout@v5
- uses: actions/checkout@v6
with:
persist-credentials: false

View File

@ -45,7 +45,7 @@ jobs:
steps:
- name: Checkout Pillow
uses: actions/checkout@v5
uses: actions/checkout@v6
with:
persist-credentials: false

View File

@ -41,7 +41,7 @@ jobs:
name: ${{ matrix.docker }}
steps:
- uses: actions/checkout@v5
- uses: actions/checkout@v6
with:
persist-credentials: false

View File

@ -39,7 +39,7 @@ jobs:
name: ${{ matrix.docker }}
steps:
- uses: actions/checkout@v5
- uses: actions/checkout@v6
with:
persist-credentials: false

View File

@ -31,15 +31,16 @@ env:
jobs:
build:
runs-on: windows-latest
runs-on: ${{ matrix.os }}
strategy:
fail-fast: false
matrix:
python-version: ["pypy3.11", "3.11", "3.12", "3.13", "3.14"]
python-version: ["pypy3.11", "3.11", "3.12", "3.13", "3.14", "3.15"]
architecture: ["x64"]
os: ["windows-latest"]
include:
# Test the oldest Python on 32-bit
- { python-version: "3.10", architecture: "x86" }
- { python-version: "3.10", architecture: "x86", os: "windows-2022" }
timeout-minutes: 45
@ -47,19 +48,19 @@ jobs:
steps:
- name: Checkout Pillow
uses: actions/checkout@v5
uses: actions/checkout@v6
with:
persist-credentials: false
- name: Checkout cached dependencies
uses: actions/checkout@v5
uses: actions/checkout@v6
with:
persist-credentials: false
repository: python-pillow/pillow-depends
path: winbuild\depends
- name: Checkout extra test images
uses: actions/checkout@v5
uses: actions/checkout@v6
with:
persist-credentials: false
repository: python-pillow/test-images
@ -83,7 +84,7 @@ jobs:
python3 -m pip install --upgrade pip
- name: Install CPython dependencies
if: "!contains(matrix.python-version, 'pypy') && !contains(matrix.python-version, '3.14') && matrix.architecture != 'x86'"
if: "!contains(matrix.python-version, 'pypy') && matrix.architecture != 'x86'"
run: |
python3 -m pip install PyQt6
@ -111,7 +112,7 @@ jobs:
- name: Cache build
id: build-cache
uses: actions/cache@v4
uses: actions/cache@v5
with:
path: winbuild\build
key:
@ -187,8 +188,9 @@ jobs:
# trim ~150MB for each job
- name: Optimize build cache
if: steps.build-cache.outputs.cache-hit != 'true'
run: rmdir /S /Q winbuild\build\src
shell: cmd
run: |
rm -rf winbuild\build\src
shell: bash
- name: Build Pillow
run: |
@ -205,9 +207,7 @@ jobs:
- name: Test Pillow
run: |
path %GITHUB_WORKSPACE%\winbuild\build\bin;%PATH%
.ci\test.cmd
shell: cmd
- name: Prepare to upload errors
if: failure()
@ -216,7 +216,7 @@ jobs:
shell: bash
- name: Upload errors
uses: actions/upload-artifact@v4
uses: actions/upload-artifact@v6
if: failure()
with:
name: errors

View File

@ -42,6 +42,8 @@ jobs:
]
python-version: [
"pypy3.11",
"3.15t",
"3.15",
"3.14t",
"3.14",
"3.13t",
@ -54,6 +56,7 @@ jobs:
- { python-version: "3.12", PYTHONOPTIMIZE: 1, REVERSE: "--reverse" }
- { python-version: "3.11", PYTHONOPTIMIZE: 2 }
# Free-threaded
- { python-version: "3.15t", disable-gil: true }
- { python-version: "3.14t", disable-gil: true }
- { python-version: "3.13t", disable-gil: true }
# Intel
@ -65,7 +68,7 @@ jobs:
name: ${{ matrix.os }} Python ${{ matrix.python-version }}
steps:
- uses: actions/checkout@v5
- uses: actions/checkout@v6
with:
persist-credentials: false
@ -89,7 +92,7 @@ jobs:
- name: Cache libimagequant
if: startsWith(matrix.os, 'ubuntu')
uses: actions/cache@v4
uses: actions/cache@v5
id: cache-libimagequant
with:
path: ~/cache-libimagequant
@ -140,7 +143,7 @@ jobs:
mkdir -p Tests/errors
- name: Upload errors
uses: actions/upload-artifact@v4
uses: actions/upload-artifact@v6
if: failure()
with:
name: errors

View File

@ -32,7 +32,6 @@ if [[ "$CIBW_PLATFORM" == "ios" ]]; then
# 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
@ -90,27 +89,25 @@ fi
ARCHIVE_SDIR=pillow-depends-main
# 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.
# Package versions for fresh source builds.
if [[ -n "$IOS_SDK" ]]; then
FREETYPE_VERSION=2.13.3
else
FREETYPE_VERSION=2.14.1
fi
HARFBUZZ_VERSION=12.1.0
LIBPNG_VERSION=1.6.50
JPEGTURBO_VERSION=3.1.2
HARFBUZZ_VERSION=12.3.0
LIBPNG_VERSION=1.6.53
JPEGTURBO_VERSION=3.1.3
OPENJPEG_VERSION=2.5.4
XZ_VERSION=5.8.1
XZ_VERSION=5.8.2
ZSTD_VERSION=1.5.7
TIFF_VERSION=4.7.1
LCMS2_VERSION=2.17
ZLIB_NG_VERSION=2.2.5
ZLIB_NG_VERSION=2.3.2
LIBWEBP_VERSION=1.6.0
BZIP2_VERSION=1.0.8
LIBXCB_VERSION=1.17.0
BROTLI_VERSION=1.1.0 # Patched; next release won't need patching. See patch file.
BROTLI_VERSION=1.2.0
LIBAVIF_VERSION=1.3.0
function build_pkg_config {
@ -149,18 +146,9 @@ function build_zlib_ng {
ORIGINAL_HOST_CONFIGURE_FLAGS=$HOST_CONFIGURE_FLAGS
unset HOST_CONFIGURE_FLAGS
build_github zlib-ng/zlib-ng $ZLIB_NG_VERSION --zlib-compat
build_github zlib-ng/zlib-ng $ZLIB_NG_VERSION --installnamedir=$BUILD_PREFIX/lib --zlib-compat
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. 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
}
@ -168,7 +156,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 $HOST_CMAKE_FLAGS . \
&& cmake -DCMAKE_INSTALL_PREFIX=$BUILD_PREFIX -DCMAKE_INSTALL_LIBDIR=$BUILD_PREFIX/lib -DCMAKE_INSTALL_NAME_DIR=$BUILD_PREFIX/lib -DCMAKE_MACOSX_BUNDLE=OFF $HOST_CMAKE_FLAGS . \
&& make -j4 install)
touch brotli-stamp
}
@ -279,7 +267,7 @@ function build {
build_simple xcb-proto 1.17.0 https://xorg.freedesktop.org/archive/individual/proto
if [[ -n "$IS_MACOS" ]]; then
build_simple xorgproto 2024.1 https://www.x.org/pub/individual/proto
build_simple xorgproto 2025.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

View File

@ -100,14 +100,14 @@ jobs:
cibw_arch: arm64_iphoneos
- name: "iOS arm64 simulator"
platform: ios
os: macos-14
os: macos-latest
cibw_arch: arm64_iphonesimulator
- name: "iOS x86_64 simulator"
platform: ios
os: macos-15-intel
cibw_arch: x86_64_iphonesimulator
steps:
- uses: actions/checkout@v5
- uses: actions/checkout@v6
with:
persist-credentials: false
submodules: true
@ -134,7 +134,7 @@ jobs:
CIBW_MANYLINUX_X86_64_IMAGE: ${{ matrix.manylinux }}
MACOSX_DEPLOYMENT_TARGET: ${{ matrix.macosx_deployment_target }}
- uses: actions/upload-artifact@v4
- uses: actions/upload-artifact@v6
with:
name: dist-${{ matrix.name }}
path: ./wheelhouse/*.whl
@ -154,12 +154,12 @@ jobs:
- cibw_arch: ARM64
os: windows-11-arm
steps:
- uses: actions/checkout@v5
- uses: actions/checkout@v6
with:
persist-credentials: false
- name: Checkout extra test images
uses: actions/checkout@v5
uses: actions/checkout@v6
with:
persist-credentials: false
repository: python-pillow/test-images
@ -186,24 +186,18 @@ jobs:
- name: Build wheels
run: |
setlocal EnableDelayedExpansion
for %%f in (winbuild\build\license\*) do (
set x=%%~nf
rem Skip FriBiDi license, it is not included in the wheel.
set fribidi=!x:~0,7!
if NOT !fribidi!==fribidi (
rem Skip imagequant license, it is not included in the wheel.
set libimagequant=!x:~0,13!
if NOT !libimagequant!==libimagequant (
echo. >> LICENSE
echo ===== %%~nf ===== >> LICENSE
echo. >> LICENSE
type %%f >> LICENSE
)
)
)
call winbuild\\build\\build_env.cmd
%pythonLocation%\python.exe -m cibuildwheel . --output-dir wheelhouse
for f in winbuild/build/license/*; do
name=$(basename "${f%.*}")
# Skip FriBiDi license, it is not included in the wheel.
[[ $name == fribidi* ]] && continue
# Skip imagequant license, it is not included in the wheel.
[[ $name == libimagequant* ]] && continue
echo "" >> LICENSE
echo "===== $name =====" >> LICENSE
echo "" >> LICENSE
cat "$f" >> LICENSE
done
cmd //c "winbuild\\build\\build_env.cmd && $pythonLocation\\python.exe -m cibuildwheel . --output-dir wheelhouse"
env:
CIBW_ARCHS: ${{ matrix.cibw_arch }}
CIBW_BEFORE_ALL: "{package}\\winbuild\\build\\build_dep_all.cmd"
@ -217,16 +211,16 @@ jobs:
-e CI -e GITHUB_ACTIONS
mcr.microsoft.com/windows/servercore:ltsc2022
powershell C:\pillow\.github\workflows\wheels-test.ps1 %CD%\..\venv-test'
shell: cmd
shell: bash
- name: Upload wheels
uses: actions/upload-artifact@v4
uses: actions/upload-artifact@v6
with:
name: dist-windows-${{ matrix.cibw_arch }}
path: ./wheelhouse/*.whl
- name: Upload fribidi.dll
uses: actions/upload-artifact@v4
uses: actions/upload-artifact@v6
with:
name: fribidi-windows-${{ matrix.cibw_arch }}
path: winbuild\build\bin\fribidi*
@ -235,7 +229,7 @@ jobs:
if: github.event_name != 'schedule' || github.repository_owner == 'python-pillow'
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v5
- uses: actions/checkout@v6
with:
persist-credentials: false
@ -246,7 +240,7 @@ jobs:
- run: make sdist
- uses: actions/upload-artifact@v4
- uses: actions/upload-artifact@v6
with:
name: dist-sdist
path: dist/*.tar.gz
@ -256,7 +250,7 @@ jobs:
runs-on: ubuntu-latest
name: Count dists
steps:
- uses: actions/download-artifact@v5
- uses: actions/download-artifact@v7
with:
pattern: dist-*
path: dist
@ -275,13 +269,13 @@ jobs:
runs-on: ubuntu-latest
name: Upload wheels to scientific-python-nightly-wheels
steps:
- uses: actions/download-artifact@v5
- uses: actions/download-artifact@v7
with:
pattern: dist-!(sdist)*
path: dist
merge-multiple: true
- name: Upload wheels to scientific-python-nightly-wheels
uses: scientific-python/upload-nightly-action@b36e8c0c10dbcfd2e05bf95f17ef8c14fd708dbf # 0.6.2
uses: scientific-python/upload-nightly-action@5748273c71e2d8d3a61f3a11a16421c8954f9ecf # 0.6.3
with:
artifacts_path: dist
anaconda_nightly_upload_token: ${{ secrets.ANACONDA_ORG_UPLOAD_TOKEN }}
@ -297,7 +291,7 @@ jobs:
permissions:
id-token: write
steps:
- uses: actions/download-artifact@v5
- uses: actions/download-artifact@v7
with:
pattern: dist-*
path: dist

1
.github/zizmor.yml vendored
View File

@ -1,4 +1,3 @@
# Configuration for the zizmor static analysis tool, run via pre-commit in CI
# https://docs.zizmor.sh/configuration/
rules:
unpinned-uses:

View File

@ -1,17 +1,17 @@
repos:
- repo: https://github.com/astral-sh/ruff-pre-commit
rev: v0.13.3
rev: v0.14.10
hooks:
- id: ruff-check
args: [--exit-non-zero-on-fix]
- repo: https://github.com/psf/black-pre-commit-mirror
rev: 25.9.0
rev: 25.12.0
hooks:
- id: black
- repo: https://github.com/PyCQA/bandit
rev: 1.8.6
rev: 1.9.2
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$|\.patch$)
exclude: (Makefile$|\.bat$|\.cmake$|\.eps$|\.fits$|\.gd$|\.opt$)
- repo: https://github.com/pre-commit/mirrors-clang-format
rev: v21.1.2
rev: v21.1.8
hooks:
- id: clang-format
types: [c]
@ -46,29 +46,29 @@ repos:
- id: check-yaml
args: [--allow-multiple-documents]
- id: end-of-file-fixer
exclude: ^Tests/images/|\.patch$
exclude: ^Tests/images/
- id: trailing-whitespace
exclude: ^.github/.*TEMPLATE|^Tests/(fonts|images)/|\.patch$
exclude: ^\.github/.*TEMPLATE|^Tests/(fonts|images)/
- repo: https://github.com/python-jsonschema/check-jsonschema
rev: 0.34.0
rev: 0.36.0
hooks:
- id: check-github-workflows
- id: check-readthedocs
- id: check-renovate
- repo: https://github.com/zizmorcore/zizmor-pre-commit
rev: v1.14.2
rev: v1.19.0
hooks:
- id: zizmor
- repo: https://github.com/sphinx-contrib/sphinx-lint
rev: v1.0.0
rev: v1.0.2
hooks:
- id: sphinx-lint
- repo: https://github.com/tox-dev/pyproject-fmt
rev: v2.7.0
rev: v2.11.1
hooks:
- id: pyproject-fmt
@ -79,7 +79,7 @@ repos:
additional_dependencies: [trove-classifiers>=2024.10.12]
- repo: https://github.com/tox-dev/tox-ini-fmt
rev: 1.6.0
rev: 1.7.1
hooks:
- id: tox-ini-fmt

View File

@ -15,7 +15,6 @@ include tox.ini
graft Tests
graft Tests/images
graft checks
graft patches
graft src
graft depends
graft winbuild

Binary file not shown.

View File

@ -2,7 +2,7 @@
NotoNastaliqUrdu-Regular.ttf and NotoSansSymbols-Regular.ttf, from https://github.com/googlei18n/noto-fonts
NotoSans-Regular.ttf, from https://www.google.com/get/noto/
NotoSansJP-Thin.otf, from https://www.google.com/get/noto/help/cjk/
AdobeVFPrototype.ttf, from https://github.com/adobe-fonts/adobe-variable-font-prototype
AdobeVFPrototype.ttf, from https://github.com/adobe-fonts/adobe-variable-font-prototype. AdobeVFPrototypeDuplicates.ttf is a modified version of this
TINY5x3GX.ttf, from http://velvetyne.fr/fonts/tiny
ArefRuqaa-Regular.ttf, from https://github.com/google/fonts/tree/master/ofl/arefruqaa
ter-x20b.pcf, from http://terminus-font.sourceforge.net/

View File

@ -55,8 +55,8 @@ def convert_to_comparable(
if a.mode == "P":
new_a = Image.new("L", a.size)
new_b = Image.new("L", b.size)
new_a.putdata(a.getdata())
new_b.putdata(b.getdata())
new_a.putdata(a.get_flattened_data())
new_b.putdata(b.get_flattened_data())
elif a.mode == "I;16":
new_a = a.convert("I")
new_b = b.convert("I")
@ -104,10 +104,9 @@ def assert_image_equal_tofile(
msg: str | None = None,
mode: str | None = None,
) -> None:
with Image.open(filename) as img:
if mode:
img = img.convert(mode)
assert_image_equal(a, img, msg)
with Image.open(filename) as im:
converted_im = im.convert(mode) if mode else im
assert_image_equal(a, converted_im, msg)
def assert_image_similar(

Binary file not shown.

Before

Width:  |  Height:  |  Size: 126 B

View File

@ -1,578 +0,0 @@
<!DOCTYPE html>
<html>
<head>
<title>BMP Suite Image List</title>
<style>
.b { background:url(bkgd.png); }
.q { background-color:#fff0e0; }
.bad { background-color:#ffa0a0; }
</style>
</head>
<body>
<h1>BMP Suite Image List</h1>
<p><i>For <a href="http://entropymine.com/jason/bmpsuite/">BMP Suite</a>
version 2.3</i></p>
<p>This document describes the images in <i>BMP Suite</i>, and shows what
I allege to be the correct way to interpret them. PNG and JPEG images are
used for reference.
</p>
<p>It also shows how your web browser displays the BMP images,
but that&rsquo;s not its main purpose.
BMP is poor image format to use on web pages, so a web browser&rsquo;s
level of support for it is arguably not important.</p>
<table border=1 cellpadding=8>
<tr>
<th>File</th>
<th>Ver.</th>
<th>Correct display</th>
<th>In your browser</th>
<th>Notes</th>
</tr>
<tr>
<td>g/pal1.bmp</td>
<td>3</td>
<td class=b><img src="pal1.png"></td>
<td class=b><img src="../g/pal1.bmp"></td>
<td>1 bit/pixel paletted image, in which black is the first color in
the palette.</td>
</tr>
<tr>
<td>g/pal1wb.bmp</td>
<td>3</td>
<td class=b><img src="pal1.png"></td>
<td class=b><img src="../g/pal1wb.bmp"></td>
<td>1 bit/pixel paletted image, in which white is the first color in
the palette.</td>
</tr>
<tr>
<td>g/pal1bg.bmp</td>
<td>3</td>
<td class=b><img src="pal1bg.png"></td>
<td class=b><img src="../g/pal1bg.bmp"></td>
<td>1 bit/pixel paletted image, with colors other than black and white.</td>
</tr>
<tr>
<td class=q>q/pal1p1.bmp</td>
<td>3</td>
<td class=b><img src="pal1p1.png"></td>
<td class=b><img src="../q/pal1p1.bmp"></td>
<td>1 bit/pixel paletted image, with only one color in the palette.
The documentation says that 1-bpp images have a palette size of 2
(not &ldquo;up to 2&rdquo;), but it would be silly for a viewer not to
support a size of 1.</td>
</tr>
<tr>
<td class=q>q/pal2.bmp</td>
<td>3</td>
<td class=b><img src="pal2.png"></td>
<td class=b><img src="../q/pal2.bmp"></td>
<td>A paletted image with 2 bits/pixel. Usually only 1, 4,
and 8 are allowed, but 2 is legal on Windows CE.</td>
</tr>
<tr>
<td>g/pal4.bmp</td>
<td>3</td>
<td class=b><img src="pal4.png"></td>
<td class=b><img src="../g/pal4.bmp"></td>
<td>Paletted image with 12 palette colors, and 4 bits/pixel.</td>
</tr>
<tr>
<td>g/pal4rle.bmp</td>
<td>3</td>
<td class=b><img src="pal4.png"></td>
<td class=b><img src="../g/pal4rle.bmp"></td>
<td>4-bit image that uses RLE compression.</td>
</tr>
<tr>
<td class=q>q/pal4rletrns.bmp</td>
<td>3</td>
<td class=b><img src="pal4rletrns.png"><br>
or<br><img src="pal4rletrns-0.png"><br>
or<br><img src="pal4rletrns-b.png"></td>
<td class=b><img src="../q/pal4rletrns.bmp"></td>
<td>An RLE-compressed image that used &ldquo;delta&rdquo;
codes to skip over some pixels, leaving them undefined. Some viewers
make undefined pixels transparent, others make them black, and
others assign them palette color 0 (purple, in this case).</td>
</tr>
<tr>
<td>g/pal8.bmp</td>
<td>3</td>
<td class=b><img src="pal8.png"></td>
<td class=b><img src="../g/pal8.bmp"></td>
<td>Our standard paletted image, with 252 palette colors, and 8
bits/pixel.</td>
</tr>
<tr>
<td>g/pal8-0.bmp</td>
<td>3</td>
<td class=b><img src="pal8.png"></td>
<td class=b><img src="../g/pal8-0.bmp"></td>
<td>Every field that can be set to 0 is set to 0: pixels/meter=0;
colors used=0 (meaning the default 256); size-of-image=0.</td>
</tr>
<tr>
<td>g/pal8rle.bmp</td>
<td>3</td>
<td class=b><img src="pal8.png"></td>
<td class=b><img src="../g/pal8rle.bmp"></td>
<td>8-bit image that uses RLE compression.</td>
</tr>
<tr>
<td class=q>q/pal8rletrns.bmp</td>
<td>3</td>
<td class=b><img src="pal8rletrns.png"><br>
or<br><img src="pal8rletrns-0.png"><br>
or<br><img src="pal8rletrns-b.png"></td>
<td class=b><img src="../q/pal8rletrns.bmp"></td>
<td>8-bit version of q/pal4rletrns.bmp.</td>
</tr>
<tr>
<td>g/pal8w126.bmp</td>
<td>3</td>
<td class=b><img src="pal8w126.png"></td>
<td class=b><img src="../g/pal8w126.bmp"></td>
<td rowspan=3>Images with different widths and heights.
In BMP format, rows are padded to a multiple of four bytes, so we
test all four possibilities.</td>
</tr>
<tr>
<td>g/pal8w125.bmp</td>
<td>3</td>
<td class=b><img src="pal8w125.png"></td>
<td class=b><img src="../g/pal8w125.bmp"></td>
</tr>
<tr>
<td>g/pal8w124.bmp</td>
<td>3</td>
<td class=b><img src="pal8w124.png"></td>
<td class=b><img src="../g/pal8w124.bmp"></td>
</tr>
<tr>
<td>g/pal8topdown.bmp</td>
<td>3</td>
<td class=b><img src="pal8.png"></td>
<td class=b><img src="../g/pal8topdown.bmp"></td>
<td>BMP images are normally stored from the bottom up, but
there is a way to store them from the top down.</td>
</tr>
<tr>
<td class=q>q/pal8offs.bmp</td>
<td>3</td>
<td class=b><img src="pal8.png"></td>
<td class=b><img src="../q/pal8offs.bmp"></td>
<td>A file with some unused bytes between the palette and the
image. This is probably valid, but I&rsquo;m not 100% sure.</td>
</tr>
<tr>
<td class=q>q/pal8oversizepal.bmp</td>
<td>3</td>
<td class=b><img src="pal8.png"></td>
<td class=b><img src="../q/pal8oversizepal.bmp"></td>
<td>An 8-bit image with 300 palette colors. This may be invalid,
because the documentation could
be interpreted to imply that 8-bit images aren&rsquo;t allowed
to have more than 256 colors.</td>
</tr>
<tr>
<td>g/pal8nonsquare.bmp</td>
<td>3</td>
<td class=b>
<img src="pal8nonsquare-v.png"><br>
or<br>
<img src="pal8nonsquare-e.png">
</td>
<td class=b><img src="../g/pal8nonsquare.bmp"></td>
<td>An image with non-square pixels: the X pixels/meter is twice
the Y pixels/meter. Image <i>editors</i> can be expected to
leave the image &ldquo;squashed&rdquo;; image <i>viewers</i> should
consider stretching it to its correct proportions.</td>
</tr>
<tr>
<td>g/pal8os2.bmp</td>
<td>OS/2v1</td>
<td class=b><img src="pal8.png"></td>
<td class=b><img src="../g/pal8os2.bmp"></td>
<td>An OS/2-style bitmap.</td>
</tr>
<tr>
<td class=q>q/pal8os2sp.bmp</td>
<td>OS/2v1</td>
<td class=b><img src="pal8.png"></td>
<td class=b><img src="../q/pal8os2sp.bmp"></td>
<td>An OS/2v1 with a less-than-full-sized palette.
Probably not valid, but such files have been seen in the wild.</td>
</tr>
<tr>
<td class=q>q/pal8os2v2.bmp</td>
<td>OS/2v2</td>
<td class=b><img src="pal8.png"></td>
<td class=b><img src="../q/pal8os2v2.bmp"></td>
<td>My attempt to make an OS/2v2 bitmap.</td>
</tr>
<tr>
<td class=q>q/pal8os2v2-16.bmp</td>
<td>OS/2v2</td>
<td class=b><img src="pal8.png"></td>
<td class=b><img src="../q/pal8os2v2-16.bmp"></td>
<td>An OS/2v2 bitmap whose header has only 16 bytes, instead of the full 64.</td>
</tr>
<tr>
<td>g/pal8v4.bmp</td>
<td>4</td>
<td class=b><img src="pal8.png"></td>
<td class=b><img src="../g/pal8v4.bmp"></td>
<td>A v4 bitmap. I&rsquo;m not sure that the gamma and chromaticity values in
this file are sensible, because I can&rsquo;t find any detailed documentation
of them.</td>
</tr>
<tr>
<td>g/pal8v5.bmp</td>
<td>5</td>
<td class=b><img src="pal8.png"></td>
<td class=b><img src="../g/pal8v5.bmp"></td>
<td>A v5 bitmap. Version 5 has additional colorspace options over v4, so it
is easier to create, and ought to be more portable.</td>
</tr>
<tr>
<td>g/rgb16.bmp</td>
<td>3</td>
<td class=b><img src="rgb16.png"></td>
<td class=b><img src="../g/rgb16.bmp"></td>
<td>A 16-bit image with the default color format: 5 bits each for red,
green, and blue, and 1 unused bit.
The whitest colors should (I assume) be displayed as pure white:
<span style="background-color:rgb(255,255,255)">(255,255,255)</span>, not
<span style="background-color:rgb(248,248,248)">(248,248,248)</span>.</td>
</tr>
<tr>
<td>g/rgb16-565.bmp</td>
<td>3</td>
<td class=b><img src="rgb16-565.png"></td>
<td class=b><img src="../g/rgb16-565.bmp"></td>
<td>A 16-bit image with a BITFIELDS segment indicating 5 red, 6 green,
and 5 blue bits. This is a standard 16-bit format, even supported by
old versions of Windows that don&rsquo;t support any other non-default 16-bit
formats.
The whitest colors should be displayed as pure white:
<span style="background-color:rgb(255,255,255)">(255,255,255)</span>, not
<span style="background-color:rgb(248,252,248)">(248,252,248)</span>.</td>
</tr>
<tr>
<td>g/rgb16-565pal.bmp</td>
<td>3</td>
<td class=b><img src="rgb16-565.png"></td>
<td class=b><img src="../g/rgb16-565pal.bmp"></td>
<td>A 16-bit image with both a BITFIELDS segment and a palette.</td>
</tr>
<tr>
<td class=q>q/rgb16-231.bmp</td>
<td>3</td>
<td class=b><img src="rgb16-231.png"></td>
<td class=b><img src="../q/rgb16-231.bmp"></td>
<td>An unusual and silly 16-bit image, with 2 red bits, 3 green bits, and 1
blue bit. Most viewers do support this image, but the colors may be darkened
with a yellow-green shadow. That&rsquo;s because they&rsquo;re doing simple
bit-shifting (possibly including one round of bit replication), instead of
proper scaling.</td>
</tr>
<tr>
<td class=q>q/rgba16-4444.bmp</td>
<td>5</td>
<td class=b><img src="rgba16-4444.png"></td>
<td class=b><img src="../q/rgba16-4444.bmp"></td>
<td>A 16-bit image with an alpha channel. There are 4 bits for each color
channel, and 4 bits for the alpha channel.
It&rsquo;s not clear if this is valid, but I can&rsquo;t find anything that
suggests it isn&rsquo;t.
</td>
</tr>
<tr>
<td>g/rgb24.bmp</td>
<td>3</td>
<td class=b><img src="rgb24.png"></td>
<td class=b><img src="../g/rgb24.bmp"></td>
<td>A perfectly ordinary 24-bit (truecolor) image.</td>
</tr>
<tr>
<td>g/rgb24pal.bmp</td>
<td>3</td>
<td class=b><img src="rgb24.png"></td>
<td class=b><img src="../g/rgb24pal.bmp"></td>
<td>A 24-bit image, with a palette containing 256 colors. There is little if
any reason for a truecolor image to contain a palette, but it is legal.</td>
</tr>
<tr>
<td class=q>q/rgb24largepal.bmp</td>
<td>3</td>
<td class=b><img src="rgb24.png"></td>
<td class=b><img src="../q/rgb24largepal.bmp"></td>
<td>A 24-bit image, with a palette containing 300 colors.
The fact that the palette has more than 256 colors may cause some viewers
to complain, but the documentation does not mention a size limit.</td>
</tr>
<tr>
<td class=q>q/rgb24prof.bmp</td>
<td>5</td>
<td class=b><img src="rgb24.png"></td>
<td class=b><img src="../q/rgb24prof.bmp"></td>
<td>My attempt to make a BMP file with an embedded color profile.</td>
</tr>
<tr>
<td class=q>q/rgb24lprof.bmp</td>
<td>5</td>
<td class=b><img src="rgb24.png"></td>
<td class=b><img src="../q/rgb24lprof.bmp"></td>
<td>My attempt to make a BMP file with a linked color profile.</td>
</tr>
<tr>
<td class=q>q/rgb24jpeg.bmp</td>
<td>5</td>
<td class=b><img src="rgb24.jpg"></td>
<td class=b><img src="../q/rgb24jpeg.bmp"></td>
<td rowspan=2>My attempt to make BMP files with embedded JPEG and PNG images.
These are not likely to be supported by much of anything (they&rsquo;re
intended for printers).</td>
</tr>
<tr>
<td class=q>q/rgb24png.bmp</td>
<td>5</td>
<td class=b><img src="rgb24.png"></td>
<td class=b><img src="../q/rgb24png.bmp"></td>
</tr>
<tr>
<td>g/rgb32.bmp</td>
<td>3</td>
<td class=b><img src="rgb24.png"></td>
<td class=b><img src="../g/rgb32.bmp"></td>
<td>A 32-bit image using the default color format for 32-bit images (no
BITFIELDS segment). There are 8 bits per color channel, and 8 unused
bits. The unused bits are set to 0.</td>
</tr>
<tr>
<td>g/rgb32bf.bmp</td>
<td>3</td>
<td class=b><img src="rgb24.png"></td>
<td class=b><img src="../g/rgb32bf.bmp"></td>
<td>A 32-bit image with a BITFIELDS segment. As usual, there are 8 bits per
color channel, and 8 unused bits. But the color channels are in an unusual
order, so the viewer must read the BITFIELDS, and not just guess.</td>
</tr>
<tr>
<td class=q>q/rgb32fakealpha.bmp</td>
<td>3</td>
<td class=b><img src="rgb24.png"><br>
or<br>
<img class=b src="fakealpha.png">
</td>
<td class=b><img src="../q/rgb32fakealpha.bmp"></td>
<td>Same as g/rgb32.bmp, except that the unused bits are set to something
other than 0.
If the image becomes transparent toward the bottom, it probably means
the viewer uses heuristics to guess whether the undefined
data represents transparency.</td>
</tr>
<tr>
<td class=q>q/rgb32-111110.bmp</td>
<td>3</td>
<td class=b><img src="rgb24.png"></td>
<td class=b><img src="../q/rgb32-111110.bmp"></td>
<td>A 32 bits/pixel image, with all 32 bits used: 11 each for red and
green, and 10 for blue. As far as I know, this is perfectly valid, but it
is unusual.</td>
</tr>
<tr>
<td class=q>q/rgba32.bmp</td>
<td>5</td>
<td class=b><img src="rgba32.png"></td>
<td class=b><img src="../q/rgba32.bmp"></td>
<td>A BMP with an alpha channel. Transparency is barely documented,
so it&rsquo;s <i>possible</i> that this file is not correctly formed.
The color channels are in an unusual order, to prevent viewers from
passing this test by making a lucky guess.</td>
</tr>
<tr>
<td class=q>q/rgba32abf.bmp</td>
<td>3</td>
<td class=b><img src="rgba32.png"></td>
<td class=b><img src="../q/rgba32abf.bmp"></td>
<td>An image of type BI_ALHPABITFIELDS. Supposedly, this was used on
Windows CE. I don&rsquo;t know whether it is constructed correctly.</td>
</tr>
<tr>
<td class=bad>b/badbitcount.bmp</td>
<td>3</td>
<td class=b>N/A</td>
<td class=b><img src="../b/badbitcount.bmp"></td>
<td>Header indicates an absurdly large number of bits/pixel.</td>
</tr>
<tr>
<td class=bad>b/badbitssize.bmp</td>
<td>3</td>
<td class=b>N/A</td>
<td class=b><img src="../b/badbitssize.bmp"></td>
<td>Header incorrectly indicates that the bitmap is several GB in size.</td>
</tr>
<tr>
<td class=bad>b/baddens1.bmp</td>
<td>3</td>
<td class=b>N/A</td>
<td class=b><img src="../b/baddens1.bmp"></td>
<td rowspan=2>Density (pixels per meter) suggests the image is <i>much</i>
larger in one dimension than the other.</td>
</tr>
<tr>
<td class=bad>b/baddens2.bmp</td>
<td>3</td>
<td class=b>N/A</td>
<td class=b><img src="../b/baddens2.bmp"></td>
</tr>
<tr>
<td class=bad>b/badfilesize.bmp</td>
<td>3</td>
<td class=b>N/A</td>
<td class=b><img src="../b/badfilesize.bmp"></td>
<td>Header incorrectly indicates that the file is several GB in size.</td>
</tr>
<tr>
<td class=bad>b/badheadersize.bmp</td>
<td>?</td>
<td class=b>N/A</td>
<td class=b><img src="../b/badheadersize.bmp"></td>
<td>Header size is 66 bytes, which is not a valid size for any known BMP
version.</td>
</tr>
<tr>
<td class=bad>b/badpalettesize.bmp</td>
<td>3</td>
<td class=b>N/A</td>
<td class=b><img src="../b/badpalettesize.bmp"></td>
<td>Header incorrectly indicates that the palette contains an absurdly large
number of colors.</td>
</tr>
<tr>
<td class=bad>b/badplanes.bmp</td>
<td>3</td>
<td class=b>N/A</td>
<td class=b><img src="../b/badplanes.bmp"></td>
<td>The &ldquo;planes&rdquo; setting, which is required to be 1, is not 1.</td>
</tr>
<tr>
<td class=bad>b/badrle.bmp</td>
<td>3</td>
<td class=b>N/A</td>
<td class=b><img src="../b/badrle.bmp"></td>
<td>An invalid RLE-compressed image that tries to cause buffer overruns.</td>
</tr>
<tr>
<td class=bad>b/badwidth.bmp</td>
<td>3</td>
<td class=b>N/A</td>
<td class=b><img src="../b/badwidth.bmp"></td>
<td>The image claims to be a negative number of pixels in width.</td>
</tr>
<tr>
<td class=bad>b/pal8badindex.bmp</td>
<td>3</td>
<td class=b>N/A</td>
<td class=b><img src="../b/pal8badindex.bmp"></td>
<td>Many of the palette indices used in the image are not present in the
palette.</td>
</tr>
<tr>
<td class=bad>b/reallybig.bmp</td>
<td>3</td>
<td class=b>N/A</td>
<td class=b><img src="../b/reallybig.bmp"></td>
<td>An image with a very large reported width and height.</td>
</tr>
<tr>
<td class=bad>b/rletopdown.bmp</td>
<td>3</td>
<td class=b>N/A</td>
<td class=b><img src="../b/rletopdown.bmp"></td>
<td>An RLE-compressed image that tries to use top-down orientation,
which isn&rsquo;t allowed.</td>
</tr>
<tr>
<td class=bad>b/shortfile.bmp</td>
<td>3</td>
<td class=b>N/A</td>
<td class=b><img src="../b/shortfile.bmp"></td>
<td>A file that has been truncated in the middle of the bitmap.</td>
</tr>
</table>
</body>
</html>

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 124 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 961 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 83 B

After

Width:  |  Height:  |  Size: 79 B

Binary file not shown.

View File

@ -72,7 +72,7 @@ def test_good() -> None:
"pal8-0.bmp": "pal8.png",
"pal8rle.bmp": "pal8.png",
"pal8topdown.bmp": "pal8.png",
"pal8nonsquare.bmp": "pal8nonsquare-v.png",
"pal8nonsquare.bmp": "pal8nonsquare-e.png",
"pal8os2.bmp": "pal8.png",
"pal8os2sp.bmp": "pal8.png",
"pal8os2v2.bmp": "pal8.png",
@ -95,16 +95,16 @@ def test_good() -> None:
for f in get_files("g"):
try:
with Image.open(f) as im:
im.load()
with Image.open(get_compare(f)) as compare:
compare.load()
if im.mode == "P":
# assert image similar doesn't really work
# with paletized image, since the palette might
# be differently ordered for an equivalent image.
im = im.convert("RGBA")
compare = im.convert("RGBA")
assert_image_similar(im, compare, 5)
# assert image similar doesn't really work
# with paletized image, since the palette might
# be differently ordered for an equivalent image.
im_converted = im.convert("RGBA") if im.mode == "P" else im
compare_converted = (
compare.convert("RGBA") if im.mode == "P" else compare
)
assert_image_similar(im_converted, compare_converted, 5)
except Exception as msg:
# there are three here that are unsupported:

View File

@ -28,9 +28,13 @@ def box_blur(image: Image.Image, radius: float = 1, n: int = 1) -> Image.Image:
def assert_image(im: Image.Image, data: list[list[int]], delta: int = 0) -> None:
it = iter(im.getdata())
it = iter(im.get_flattened_data())
for data_row in data:
im_row = [next(it) for _ in range(im.size[0])]
im_row = []
for _ in range(im.width):
im_v = next(it)
assert isinstance(im_v, (int, float))
im_row.append(im_v)
if any(abs(data_v - im_v) > delta for data_v, im_v in zip(data_row, im_row)):
assert im_row == data_row
with pytest.raises(StopIteration):

View File

@ -1,5 +1,6 @@
from __future__ import annotations
from io import BytesIO
from pathlib import Path
import pytest
@ -277,25 +278,25 @@ def test_apng_mode() -> None:
assert isinstance(im, PngImagePlugin.PngImageFile)
assert im.mode == "P"
im.seek(im.n_frames - 1)
im = im.convert("RGB")
assert im.getpixel((0, 0)) == (0, 255, 0)
assert im.getpixel((64, 32)) == (0, 255, 0)
im_rgb = im.convert("RGB")
assert im_rgb.getpixel((0, 0)) == (0, 255, 0)
assert im_rgb.getpixel((64, 32)) == (0, 255, 0)
with Image.open("Tests/images/apng/mode_palette_alpha.png") as im:
assert isinstance(im, PngImagePlugin.PngImageFile)
assert im.mode == "P"
im.seek(im.n_frames - 1)
im = im.convert("RGBA")
assert im.getpixel((0, 0)) == (0, 255, 0, 255)
assert im.getpixel((64, 32)) == (0, 255, 0, 255)
im_rgba = im.convert("RGBA")
assert im_rgba.getpixel((0, 0)) == (0, 255, 0, 255)
assert im_rgba.getpixel((64, 32)) == (0, 255, 0, 255)
with Image.open("Tests/images/apng/mode_palette_1bit_alpha.png") as im:
assert isinstance(im, PngImagePlugin.PngImageFile)
assert im.mode == "P"
im.seek(im.n_frames - 1)
im = im.convert("RGBA")
assert im.getpixel((0, 0)) == (0, 0, 255, 128)
assert im.getpixel((64, 32)) == (0, 0, 255, 128)
im_rgba = im.convert("RGBA")
assert im_rgba.getpixel((0, 0)) == (0, 0, 255, 128)
assert im_rgba.getpixel((64, 32)) == (0, 0, 255, 128)
def test_apng_chunk_errors() -> None:
@ -517,6 +518,24 @@ def test_apng_save_duration_loop(tmp_path: Path) -> None:
assert im.info["duration"] == 600
def test_apng_save_duration_float(tmp_path: Path) -> None:
test_file = tmp_path / "temp.png"
im = Image.new("1", (1, 1))
im2 = Image.new("1", (1, 1), 1)
im.save(test_file, save_all=True, append_images=[im2], duration=0.5)
with Image.open(test_file) as reloaded:
assert reloaded.info["duration"] == 0.5
def test_apng_save_large_duration(tmp_path: Path) -> None:
test_file = tmp_path / "temp.png"
im = Image.new("1", (1, 1))
im2 = Image.new("1", (1, 1), 1)
with pytest.raises(ValueError, match="cannot write duration"):
im.save(test_file, save_all=True, append_images=[im2], duration=65536000)
def test_apng_save_disposal(tmp_path: Path) -> None:
test_file = tmp_path / "temp.png"
size = (128, 64)
@ -718,6 +737,25 @@ def test_apng_save_size(tmp_path: Path) -> None:
assert reloaded.size == (200, 200)
def test_compress_level() -> None:
compress_level_sizes = {}
for compress_level in (0, 9):
out = BytesIO()
im = Image.new("L", (100, 100))
im.save(
out,
"PNG",
save_all=True,
append_images=[Image.new("L", (200, 200))],
compress_level=compress_level,
)
compress_level_sizes[compress_level] = len(out.getvalue())
assert compress_level_sizes[0] > compress_level_sizes[9]
def test_seek_after_close() -> None:
im = Image.open("Tests/images/apng/delay.png")
im.seek(1)

View File

@ -121,7 +121,6 @@ class TestFileAvif:
assert image.size == (128, 128)
assert image.format == "AVIF"
assert image.get_format_mimetype() == "image/avif"
image.getdata()
# generated with:
# avifdec hopper.avif hopper_avif_write.png
@ -143,7 +142,6 @@ class TestFileAvif:
assert reloaded.mode == "RGB"
assert reloaded.size == (128, 128)
assert reloaded.format == "AVIF"
reloaded.getdata()
# avifdec hopper.avif avif/hopper_avif_write.png
assert_image_similar_tofile(

View File

@ -165,9 +165,9 @@ def test_rgba_bitfields() -> None:
with Image.open("Tests/images/rgb32bf-rgba.bmp") as im:
# So before the comparing the image, swap the channels
b, g, r = im.split()[1:]
im = Image.merge("RGB", (r, g, b))
im_rgb = Image.merge("RGB", (r, g, b))
assert_image_equal_tofile(im, "Tests/images/bmp/q/rgb32bf-xbgr.bmp")
assert_image_equal_tofile(im_rgb, "Tests/images/bmp/q/rgb32bf-xbgr.bmp")
# This test image has been manually hexedited
# to change the bitfield compression in the header from XBGR to ABGR

View File

@ -61,6 +61,7 @@ def test_handler(tmp_path: Path) -> None:
def load(self, im: ImageFile.StubImageFile) -> Image.Image:
self.loaded = True
assert im.fp is not None
im.fp.close()
return Image.new("RGB", (1, 1))

View File

@ -57,7 +57,7 @@ TEST_FILE_UNCOMPRESSED_RGB_WITH_ALPHA = "Tests/images/uncompressed_rgb.dds"
def test_sanity_dxt1_bc1(image_path: str) -> None:
"""Check DXT1 and BC1 images can be opened"""
with Image.open(TEST_FILE_DXT1.replace(".dds", ".png")) as target:
target = target.convert("RGBA")
target_rgba = target.convert("RGBA")
with Image.open(image_path) as im:
im.load()
@ -65,7 +65,7 @@ def test_sanity_dxt1_bc1(image_path: str) -> None:
assert im.mode == "RGBA"
assert im.size == (256, 256)
assert_image_equal(im, target)
assert_image_equal(im, target_rgba)
def test_sanity_dxt3() -> None:
@ -380,6 +380,11 @@ def test_palette() -> None:
assert_image_equal_tofile(im, "Tests/images/transparent.gif")
def test_zero_mask_totals() -> None:
with Image.open("Tests/images/zero_mask_totals.dds") as im:
im.load()
def test_unsupported_header_size() -> None:
with pytest.raises(OSError, match="Unsupported header size 0"):
with Image.open(BytesIO(b"DDS " + b"\x00" * 4)):
@ -515,9 +520,9 @@ def test_save_dx10_bc5(tmp_path: Path) -> None:
im.save(out, pixel_format="BC5")
assert_image_similar_tofile(im, out, 9.56)
im = hopper("L")
im_l = hopper("L")
with pytest.raises(OSError, match="only RGB mode can be written as BC5"):
im.save(out, pixel_format="BC5")
im_l.save(out, pixel_format="BC5")
@pytest.mark.parametrize(

View File

@ -265,9 +265,9 @@ def test_bytesio_object() -> None:
img.load()
with Image.open(FILE1_COMPARE) as image1_scale1_compare:
image1_scale1_compare = image1_scale1_compare.convert("RGB")
image1_scale1_compare.load()
assert_image_similar(img, image1_scale1_compare, 5)
image1_scale1_compare_rgb = image1_scale1_compare.convert("RGB")
image1_scale1_compare_rgb.load()
assert_image_similar(img, image1_scale1_compare_rgb, 5)
@pytest.mark.skipif(not HAS_GHOSTSCRIPT, reason="Ghostscript not available")
@ -301,17 +301,17 @@ def test_render_scale1() -> None:
with Image.open(FILE1) as image1_scale1:
image1_scale1.load()
with Image.open(FILE1_COMPARE) as image1_scale1_compare:
image1_scale1_compare = image1_scale1_compare.convert("RGB")
image1_scale1_compare.load()
assert_image_similar(image1_scale1, image1_scale1_compare, 5)
image1_scale1_compare_rgb = image1_scale1_compare.convert("RGB")
image1_scale1_compare_rgb.load()
assert_image_similar(image1_scale1, image1_scale1_compare_rgb, 5)
# Non-zero bounding box
with Image.open(FILE2) as image2_scale1:
image2_scale1.load()
with Image.open(FILE2_COMPARE) as image2_scale1_compare:
image2_scale1_compare = image2_scale1_compare.convert("RGB")
image2_scale1_compare.load()
assert_image_similar(image2_scale1, image2_scale1_compare, 10)
image2_scale1_compare_rgb = image2_scale1_compare.convert("RGB")
image2_scale1_compare_rgb.load()
assert_image_similar(image2_scale1, image2_scale1_compare_rgb, 10)
@pytest.mark.skipif(not HAS_GHOSTSCRIPT, reason="Ghostscript not available")
@ -324,18 +324,16 @@ def test_render_scale2() -> None:
assert isinstance(image1_scale2, EpsImagePlugin.EpsImageFile)
image1_scale2.load(scale=2)
with Image.open(FILE1_COMPARE_SCALE2) as image1_scale2_compare:
image1_scale2_compare = image1_scale2_compare.convert("RGB")
image1_scale2_compare.load()
assert_image_similar(image1_scale2, image1_scale2_compare, 5)
image1_scale2_compare_rgb = image1_scale2_compare.convert("RGB")
assert_image_similar(image1_scale2, image1_scale2_compare_rgb, 5)
# Non-zero bounding box
with Image.open(FILE2) as image2_scale2:
assert isinstance(image2_scale2, EpsImagePlugin.EpsImageFile)
image2_scale2.load(scale=2)
with Image.open(FILE2_COMPARE_SCALE2) as image2_scale2_compare:
image2_scale2_compare = image2_scale2_compare.convert("RGB")
image2_scale2_compare.load()
assert_image_similar(image2_scale2, image2_scale2_compare, 10)
image2_scale2_compare_rgb = image2_scale2_compare.convert("RGB")
assert_image_similar(image2_scale2, image2_scale2_compare_rgb, 10)
@pytest.mark.skipif(not HAS_GHOSTSCRIPT, reason="Ghostscript not available")
@ -345,8 +343,8 @@ def test_render_scale2() -> None:
def test_resize(filename: str) -> None:
with Image.open(filename) as im:
new_size = (100, 100)
im = im.resize(new_size)
assert im.size == new_size
im_resized = im.resize(new_size)
assert im_resized.size == new_size
@pytest.mark.skipif(not HAS_GHOSTSCRIPT, reason="Ghostscript not available")

View File

@ -33,7 +33,7 @@ def test_multiple_load_operations() -> None:
assert_image_equal_tofile(im, "Tests/images/gbr.png")
def create_gbr_image(info: dict[str, int] = {}, magic_number=b"") -> BytesIO:
def create_gbr_image(info: dict[str, int] = {}, magic_number: bytes = b"") -> BytesIO:
return BytesIO(
b"".join(
_binary.o32be(i)

View File

@ -327,14 +327,13 @@ def test_loading_multiple_palettes(path: str, mode: str) -> None:
im.seek(1)
assert im.mode == mode
if mode == "RGBA":
im = im.convert("RGB")
im_rgb = im.convert("RGB") if mode == "RGBA" else im
# Check a color only from the old palette
assert im.getpixel((0, 0)) == original_color
assert im_rgb.getpixel((0, 0)) == original_color
# Check a color from the new palette
assert im.getpixel((24, 24)) not in first_frame_colors
assert im_rgb.getpixel((24, 24)) not in first_frame_colors
def test_headers_saving_for_animated_gifs(tmp_path: Path) -> None:
@ -354,16 +353,16 @@ def test_palette_handling(tmp_path: Path) -> None:
# see https://github.com/python-pillow/Pillow/issues/513
with Image.open(TEST_GIF) as im:
im = im.convert("RGB")
im_rgb = im.convert("RGB")
im = im.resize((100, 100), Image.Resampling.LANCZOS)
im2 = im.convert("P", palette=Image.Palette.ADAPTIVE, colors=256)
im_rgb = im_rgb.resize((100, 100), Image.Resampling.LANCZOS)
im_p = im_rgb.convert("P", palette=Image.Palette.ADAPTIVE, colors=256)
f = tmp_path / "temp.gif"
im2.save(f, optimize=True)
f = tmp_path / "temp.gif"
im_p.save(f, optimize=True)
with Image.open(f) as reloaded:
assert_image_similar(im, reloaded.convert("RGB"), 10)
assert_image_similar(im_rgb, reloaded.convert("RGB"), 10)
def test_palette_434(tmp_path: Path) -> None:
@ -383,35 +382,36 @@ def test_palette_434(tmp_path: Path) -> None:
with roundtrip(im, optimize=True) as reloaded:
assert_image_similar(im, reloaded, 1)
im = im.convert("RGB")
# check automatic P conversion
with roundtrip(im) as reloaded:
reloaded = reloaded.convert("RGB")
assert_image_equal(im, reloaded)
im_rgb = im.convert("RGB")
# check automatic P conversion
with roundtrip(im_rgb) as reloaded:
reloaded = reloaded.convert("RGB")
assert_image_equal(im_rgb, reloaded)
@pytest.mark.skipif(not netpbm_available(), reason="Netpbm not available")
def test_save_netpbm_bmp_mode(tmp_path: Path) -> None:
with Image.open(TEST_GIF) as img:
img = img.convert("RGB")
img_rgb = img.convert("RGB")
tempfile = str(tmp_path / "temp.gif")
b = BytesIO()
GifImagePlugin._save_netpbm(img, b, tempfile)
with Image.open(tempfile) as reloaded:
assert_image_similar(img, reloaded.convert("RGB"), 0)
tempfile = str(tmp_path / "temp.gif")
b = BytesIO()
GifImagePlugin._save_netpbm(img_rgb, b, tempfile)
with Image.open(tempfile) as reloaded:
assert_image_similar(img_rgb, reloaded.convert("RGB"), 0)
@pytest.mark.skipif(not netpbm_available(), reason="Netpbm not available")
def test_save_netpbm_l_mode(tmp_path: Path) -> None:
with Image.open(TEST_GIF) as img:
img = img.convert("L")
img_l = img.convert("L")
tempfile = str(tmp_path / "temp.gif")
b = BytesIO()
GifImagePlugin._save_netpbm(img, b, tempfile)
GifImagePlugin._save_netpbm(img_l, b, tempfile)
with Image.open(tempfile) as reloaded:
assert_image_similar(img, reloaded.convert("L"), 0)
assert_image_similar(img_l, reloaded.convert("L"), 0)
def test_seek() -> None:
@ -1038,9 +1038,9 @@ def test_webp_background(tmp_path: Path) -> None:
im.save(out)
# Test non-opaque WebP background
im = Image.new("L", (100, 100), "#000")
im.info["background"] = (0, 0, 0, 0)
im.save(out)
im2 = Image.new("L", (100, 100), "#000")
im2.info["background"] = (0, 0, 0, 0)
im2.save(out)
def test_comment(tmp_path: Path) -> None:
@ -1048,16 +1048,16 @@ def test_comment(tmp_path: Path) -> None:
assert im.info["comment"] == b"File written by Adobe Photoshop\xa8 4.0"
out = tmp_path / "temp.gif"
im = Image.new("L", (100, 100), "#000")
im.info["comment"] = b"Test comment text"
im.save(out)
im2 = Image.new("L", (100, 100), "#000")
im2.info["comment"] = b"Test comment text"
im2.save(out)
with Image.open(out) as reread:
assert reread.info["comment"] == im.info["comment"]
assert reread.info["comment"] == im2.info["comment"]
im.info["comment"] = "Test comment text"
im.save(out)
im2.info["comment"] = "Test comment text"
im2.save(out)
with Image.open(out) as reread:
assert reread.info["comment"] == im.info["comment"].encode()
assert reread.info["comment"] == im2.info["comment"].encode()
# Test that GIF89a is used for comments
assert reread.info["version"] == b"GIF89a"

View File

@ -59,8 +59,9 @@ def test_handler(tmp_path: Path) -> None:
def open(self, im: Image.Image) -> None:
self.opened = True
def load(self, im: Image.Image) -> Image.Image:
def load(self, im: ImageFile.ImageFile) -> Image.Image:
self.loaded = True
assert im.fp is not None
im.fp.close()
return Image.new("RGB", (1, 1))

View File

@ -61,8 +61,9 @@ def test_handler(tmp_path: Path) -> None:
def open(self, im: Image.Image) -> None:
self.opened = True
def load(self, im: Image.Image) -> Image.Image:
def load(self, im: ImageFile.ImageFile) -> Image.Image:
self.loaded = True
assert im.fp is not None
im.fp.close()
return Image.new("RGB", (1, 1))

View File

@ -6,13 +6,13 @@ import pytest
from PIL import Image, IptcImagePlugin, TiffImagePlugin, TiffTags
from .helper import assert_image_equal, hopper
from .helper import assert_image_equal
TEST_FILE = "Tests/images/iptc.jpg"
def create_iptc_image(info: dict[str, int] = {}) -> BytesIO:
def field(tag, value):
def field(tag: tuple[int, int], value: bytes) -> bytes:
return bytes((0x1C,) + tag + (0, len(value))) + value
data = field((3, 60), bytes((info.get("layers", 1), info.get("component", 0))))
@ -85,7 +85,7 @@ def test_getiptcinfo() -> None:
def test_getiptcinfo_jpg_none() -> None:
# Arrange
with hopper() as im:
with Image.open("Tests/images/hopper.jpg") as im:
# Act
iptc = IptcImagePlugin.getiptcinfo(im)
@ -143,6 +143,7 @@ def test_getiptcinfo_tiff() -> None:
# Test with LONG tag type
with Image.open("Tests/images/hopper.Lab.tif") as im:
assert isinstance(im, TiffImagePlugin.TiffImageFile)
im.tag_v2.tagtype[TiffImagePlugin.IPTC_NAA_CHUNK] = TiffTags.LONG
iptc = IptcImagePlugin.getiptcinfo(im)

View File

@ -1133,8 +1133,9 @@ class TestFileCloseW32:
im.save(tmpfile)
im = Image.open(tmpfile)
assert im.fp is not None
assert not im.fp.closed
fp = im.fp
assert not fp.closed
with pytest.raises(OSError):
os.remove(tmpfile)
im.load()

View File

@ -164,7 +164,7 @@ def test_reduce() -> None:
with Image.open("Tests/images/test-card-lossless.jp2") as im:
assert callable(im.reduce)
im.reduce = 2
im.reduce = 2 # type: ignore[assignment, method-assign]
assert im.reduce == 2
im.load()

View File

@ -11,7 +11,15 @@ from typing import Any, NamedTuple
import pytest
from PIL import Image, ImageFilter, ImageOps, TiffImagePlugin, TiffTags, features
from PIL import (
Image,
ImageFile,
ImageFilter,
ImageOps,
TiffImagePlugin,
TiffTags,
features,
)
from PIL.TiffImagePlugin import OSUBFILETYPE, SAMPLEFORMAT, STRIPOFFSETS, SUBIFD
from .helper import (
@ -27,14 +35,13 @@ from .helper import (
@skip_unless_feature("libtiff")
class LibTiffTestCase:
def _assert_noerr(self, tmp_path: Path, im: TiffImagePlugin.TiffImageFile) -> None:
def _assert_noerr(self, tmp_path: Path, im: ImageFile.ImageFile) -> None:
"""Helper tests that assert basic sanity about the g4 tiff reading"""
# 1 bit
assert im.mode == "1"
# Does the data actually load
im.load()
im.getdata()
assert isinstance(im, TiffImagePlugin.TiffImageFile)
assert im._compression == "group4"
@ -355,6 +362,36 @@ class TestFileLibTiff(LibTiffTestCase):
# Should not segfault
im.save(outfile)
@pytest.mark.parametrize("tagtype", (TiffTags.SIGNED_RATIONAL, TiffTags.IFD))
def test_tag_type(
self, tagtype: int, monkeypatch: pytest.MonkeyPatch, tmp_path: Path
) -> None:
monkeypatch.setattr(TiffImagePlugin, "WRITE_LIBTIFF", True)
ifd = TiffImagePlugin.ImageFileDirectory_v2()
ifd[37000] = 100
ifd.tagtype[37000] = tagtype
out = tmp_path / "temp.tif"
im = Image.new("L", (1, 1))
im.save(out, tiffinfo=ifd)
with Image.open(out) as reloaded:
assert isinstance(reloaded, TiffImagePlugin.TiffImageFile)
assert reloaded.tag_v2[37000] == 100
def test_inknames_tag(
self, monkeypatch: pytest.MonkeyPatch, tmp_path: Path
) -> None:
monkeypatch.setattr(TiffImagePlugin, "WRITE_LIBTIFF", True)
out = tmp_path / "temp.tif"
hopper("L").save(out, tiffinfo={333: "name\x00"})
with Image.open(out) as reloaded:
assert isinstance(reloaded, TiffImagePlugin.TiffImageFile)
assert reloaded.tag_v2[333] in ("name", "name\x00")
def test_whitepoint_tag(
self, monkeypatch: pytest.MonkeyPatch, tmp_path: Path
) -> None:
@ -478,12 +515,12 @@ class TestFileLibTiff(LibTiffTestCase):
# and save to compressed tif.
out = tmp_path / "temp.tif"
with Image.open("Tests/images/pport_g4.tif") as im:
im = im.convert("L")
im_l = im.convert("L")
im = im.filter(ImageFilter.GaussianBlur(4))
im.save(out, compression="tiff_adobe_deflate")
im_l = im_l.filter(ImageFilter.GaussianBlur(4))
im_l.save(out, compression="tiff_adobe_deflate")
assert_image_equal_tofile(im, out)
assert_image_equal_tofile(im_l, out)
def test_compressions(self, tmp_path: Path) -> None:
# Test various tiff compressions and assert similar image content but reduced
@ -572,8 +609,9 @@ class TestFileLibTiff(LibTiffTestCase):
im.save(out, compression=compression)
def test_fp_leak(self) -> None:
im: Image.Image | None = Image.open("Tests/images/hopper_g4_500.tif")
im: ImageFile.ImageFile | None = Image.open("Tests/images/hopper_g4_500.tif")
assert im is not None
assert im.fp is not None
fn = im.fp.fileno()
os.fstat(fn)
@ -1049,8 +1087,10 @@ class TestFileLibTiff(LibTiffTestCase):
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")
im_transposed = im.transpose(Image.Transpose.FLIP_LEFT_RIGHT)
assert_image_equal_tofile(
im_transposed, "Tests/images/old-style-jpeg-compression.png"
)
def test_open_missing_samplesperpixel(self) -> None:
with Image.open(
@ -1117,9 +1157,9 @@ class TestFileLibTiff(LibTiffTestCase):
with Image.open("Tests/images/g4_orientation_1.tif") as base_im:
for i in range(2, 9):
with Image.open("Tests/images/g4_orientation_" + str(i) + ".tif") as im:
im = ImageOps.exif_transpose(im)
im_transposed = ImageOps.exif_transpose(im)
assert_image_similar(base_im, im, 0.7)
assert_image_similar(base_im, im_transposed, 0.7)
@pytest.mark.parametrize(
"test_file",

View File

@ -22,10 +22,10 @@ def test_sanity() -> None:
# Adjust for the gamma of 2.2 encoded into the file
lut = ImagePalette.make_gamma_lut(1 / 2.2)
im = Image.merge("RGBA", [chan.point(lut) for chan in im.split()])
im1 = Image.merge("RGBA", [chan.point(lut) for chan in im.split()])
im2 = hopper("RGBA")
assert_image_similar(im, im2, 10)
assert_image_similar(im1, im2, 10)
def test_n_frames() -> None:

View File

@ -300,12 +300,12 @@ def test_save_all() -> None:
im_reloaded.seek(1)
assert_image_similar(im, im_reloaded, 30)
im = Image.new("RGB", (1, 1))
im_rgb = Image.new("RGB", (1, 1))
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)
im_reloaded = roundtrip(im_rgb, save_all=True, append_images=append_images)
assert_image_equal(im, im_reloaded)
assert_image_equal(im_rgb, im_reloaded)
assert isinstance(im_reloaded, MpoImagePlugin.MpoImageFile)
assert im_reloaded.mpinfo is not None
assert im_reloaded.mpinfo[45056] == b"0100"
@ -315,7 +315,7 @@ def test_save_all() -> None:
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)
jpg = roundtrip(im_rgb, save_all=True)
assert "mp" not in jpg.info

View File

@ -101,12 +101,13 @@ class TestFilePng:
assert im.get_format_mimetype() == "image/png"
for mode in ["1", "L", "P", "RGB", "I;16", "I;16B"]:
im = hopper(mode)
im.save(test_file)
im1 = hopper(mode)
im1.save(test_file)
with Image.open(test_file) as reloaded:
if mode == "I;16B":
reloaded = reloaded.convert(mode)
assert_image_equal(reloaded, im)
converted_reloaded = (
reloaded.convert(mode) if mode == "I;16B" else reloaded
)
assert_image_equal(converted_reloaded, im1)
def test_invalid_file(self) -> None:
invalid_file = "Tests/images/flower.jpg"
@ -225,11 +226,11 @@ class TestFilePng:
test_file = "Tests/images/pil123p.png"
with Image.open(test_file) as im:
assert_image(im, "P", (162, 150))
im = im.convert("RGBA")
assert_image(im, "RGBA", (162, 150))
im_rgba = im.convert("RGBA")
assert_image(im_rgba, "RGBA", (162, 150))
# image has 124 unique alpha values
colors = im.getchannel("A").getcolors()
colors = im_rgba.getchannel("A").getcolors()
assert colors is not None
assert len(colors) == 124
@ -239,11 +240,11 @@ class TestFilePng:
assert im.info["transparency"] == (0, 255, 52)
assert_image(im, "RGB", (64, 64))
im = im.convert("RGBA")
assert_image(im, "RGBA", (64, 64))
im_rgba = im.convert("RGBA")
assert_image(im_rgba, "RGBA", (64, 64))
# image has 876 transparent pixels
colors = im.getchannel("A").getcolors()
colors = im_rgba.getchannel("A").getcolors()
assert colors is not None
assert colors[0][0] == 876
@ -262,11 +263,11 @@ class TestFilePng:
assert len(im.info["transparency"]) == 256
assert_image(im, "P", (162, 150))
im = im.convert("RGBA")
assert_image(im, "RGBA", (162, 150))
im_rgba = im.convert("RGBA")
assert_image(im_rgba, "RGBA", (162, 150))
# image has 124 unique alpha values
colors = im.getchannel("A").getcolors()
colors = im_rgba.getchannel("A").getcolors()
assert colors is not None
assert len(colors) == 124
@ -285,13 +286,13 @@ class TestFilePng:
assert im.info["transparency"] == 164
assert im.getpixel((31, 31)) == 164
assert_image(im, "P", (64, 64))
im = im.convert("RGBA")
assert_image(im, "RGBA", (64, 64))
im_rgba = im.convert("RGBA")
assert_image(im_rgba, "RGBA", (64, 64))
assert im.getpixel((31, 31)) == (0, 255, 52, 0)
assert im_rgba.getpixel((31, 31)) == (0, 255, 52, 0)
# image has 876 transparent pixels
colors = im.getchannel("A").getcolors()
colors = im_rgba.getchannel("A").getcolors()
assert colors is not None
assert colors[0][0] == 876
@ -338,6 +339,15 @@ class TestFilePng:
assert colors is not None
assert colors[0][0] == num_transparent
def test_save_1_transparency(self, tmp_path: Path) -> None:
out = tmp_path / "temp.png"
im = Image.new("1", (1, 1), 1)
im.save(out, transparency=1)
with Image.open(out) as reloaded:
assert reloaded.info["transparency"] == 255
def test_save_rgb_single_transparency(self, tmp_path: Path) -> None:
in_file = "Tests/images/caption_6_33_22.png"
with Image.open(in_file) as im:
@ -778,7 +788,9 @@ class TestFilePng:
im.save(test_file, exif=im.getexif())
with Image.open(test_file) as reloaded:
assert isinstance(reloaded, PngImagePlugin.PngImageFile)
exif = reloaded._getexif()
assert exif is not None
assert exif[305] == "Adobe Photoshop CS Macintosh"
def test_exif_argument(self, tmp_path: Path) -> None:
@ -811,7 +823,7 @@ class TestFilePng:
monkeypatch.setattr(sys, "stdout", mystdout)
with Image.open(TEST_PNG_FILE) as im:
im.save(sys.stdout, "PNG")
im.save(sys.stdout, "PNG") # type: ignore[arg-type]
if isinstance(mystdout, MyStdOut):
mystdout = mystdout.buffer

View File

@ -389,7 +389,7 @@ def test_save_stdout(buffer: bool, monkeypatch: pytest.MonkeyPatch) -> None:
monkeypatch.setattr(sys, "stdout", mystdout)
with Image.open(TEST_FILE) as im:
im.save(sys.stdout, "PPM")
im.save(sys.stdout, "PPM") # type: ignore[arg-type]
if isinstance(mystdout, MyStdOut):
mystdout = mystdout.buffer

View File

@ -100,7 +100,7 @@ def test_seek_tell() -> None:
im.seek(2)
layer_number = im.tell()
assert layer_number == 2
assert layer_number == 2
def test_seek_eoferror() -> None:
@ -138,7 +138,7 @@ def test_icc_profile() -> None:
assert "icc_profile" in im.info
icc_profile = im.info["icc_profile"]
assert len(icc_profile) == 3144
assert len(icc_profile) == 3144
def test_no_icc_profile() -> None:
@ -158,17 +158,16 @@ def test_combined_larger_than_size() -> None:
@pytest.mark.parametrize(
"test_file,raises",
"test_file",
[
("Tests/images/timeout-c8efc3fded6426986ba867a399791bae544f59bc.psd", OSError),
("Tests/images/timeout-dedc7a4ebd856d79b4359bbcc79e8ef231ce38f6.psd", OSError),
"Tests/images/timeout-c8efc3fded6426986ba867a399791bae544f59bc.psd",
"Tests/images/timeout-dedc7a4ebd856d79b4359bbcc79e8ef231ce38f6.psd",
],
)
def test_crashes(test_file: str, raises: type[Exception]) -> None:
with open(test_file, "rb") as f:
with pytest.raises(raises):
with Image.open(f):
pass
def test_crashes(test_file: str) -> None:
with pytest.raises(OSError):
with Image.open(test_file):
pass
@pytest.mark.parametrize(
@ -179,8 +178,7 @@ def test_crashes(test_file: str, raises: type[Exception]) -> None:
],
)
def test_layer_crashes(test_file: str) -> None:
with open(test_file, "rb") as f:
with Image.open(f) as im:
assert isinstance(im, PsdImagePlugin.PsdImageFile)
with pytest.raises(SyntaxError):
im.layers
with Image.open(test_file) as im:
assert isinstance(im, PsdImagePlugin.PsdImageFile)
with pytest.raises(SyntaxError):
im.layers

View File

@ -84,8 +84,8 @@ def test_rgbx() -> None:
with Image.open(io.BytesIO(data)) as im:
r, g, b = im.split()
im = Image.merge("RGB", (b, g, r))
assert_image_equal_tofile(im, os.path.join(EXTRA_DIR, "32bpp.png"))
im_rgb = Image.merge("RGB", (b, g, r))
assert_image_equal_tofile(im_rgb, os.path.join(EXTRA_DIR, "32bpp.png"))
@pytest.mark.skipif(

View File

@ -764,9 +764,9 @@ class TestFileTiff:
# Test appending images
mp = BytesIO()
im = Image.new("RGB", (100, 100), "#f00")
im_rgb = Image.new("RGB", (100, 100), "#f00")
ims = [Image.new("RGB", (100, 100), color) for color in ["#0f0", "#00f"]]
im.copy().save(mp, format="TIFF", save_all=True, append_images=ims)
im_rgb.copy().save(mp, format="TIFF", save_all=True, append_images=ims)
mp.seek(0, os.SEEK_SET)
with Image.open(mp) as reread:
@ -778,7 +778,7 @@ class TestFileTiff:
yield from ims
mp = BytesIO()
im.save(mp, format="TIFF", save_all=True, append_images=im_generator(ims))
im_rgb.save(mp, format="TIFF", save_all=True, append_images=im_generator(ims))
mp.seek(0, os.SEEK_SET)
with Image.open(mp) as reread:
@ -971,6 +971,7 @@ class TestFileTiff:
im = Image.open(tmpfile)
fp = im.fp
assert fp is not None
assert not fp.closed
im.load()
assert fp.closed
@ -984,6 +985,7 @@ class TestFileTiff:
with open(tmpfile, "rb") as f:
im = Image.open(f)
fp = im.fp
assert fp is not None
assert not fp.closed
im.load()
assert not fp.closed
@ -1034,8 +1036,9 @@ class TestFileTiffW32:
im.save(tmpfile)
im = Image.open(tmpfile)
assert im.fp is not None
assert not im.fp.closed
fp = im.fp
assert not fp.closed
with pytest.raises(OSError):
os.remove(tmpfile)
im.load()

View File

@ -175,13 +175,13 @@ def test_change_stripbytecounts_tag_type(tmp_path: Path) -> None:
del info[278]
# Resize the image so that STRIPBYTECOUNTS will be larger than a SHORT
im = im.resize((500, 500))
info[TiffImagePlugin.IMAGEWIDTH] = im.width
im_resized = im.resize((500, 500))
info[TiffImagePlugin.IMAGEWIDTH] = im_resized.width
# STRIPBYTECOUNTS can be a SHORT or a LONG
info.tagtype[TiffImagePlugin.STRIPBYTECOUNTS] = TiffTags.SHORT
im.save(out, tiffinfo=info)
im_resized.save(out, tiffinfo=info)
with Image.open(out) as reloaded:
assert isinstance(reloaded, TiffImagePlugin.TiffImageFile)

View File

@ -60,7 +60,6 @@ class TestFileWebp:
assert image.size == (128, 128)
assert image.format == "WEBP"
image.load()
image.getdata()
# generated with:
# dwebp -ppm ../../Tests/images/hopper.webp -o hopper_webp_bits.ppm
@ -77,7 +76,6 @@ class TestFileWebp:
assert image.size == (128, 128)
assert image.format == "WEBP"
image.load()
image.getdata()
if mode == self.rgb_mode:
# generated with: dwebp -ppm temp.webp -o hopper_webp_write.ppm

View File

@ -29,7 +29,6 @@ def test_read_rgba() -> None:
assert image.size == (200, 150)
assert image.format == "WEBP"
image.load()
image.getdata()
image.tobytes()
@ -60,7 +59,6 @@ def test_write_lossless_rgb(tmp_path: Path) -> None:
assert image.size == pil_image.size
assert image.format == "WEBP"
image.load()
image.getdata()
assert_image_equal(image, pil_image)
@ -83,7 +81,6 @@ def test_write_rgba(tmp_path: Path) -> None:
assert image.size == (10, 10)
assert image.format == "WEBP"
image.load()
image.getdata()
assert_image_similar(image, pil_image, 1.0)
@ -133,7 +130,6 @@ def test_write_unsupported_mode_PA(tmp_path: Path) -> None:
assert image.format == "WEBP"
image.load()
image.getdata()
with Image.open(file_path) as im:
target = im.convert("RGBA")

View File

@ -24,6 +24,5 @@ def test_write_lossless_rgb(tmp_path: Path) -> None:
assert image.size == (128, 128)
assert image.format == "WEBP"
image.load()
image.getdata()
assert_image_equal(image, hopper(RGB_MODE))

View File

@ -13,15 +13,15 @@ def test_white() -> None:
k = i.getpixel((0, 0))
L = i.getdata(0)
a = i.getdata(1)
b = i.getdata(2)
L = i.get_flattened_data(0)
a = i.get_flattened_data(1)
b = i.get_flattened_data(2)
assert k == (255, 128, 128)
assert list(L) == [255] * 100
assert list(a) == [128] * 100
assert list(b) == [128] * 100
assert L == (255,) * 100
assert a == (128,) * 100
assert b == (128,) * 100
def test_green() -> None:

View File

@ -613,8 +613,8 @@ class TestImage:
assert im.getpixel((0, 0)) == 0
assert im.getpixel((255, 255)) == 255
with Image.open(target_file) as target:
target = target.convert(mode)
assert_image_equal(im, target)
im_target = target.convert(mode)
assert_image_equal(im, im_target)
def test_radial_gradient_wrong_mode(self) -> None:
# Arrange
@ -638,8 +638,8 @@ class TestImage:
assert im.getpixel((0, 0)) == 255
assert im.getpixel((128, 128)) == 0
with Image.open(target_file) as target:
target = target.convert(mode)
assert_image_equal(im, target)
im_target = target.convert(mode)
assert_image_equal(im, im_target)
def test_register_extensions(self) -> None:
test_format = "a"
@ -663,20 +663,20 @@ class TestImage:
assert_image_equal(im, im.remap_palette(list(range(256))))
# Test identity transform with an RGBA palette
im = Image.new("P", (256, 1))
im_p = Image.new("P", (256, 1))
for x in range(256):
im.putpixel((x, 0), x)
im.putpalette(list(range(256)) * 4, "RGBA")
im_remapped = im.remap_palette(list(range(256)))
assert_image_equal(im, im_remapped)
assert im.palette is not None
im_p.putpixel((x, 0), x)
im_p.putpalette(list(range(256)) * 4, "RGBA")
im_remapped = im_p.remap_palette(list(range(256)))
assert_image_equal(im_p, im_remapped)
assert im_p.palette is not None
assert im_remapped.palette is not None
assert im.palette.palette == im_remapped.palette.palette
assert im_p.palette.palette == im_remapped.palette.palette
# Test illegal image mode
with hopper() as im:
with hopper() as im_hopper:
with pytest.raises(ValueError):
im.remap_palette([])
im_hopper.remap_palette([])
def test_remap_palette_transparency(self) -> None:
im = Image.new("P", (1, 2), (0, 0, 0))
@ -1181,10 +1181,10 @@ class TestImageBytes:
assert reloaded.tobytes() == source_bytes
@pytest.mark.parametrize("mode", Image.MODES)
def test_getdata_putdata(self, mode: str) -> None:
def test_get_flattened_data_putdata(self, mode: str) -> None:
im = hopper(mode)
reloaded = Image.new(mode, im.size)
reloaded.putdata(im.getdata())
reloaded.putdata(im.get_flattened_data())
assert_image_equal(im, reloaded)

View File

@ -78,7 +78,7 @@ def test_fromarray() -> None:
},
)
out = Image.fromarray(wrapped)
return out.mode, out.size, list(i.getdata()) == list(out.getdata())
return out.mode, out.size, i.get_flattened_data() == out.get_flattened_data()
# assert test("1") == ("1", (128, 100), True)
assert test("L") == ("L", (128, 100), True)

View File

@ -80,8 +80,8 @@ def test_16bit() -> None:
_test_float_conversion(im)
for color in (65535, 65536):
im = Image.new("I", (1, 1), color)
im_i16 = im.convert("I;16")
im_i = Image.new("I", (1, 1), color)
im_i16 = im_i.convert("I;16")
assert im_i16.getpixel((0, 0)) == 65535

View File

@ -78,13 +78,13 @@ def test_crop_crash() -> None:
extents = (1, 1, 10, 10)
# works prepatch
with Image.open(test_img) as img:
img2 = img.crop(extents)
img2.load()
img1 = img.crop(extents)
img1.load()
# fail prepatch
with Image.open(test_img) as img:
img = img.crop(extents)
img.load()
img2 = img.crop(extents)
img2.load()
def test_crop_zero() -> None:
@ -95,10 +95,10 @@ def test_crop_zero() -> None:
cropped = im.crop((10, 10, 20, 20))
assert cropped.size == (10, 10)
assert cropped.getdata()[0] == (0, 0, 0)
assert cropped.getpixel((0, 0)) == (0, 0, 0)
im = Image.new("RGB", (0, 0))
cropped = im.crop((10, 10, 20, 20))
assert cropped.size == (10, 10)
assert cropped.getdata()[2] == (0, 0, 0)
assert cropped.getpixel((2, 0)) == (0, 0, 0)

View File

@ -1,23 +1,23 @@
from __future__ import annotations
import pytest
from PIL import Image
from .helper import hopper
def test_sanity() -> None:
data = hopper().getdata()
len(data)
list(data)
data = hopper().get_flattened_data()
assert len(data) == 128 * 128
assert data[0] == (20, 20, 70)
def test_mode() -> None:
def getdata(mode: str) -> tuple[float | tuple[int, ...], int, int]:
def getdata(mode: str) -> tuple[float | tuple[int, ...] | None, int, int]:
im = hopper(mode).resize((32, 30), Image.Resampling.NEAREST)
data = im.getdata()
data = im.get_flattened_data()
return data[0], len(data), len(list(data))
assert getdata("1") == (0, 960, 960)
@ -28,3 +28,13 @@ def test_mode() -> None:
assert getdata("RGBA") == ((11, 13, 52, 255), 960, 960)
assert getdata("CMYK") == ((244, 242, 203, 0), 960, 960)
assert getdata("YCbCr") == ((16, 147, 123), 960, 960)
def test_deprecation() -> None:
im = hopper()
with pytest.warns(DeprecationWarning, match="getdata"):
data = im.getdata()
assert len(data) == 128 * 128
assert data[0] == (20, 20, 70)
assert list(data)[0] == (20, 20, 70)

View File

@ -38,6 +38,7 @@ def test_close_after_load(caplog: pytest.LogCaptureFixture) -> None:
def test_contextmanager() -> None:
fn = None
with Image.open("Tests/images/hopper.gif") as im:
assert im.fp is not None
fn = im.fp.fileno()
os.fstat(fn)

View File

@ -2,6 +2,7 @@ from __future__ import annotations
import sys
from array import array
from typing import cast
import pytest
@ -12,21 +13,19 @@ from .helper import assert_image_equal, hopper
def test_sanity() -> None:
im1 = hopper()
for data in (im1.get_flattened_data(), im1.im):
im2 = Image.new(im1.mode, im1.size, 0)
im2.putdata(data)
data = list(im1.getdata())
assert_image_equal(im1, im2)
im2 = Image.new(im1.mode, im1.size, 0)
im2.putdata(data)
# readonly
im2 = Image.new(im1.mode, im2.size, 0)
im2.readonly = 1
im2.putdata(data)
assert_image_equal(im1, im2)
# readonly
im2 = Image.new(im1.mode, im2.size, 0)
im2.readonly = 1
im2.putdata(data)
assert not im2.readonly
assert_image_equal(im1, im2)
assert not im2.readonly
assert_image_equal(im1, im2)
def test_long_integers() -> None:
@ -60,22 +59,22 @@ def test_mode_with_L_with_float() -> None:
@pytest.mark.parametrize("mode", ("I", "I;16", "I;16L", "I;16B"))
def test_mode_i(mode: str) -> None:
src = hopper("L")
data = list(src.getdata())
data = src.get_flattened_data()
im = Image.new(mode, src.size, 0)
im.putdata(data, 2, 256)
target = [2 * elt + 256 for elt in data]
assert list(im.getdata()) == target
target = tuple(2 * elt + 256 for elt in cast(tuple[int, ...], data))
assert im.get_flattened_data() == target
def test_mode_F() -> None:
src = hopper("L")
data = list(src.getdata())
data = src.get_flattened_data()
im = Image.new("F", src.size, 0)
im.putdata(data, 2.0, 256.0)
target = [2.0 * float(elt) + 256.0 for elt in data]
assert list(im.getdata()) == target
target = tuple(2.0 * float(elt) + 256.0 for elt in cast(tuple[int, ...], data))
assert im.get_flattened_data() == target
def test_array_B() -> None:
@ -86,7 +85,7 @@ def test_array_B() -> None:
im = Image.new("L", (150, 100))
im.putdata(arr)
assert len(im.getdata()) == len(arr)
assert len(im.get_flattened_data()) == len(arr)
def test_array_F() -> None:
@ -97,7 +96,7 @@ def test_array_F() -> None:
arr = array("f", [0.0]) * 15000
im.putdata(arr)
assert len(im.getdata()) == len(arr)
assert len(im.get_flattened_data()) == len(arr)
def test_not_flattened() -> None:

View File

@ -58,8 +58,8 @@ def test_rgba_quantize() -> None:
def test_quantize() -> None:
with Image.open("Tests/images/caption_6_33_22.png") as image:
image = image.convert("RGB")
converted = image.quantize()
converted = image.convert("RGB")
converted = converted.quantize()
assert converted.mode == "P"
assert_image_similar(converted.convert("RGB"), image, 1)
@ -67,13 +67,13 @@ def test_quantize() -> None:
def test_quantize_no_dither() -> None:
image = hopper()
with Image.open("Tests/images/caption_6_33_22.png") as palette:
palette = palette.convert("P")
palette_p = palette.convert("P")
converted = image.quantize(dither=Image.Dither.NONE, palette=palette)
converted = image.quantize(dither=Image.Dither.NONE, palette=palette_p)
assert converted.mode == "P"
assert converted.palette is not None
assert palette.palette is not None
assert converted.palette.palette == palette.palette.palette
assert palette_p.palette is not None
assert converted.palette.palette == palette_p.palette.palette
def test_quantize_no_dither2() -> None:
@ -97,10 +97,10 @@ def test_quantize_no_dither2() -> None:
def test_quantize_dither_diff() -> None:
image = hopper()
with Image.open("Tests/images/caption_6_33_22.png") as palette:
palette = palette.convert("P")
palette_p = palette.convert("P")
dither = image.quantize(dither=Image.Dither.FLOYDSTEINBERG, palette=palette)
nodither = image.quantize(dither=Image.Dither.NONE, palette=palette)
dither = image.quantize(dither=Image.Dither.FLOYDSTEINBERG, palette=palette_p)
nodither = image.quantize(dither=Image.Dither.NONE, palette=palette_p)
assert dither.tobytes() != nodither.tobytes()

View File

@ -160,7 +160,7 @@ class TestImagingCoreResize:
r = self.resize(Image.new("RGB", (0, 0), "white"), (212, 195), resample)
assert r.mode == "RGB"
assert r.size == (212, 195)
assert r.getdata()[0] == (0, 0, 0)
assert r.getpixel((0, 0)) == (0, 0, 0)
def test_unknown_filter(self) -> None:
with pytest.raises(ValueError):
@ -314,8 +314,8 @@ class TestImageResize:
@skip_unless_feature("libtiff")
def test_transposed(self) -> None:
with Image.open("Tests/images/g4_orientation_5.tif") as im:
im = im.resize((64, 64))
assert im.size == (64, 64)
im_resized = im.resize((64, 64))
assert im_resized.size == (64, 64)
@pytest.mark.parametrize(
"mode", ("L", "RGB", "I", "I;16", "I;16L", "I;16B", "I;16N", "F")

View File

@ -43,8 +43,8 @@ def test_angle(angle: int) -> None:
with Image.open("Tests/images/test-card.png") as im:
rotate(im, im.mode, angle)
im = hopper()
assert_image_equal(im.rotate(angle), im.rotate(angle, expand=1))
im_hopper = hopper()
assert_image_equal(im_hopper.rotate(angle), im_hopper.rotate(angle, expand=1))
@pytest.mark.parametrize("angle", (0, 45, 90, 180, 270))
@ -76,9 +76,9 @@ def test_center_0() -> None:
with Image.open("Tests/images/hopper_45.png") as target:
target_origin = target.size[1] / 2
target = target.crop((0, target_origin, 128, target_origin + 128))
im_target = target.crop((0, target_origin, 128, target_origin + 128))
assert_image_similar(im, target, 15)
assert_image_similar(im, im_target, 15)
def test_center_14() -> None:
@ -87,22 +87,22 @@ def test_center_14() -> None:
with Image.open("Tests/images/hopper_45.png") as target:
target_origin = target.size[1] / 2 - 14
target = target.crop((6, target_origin, 128 + 6, target_origin + 128))
im_target = target.crop((6, target_origin, 128 + 6, target_origin + 128))
assert_image_similar(im, target, 10)
assert_image_similar(im, im_target, 10)
def test_translate() -> None:
im = hopper()
with Image.open("Tests/images/hopper_45.png") as target:
target_origin = (target.size[1] / 2 - 64) - 5
target = target.crop(
im_target = target.crop(
(target_origin, target_origin, target_origin + 128, target_origin + 128)
)
im = im.rotate(45, translate=(5, 5), resample=Image.Resampling.BICUBIC)
assert_image_similar(im, target, 1)
assert_image_similar(im, im_target, 1)
def test_fastpath_center() -> None:

View File

@ -159,9 +159,9 @@ def test_reducing_gap_for_DCT_scaling() -> None:
with Image.open("Tests/images/hopper.jpg") as ref:
# thumbnail should call draft with reducing_gap scale
ref.draft(None, (18 * 3, 18 * 3))
ref = ref.resize((18, 18), Image.Resampling.BICUBIC)
im_ref = ref.resize((18, 18), Image.Resampling.BICUBIC)
with Image.open("Tests/images/hopper.jpg") as im:
im.thumbnail((18, 18), Image.Resampling.BICUBIC, reducing_gap=3.0)
assert_image_similar(ref, im, 1.4)
assert_image_similar(im_ref, im, 1.4)

View File

@ -250,14 +250,14 @@ class TestImageTransform:
def test_missing_method_data(self) -> None:
with hopper() as im:
with pytest.raises(ValueError):
im.transform((100, 100), None)
im.transform((100, 100), None) # type: ignore[arg-type]
@pytest.mark.parametrize("resample", (Image.Resampling.BOX, "unknown"))
def test_unknown_resampling_filter(self, resample: Image.Resampling | str) -> None:
with hopper() as im:
(w, h) = im.size
with pytest.raises(ValueError):
im.transform((100, 100), Image.Transform.EXTENT, (0, 0, w, h), resample)
im.transform((100, 100), Image.Transform.EXTENT, (0, 0, w, h), resample) # type: ignore[arg-type]
class TestImageTransformAffine:

View File

@ -274,13 +274,13 @@ def test_simple_lab() -> None:
# not a linear luminance map. so L != 128:
assert k == (137, 128, 128)
l_data = i_lab.getdata(0)
a_data = i_lab.getdata(1)
b_data = i_lab.getdata(2)
l_data = i_lab.get_flattened_data(0)
a_data = i_lab.get_flattened_data(1)
b_data = i_lab.get_flattened_data(2)
assert list(l_data) == [137] * 100
assert list(a_data) == [128] * 100
assert list(b_data) == [128] * 100
assert l_data == (137,) * 100
assert a_data == (128,) * 100
assert b_data == (128,) * 100
def test_lab_color() -> None:

View File

@ -68,10 +68,22 @@ def test_sanity() -> None:
draw.rectangle(list(range(4)))
def test_valueerror() -> None:
def test_new_color() -> None:
with Image.open("Tests/images/chi.gif") as im:
draw = ImageDraw.Draw(im)
assert im.palette is not None
assert len(im.palette.colors) == 249
# Test drawing a new color onto the palette
draw.line((0, 0), fill=(0, 0, 0))
assert im.palette is not None
assert len(im.palette.colors) == 250
assert im.palette.dirty
# Test drawing another new color, now that the palette is dirty
draw.point((0, 0), fill=(1, 0, 0))
assert len(im.palette.colors) == 251
assert im.convert("RGB").getpixel((0, 0)) == (1, 0, 0)
def test_mode_mismatch() -> None:
@ -198,10 +210,10 @@ def test_bitmap() -> None:
im = Image.new("RGB", (W, H))
draw = ImageDraw.Draw(im)
with Image.open("Tests/images/pil123rgba.png") as small:
small = small.resize((50, 50), Image.Resampling.NEAREST)
small_resized = small.resize((50, 50), Image.Resampling.NEAREST)
# Act
draw.bitmap((10, 10), small)
draw.bitmap((10, 10), small_resized)
# Assert
assert_image_equal_tofile(im, "Tests/images/imagedraw_bitmap.png")

View File

@ -702,7 +702,7 @@ def test_variation_get(font: ImageFont.FreeTypeFont) -> None:
font.get_variation_axes()
font = ImageFont.truetype("Tests/fonts/AdobeVFPrototype.ttf")
assert font.get_variation_names(), [
assert font.get_variation_names() == [
b"ExtraLight",
b"Light",
b"Regular",
@ -742,6 +742,21 @@ def test_variation_get(font: ImageFont.FreeTypeFont) -> None:
]
def test_variation_duplicates() -> None:
font = ImageFont.truetype("Tests/fonts/AdobeVFPrototypeDuplicates.ttf")
assert font.get_variation_names() == [
b"ExtraLight",
b"Light",
b"Regular",
b"Semibold",
b"Bold",
b"Black",
b"Black Medium Contrast",
b"Black High Contrast",
b"Default",
]
def _check_text(font: ImageFont.FreeTypeFont, path: str, epsilon: float) -> None:
im = Image.new("RGB", (100, 75), "white")
d = ImageDraw.Draw(im)

View File

@ -15,13 +15,10 @@ def string_to_img(image_string: str) -> Image.Image:
rows = [s for s in image_string.replace(" ", "").split("\n") if len(s)]
height = len(rows)
width = len(rows[0])
im = Image.new("L", (width, height))
for i in range(width):
for j in range(height):
c = rows[j][i]
v = c in "X1"
im.putpixel((i, j), v)
im = Image.new("1", (width, height))
for x in range(width):
for y in range(height):
im.putpixel((x, y), rows[y][x] in "X1")
return im
@ -42,10 +39,10 @@ def img_to_string(im: Image.Image) -> str:
"""Turn a (small) binary image into a string representation"""
chars = ".1"
result = []
for r in range(im.height):
for y in range(im.height):
line = ""
for c in range(im.width):
value = im.getpixel((c, r))
for x in range(im.width):
value = im.getpixel((x, y))
assert not isinstance(value, tuple)
assert value is not None
line += chars[value > 0]
@ -165,10 +162,12 @@ def test_edge() -> None:
)
def test_corner() -> None:
@pytest.mark.parametrize("mode", ("1", "L"))
def test_corner(mode: str) -> None:
# Create a corner detector pattern
mop = ImageMorph.MorphOp(patterns=["1:(... ... ...)->0", "4:(00. 01. ...)->1"])
count, Aout = mop.apply(A)
image = A.convert(mode) if mode == "L" else A
count, Aout = mop.apply(image)
assert count == 5
assert_img_equal_img_string(
Aout,
@ -184,7 +183,7 @@ def test_corner() -> None:
)
# Test the coordinate counting with the same operator
coords = mop.match(A)
coords = mop.match(image)
assert len(coords) == 4
assert tuple(coords) == ((2, 2), (4, 2), (2, 4), (4, 4))
@ -232,15 +231,15 @@ def test_negate() -> None:
def test_incorrect_mode() -> None:
im = hopper("RGB")
mop = ImageMorph.MorphOp(op_name="erosion8")
with pytest.raises(ValueError, match="Image mode must be L"):
mop.apply(im)
with pytest.raises(ValueError, match="Image mode must be L"):
mop.match(im)
with pytest.raises(ValueError, match="Image mode must be L"):
mop.get_on_pixels(im)
with hopper() as im:
with pytest.raises(ValueError, match="Image mode must be 1 or L"):
mop.apply(im)
with pytest.raises(ValueError, match="Image mode must be 1 or L"):
mop.match(im)
with pytest.raises(ValueError, match="Image mode must be 1 or L"):
mop.get_on_pixels(im)
def test_add_patterns() -> None:
@ -281,6 +280,11 @@ def test_pattern_syntax_error(pattern: str) -> None:
lb.build_lut()
def test_build_default_lut() -> None:
lb = ImageMorph.LutBuilder(op_name="corner")
assert lb.build_default_lut() == lb.lut
def test_load_invalid_mrl() -> None:
# Arrange
invalid_mrl = "Tests/images/hopper.png"

View File

@ -261,10 +261,10 @@ def test_colorize_2color() -> None:
# Open test image (256px by 10px, black to white)
with Image.open("Tests/images/bw_gradient.png") as im:
im = im.convert("L")
im_l = im.convert("L")
# Create image with original 2-color functionality
im_test = ImageOps.colorize(im, "red", "green")
im_test = ImageOps.colorize(im_l, "red", "green")
# Test output image (2-color)
left = (0, 1)
@ -301,11 +301,11 @@ def test_colorize_2color_offset() -> None:
# Open test image (256px by 10px, black to white)
with Image.open("Tests/images/bw_gradient.png") as im:
im = im.convert("L")
im_l = im.convert("L")
# Create image with original 2-color functionality with offsets
im_test = ImageOps.colorize(
im, black="red", white="green", blackpoint=50, whitepoint=100
im_l, black="red", white="green", blackpoint=50, whitepoint=100
)
# Test output image (2-color) with offsets
@ -343,11 +343,11 @@ def test_colorize_3color_offset() -> None:
# Open test image (256px by 10px, black to white)
with Image.open("Tests/images/bw_gradient.png") as im:
im = im.convert("L")
im_l = im.convert("L")
# Create image with new three color functionality with offsets
im_test = ImageOps.colorize(
im,
im_l,
black="red",
white="green",
mid="blue",
@ -457,9 +457,9 @@ def test_exif_transpose() -> None:
assert 0x0112 not in transposed_im.getexif()
# Orientation set directly on Image.Exif
im = hopper()
im.getexif()[0x0112] = 3
transposed_im = ImageOps.exif_transpose(im)
im1 = hopper()
im1.getexif()[0x0112] = 3
transposed_im = ImageOps.exif_transpose(im1)
assert 0x0112 not in transposed_im.getexif()

View File

@ -49,6 +49,12 @@ def test_getcolor() -> None:
palette.getcolor("unknown") # type: ignore[arg-type]
def test_getcolor_rgba() -> None:
palette = ImagePalette.ImagePalette("RGBA", (1, 2, 3, 4))
palette.getcolor((5, 6, 7, 8))
assert palette.palette == b"\x01\x02\x03\x04\x05\x06\x07\x08"
def test_getcolor_rgba_color_rgb_palette() -> None:
palette = ImagePalette.ImagePalette("RGB")

View File

@ -2,7 +2,7 @@ from __future__ import annotations
import pytest
from PIL import Image, ImageDraw, ImageFont, ImageText
from PIL import Image, ImageDraw, ImageFont, ImageText, features
from .helper import assert_image_similar_tofile, skip_unless_feature
@ -20,37 +20,75 @@ def layout_engine(request: pytest.FixtureRequest) -> ImageFont.Layout:
return request.param
@pytest.fixture(scope="module")
def font(layout_engine: ImageFont.Layout) -> ImageFont.FreeTypeFont:
return ImageFont.truetype(FONT_PATH, 20, layout_engine=layout_engine)
@pytest.fixture(
scope="module",
params=[
None,
pytest.param(ImageFont.Layout.BASIC, marks=skip_unless_feature("freetype2")),
pytest.param(ImageFont.Layout.RAQM, marks=skip_unless_feature("raqm")),
],
)
def font(
request: pytest.FixtureRequest,
) -> ImageFont.ImageFont | ImageFont.FreeTypeFont:
layout_engine = request.param
if layout_engine is None:
return ImageFont.load_default_imagefont()
else:
return ImageFont.truetype(FONT_PATH, 20, layout_engine=layout_engine)
def test_get_length(font: ImageFont.FreeTypeFont) -> None:
assert ImageText.Text("A", font).get_length() == 12
assert ImageText.Text("AB", font).get_length() == 24
assert ImageText.Text("M", font).get_length() == 12
assert ImageText.Text("y", font).get_length() == 12
assert ImageText.Text("a", font).get_length() == 12
def test_get_length(font: ImageFont.ImageFont | ImageFont.FreeTypeFont) -> None:
factor = 1 if isinstance(font, ImageFont.ImageFont) else 2
assert ImageText.Text("A", font).get_length() == 6 * factor
assert ImageText.Text("AB", font).get_length() == 12 * factor
assert ImageText.Text("M", font).get_length() == 6 * factor
assert ImageText.Text("y", font).get_length() == 6 * factor
assert ImageText.Text("a", font).get_length() == 6 * factor
text = ImageText.Text("\n", font)
with pytest.raises(ValueError, match="can't measure length of multiline text"):
text.get_length()
def test_get_bbox(font: ImageFont.FreeTypeFont) -> None:
assert ImageText.Text("A", font).get_bbox() == (0, 4, 12, 16)
assert ImageText.Text("AB", font).get_bbox() == (0, 4, 24, 16)
assert ImageText.Text("M", font).get_bbox() == (0, 4, 12, 16)
assert ImageText.Text("y", font).get_bbox() == (0, 7, 12, 20)
assert ImageText.Text("a", font).get_bbox() == (0, 7, 12, 16)
@pytest.mark.parametrize(
"text, expected",
(
("A", (0, 4, 12, 16)),
("AB", (0, 4, 24, 16)),
("M", (0, 4, 12, 16)),
("y", (0, 7, 12, 20)),
("a", (0, 7, 12, 16)),
),
)
def test_get_bbox(
font: ImageFont.ImageFont | ImageFont.FreeTypeFont,
text: str,
expected: tuple[int, int, int, int],
) -> None:
if isinstance(font, ImageFont.ImageFont):
expected = (0, 0, expected[2] // 2, 11)
assert ImageText.Text(text, font).get_bbox() == expected
def test_standard_embedded_color(layout_engine: ImageFont.Layout) -> None:
font = ImageFont.truetype(FONT_PATH, 40, layout_engine=layout_engine)
text = ImageText.Text("Hello World!", font)
text.embed_color()
if features.check_module("freetype2"):
font = ImageFont.truetype(FONT_PATH, 40, layout_engine=layout_engine)
text = ImageText.Text("Hello World!", font)
text.embed_color()
assert text.get_length() == 288
im = Image.new("RGB", (300, 64), "white")
draw = ImageDraw.Draw(im)
draw.text((10, 10), text, "#fa6")
im = Image.new("RGB", (300, 64), "white")
draw = ImageDraw.Draw(im)
draw.text((10, 10), text, "#fa6")
assert_image_similar_tofile(im, "Tests/images/standard_embedded.png", 3.1)
assert_image_similar_tofile(im, "Tests/images/standard_embedded.png", 3.1)
text = ImageText.Text("", mode="1")
with pytest.raises(
ValueError, match="Embedded color supported only in RGB and RGBA modes"
):
text.embed_color()
@skip_unless_feature("freetype2")

View File

@ -20,21 +20,19 @@ TEST_IMAGE_SIZE = (10, 10)
def test_numpy_to_image() -> None:
def to_image(dtype: npt.DTypeLike, bands: int = 1, boolean: int = 0) -> Image.Image:
data = tuple(range(100))
if bands == 1:
if boolean:
data = [0, 255] * 50
else:
data = list(range(100))
data = (0, 255) * 50
a = numpy.array(data, dtype=dtype)
a.shape = TEST_IMAGE_SIZE
i = Image.fromarray(a)
assert list(i.getdata()) == data
assert i.get_flattened_data() == data
else:
data = list(range(100))
a = numpy.array([[x] * bands for x in data], dtype=dtype)
a.shape = TEST_IMAGE_SIZE[0], TEST_IMAGE_SIZE[1], bands
i = Image.fromarray(a)
assert list(i.getchannel(0).getdata()) == list(range(100))
assert i.get_flattened_data(0) == tuple(range(100))
return i
# Check supported 1-bit integer formats
@ -191,7 +189,7 @@ def test_putdata() -> None:
arr = numpy.zeros((15000,), numpy.float32)
im.putdata(arr)
assert len(im.getdata()) == len(arr)
assert len(im.get_flattened_data()) == len(arr)
def test_resize() -> None:
@ -248,7 +246,7 @@ def test_bool() -> None:
a[0][0] = True
im2 = Image.fromarray(a)
assert im2.getdata()[0] == 255
assert im2.getpixel((0, 0)) == 255
def test_no_resource_warning_for_numpy_array() -> None:

View File

@ -19,30 +19,28 @@ def helper_pickle_file(
# Arrange
with Image.open(test_file) as im:
filename = tmp_path / "temp.pkl"
if mode:
im = im.convert(mode)
converted_im = im.convert(mode) if mode else im
# Act
with open(filename, "wb") as f:
pickle.dump(im, f, protocol)
pickle.dump(converted_im, f, protocol)
with open(filename, "rb") as f:
loaded_im = pickle.load(f)
# Assert
assert im == loaded_im
assert converted_im == loaded_im
def helper_pickle_string(protocol: int, test_file: str, mode: str | None) -> None:
with Image.open(test_file) as im:
if mode:
im = im.convert(mode)
converted_im = im.convert(mode) if mode else im
# Act
dumped_string = pickle.dumps(im, protocol)
dumped_string = pickle.dumps(converted_im, protocol)
loaded_im = pickle.loads(dumped_string)
# Assert
assert im == loaded_im
assert converted_im == loaded_im
@pytest.mark.parametrize(
@ -90,18 +88,18 @@ def test_pickle_la_mode_with_palette(tmp_path: Path) -> None:
# Arrange
filename = tmp_path / "temp.pkl"
with Image.open("Tests/images/hopper.jpg") as im:
im = im.convert("PA")
im_pa = im.convert("PA")
# Act / Assert
for protocol in range(pickle.HIGHEST_PROTOCOL + 1):
im._mode = "LA"
im_pa._mode = "LA"
with open(filename, "wb") as f:
pickle.dump(im, f, protocol)
pickle.dump(im_pa, f, protocol)
with open(filename, "rb") as f:
loaded_im = pickle.load(f)
im._mode = "PA"
assert im == loaded_im
im_pa._mode = "PA"
assert im_pa == loaded_im
@skip_unless_feature("webp")

View File

@ -6,10 +6,15 @@ import pytest
from PIL import __version__
TYPE_CHECKING = False
if TYPE_CHECKING:
from importlib.metadata import PackageMetadata
pyroma = pytest.importorskip("pyroma", reason="Pyroma not installed")
def map_metadata_keys(md):
def map_metadata_keys(md: PackageMetadata) -> dict[str, str | list[str] | None]:
# Convert installed wheel metadata into canonical Core Metadata 2.4 format.
# This was a utility method in pyroma 4.3.3; it was removed in 5.0.
# This implementation is constructed from the relevant logic from
@ -17,16 +22,16 @@ def map_metadata_keys(md):
# upstream to Pyroma as https://github.com/regebro/pyroma/pull/116,
# so it may be possible to simplify this test in future.
data = {}
for key in set(md.keys()):
for key in set(md):
value = md.get_all(key)
key = pyroma.projectdata.normalize(key)
if len(value) == 1:
value = value[0]
if value.strip() == "UNKNOWN":
continue
data[key] = value
if value is not None and len(value) == 1:
first_value = value[0]
if first_value.strip() != "UNKNOWN":
data[key] = first_value
else:
data[key] = value
return data

View File

@ -49,11 +49,13 @@ class TestShellInjection:
@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:
im = im.convert("RGB")
self.assert_save_filename_check(tmp_path, im, GifImagePlugin._save_netpbm)
im_rgb = im.convert("RGB")
self.assert_save_filename_check(
tmp_path, im_rgb, GifImagePlugin._save_netpbm
)
@pytest.mark.skipif(not netpbm_available(), reason="Netpbm not available")
def test_save_netpbm_filename_l_mode(self, tmp_path: Path) -> None:
with Image.open(TEST_GIF) as im:
im = im.convert("L")
self.assert_save_filename_check(tmp_path, im, GifImagePlugin._save_netpbm)
im_l = im.convert("L")
self.assert_save_filename_check(tmp_path, im_l, GifImagePlugin._save_netpbm)

View File

@ -2,7 +2,7 @@
# install libimagequant
archive_name=libimagequant
archive_version=4.4.0
archive_version=4.4.1
archive=$archive_name-$archive_version

View File

@ -11,7 +11,7 @@ import subprocess
TYPE_CHECKING = False
if TYPE_CHECKING:
from sphinx.application import Sphinx
from typing import Any
DOC_NAME_REGEX = re.compile(r"releasenotes/\d+\.\d+\.\d+")
VERSION_TITLE_REGEX = re.compile(r"^(\d+\.\d+\.\d+)\n-+\n")
@ -28,7 +28,7 @@ def get_date_for(git_version: str) -> str | None:
return out.split()[0]
def add_date(app: Sphinx, doc_name: str, source: list[str]) -> None:
def add_date(app: Any, doc_name: str, source: list[str]) -> None:
if DOC_NAME_REGEX.match(doc_name) and (m := VERSION_TITLE_REGEX.match(source[0])):
old_title = m.group(1)
@ -43,6 +43,6 @@ def add_date(app: Sphinx, doc_name: str, source: list[str]) -> None:
source[0] = result
def setup(app: Sphinx) -> dict[str, bool]:
def setup(app: Any) -> dict[str, bool]:
app.connect("source-read", add_date)
return {"parallel_read_safe": True}

View File

@ -73,6 +73,16 @@ Image._show
``Image._show`` has been deprecated, and will be removed in Pillow 13 (2026-10-15).
Use :py:meth:`~PIL.ImageShow.show` instead.
Image getdata()
~~~~~~~~~~~~~~~
.. deprecated:: 12.1.0
:py:meth:`~PIL.Image.Image.getdata` has been deprecated.
:py:meth:`~PIL.Image.Image.get_flattened_data` can be used instead. This new method is
identical, except that it returns a tuple of pixel values, instead of an internal
Pillow data type.
Removed features
----------------

View File

@ -213,6 +213,7 @@ class DdsImageFile(ImageFile.ImageFile):
format_description = "DirectDraw Surface"
def _open(self) -> None:
assert self.fp is not None
if not _accept(self.fp.read(4)):
msg = "not a DDS file"
raise SyntaxError(msg)

View File

@ -999,7 +999,7 @@ where applicable:
The number of times to loop this APNG, 0 indicates infinite looping.
**duration**
The time to display this APNG frame (in milliseconds).
The time to display this APNG frame (in milliseconds), given as a float.
.. note::
@ -1041,9 +1041,8 @@ following parameters can also be set:
Defaults to 0.
**duration**
Integer (or list or tuple of integers) length of time to display this APNG frame
(in milliseconds).
Defaults to 0.
The length of time (or list or tuple of lengths of time) to display this APNG frame
(in milliseconds). Defaults to 0.
**disposal**
An integer (or list or tuple of integers) specifying the APNG disposal

View File

@ -64,7 +64,7 @@ Many of Pillow's features require external libraries:
* **libimagequant** provides improved color quantization
* Pillow has been tested with libimagequant **2.6-4.4.0**
* Pillow has been tested with libimagequant **2.6-4.4.1**
* Libimagequant is licensed GPLv3, which is more restrictive than
the Pillow license, therefore we will not be distributing binaries
with libimagequant support enabled.
@ -116,7 +116,7 @@ Many of Pillow's features require external libraries:
.. Note:: ``redhat-rpm-config`` is required on Fedora 23, but not earlier versions.
Prerequisites for **Ubuntu 16.04 LTS - 22.04 LTS** are installed with::
Prerequisites for **Ubuntu 16.04 LTS - 24.04 LTS** are installed with::
sudo apt-get install libtiff5-dev libjpeg8-dev libopenjp2-7-dev zlib1g-dev \
libfreetype6-dev liblcms2-dev libwebp-dev tcl8.6-dev tk8.6-dev python3-tk \

View File

@ -33,10 +33,10 @@ These platforms are built and tested for every change.
+----------------------------------+----------------------------+---------------------+
| Debian 13 Trixie | 3.13 | x86, x86-64 |
+----------------------------------+----------------------------+---------------------+
| Fedora 41 | 3.13 | x86-64 |
+----------------------------------+----------------------------+---------------------+
| Fedora 42 | 3.13 | x86-64 |
+----------------------------------+----------------------------+---------------------+
| Fedora 43 | 3.14 | x86-64 |
+----------------------------------+----------------------------+---------------------+
| Gentoo | 3.12 | x86-64 |
+----------------------------------+----------------------------+---------------------+
| macOS 15 Sequoia | 3.10 | x86-64 |
@ -53,8 +53,8 @@ These platforms are built and tested for every change.
| | | s390x |
+----------------------------------+----------------------------+---------------------+
| Windows Server 2022 | 3.10 | x86 |
| +----------------------------+---------------------+
| | 3.11, 3.12, 3.13, 3.14, | x86-64 |
+----------------------------------+----------------------------+---------------------+
| Windows Server 2025 | 3.11, 3.12, 3.13, 3.14, | x86-64 |
| | PyPy3 | |
| +----------------------------+---------------------+
| | 3.12 (MinGW) | x86-64 |
@ -71,100 +71,102 @@ These platforms have been reported to work at the versions mentioned.
Contributors please test Pillow on your platform then update this
document and send a pull request.
+----------------------------------+----------------------------+------------------+--------------+
| Operating system | | Tested Python | | Latest tested | | Tested |
| | | versions | | Pillow version | | processors |
+==================================+============================+==================+==============+
| macOS 26 Tahoe | 3.9, 3.10, 3.11, 3.12, 3.13| 11.3.0 |arm |
+----------------------------------+----------------------------+------------------+--------------+
| macOS 15 Sequoia | 3.9, 3.10, 3.11, 3.12, 3.13| 11.3.0 |arm |
| +----------------------------+------------------+ |
| | 3.8 | 10.4.0 | |
+----------------------------------+----------------------------+------------------+--------------+
| macOS 14 Sonoma | 3.8, 3.9, 3.10, 3.11, 3.12 | 10.4.0 |arm |
+----------------------------------+----------------------------+------------------+--------------+
| macOS 13 Ventura | 3.8, 3.9, 3.10, 3.11 | 10.0.1 |arm |
| +----------------------------+------------------+ |
| | 3.7 | 9.5.0 | |
+----------------------------------+----------------------------+------------------+--------------+
| macOS 12 Monterey | 3.7, 3.8, 3.9, 3.10, 3.11 | 9.3.0 |arm |
+----------------------------------+----------------------------+------------------+--------------+
| macOS 11 Big Sur | 3.7, 3.8, 3.9, 3.10 | 8.4.0 |arm |
| +----------------------------+------------------+--------------+
| | 3.7, 3.8, 3.9, 3.10, 3.11 | 9.4.0 |x86-64 |
| +----------------------------+------------------+ |
| | 3.6 | 8.4.0 | |
+----------------------------------+----------------------------+------------------+--------------+
| macOS 10.15 Catalina | 3.6, 3.7, 3.8, 3.9 | 8.3.2 |x86-64 |
| +----------------------------+------------------+ |
| | 3.5 | 7.2.0 | |
+----------------------------------+----------------------------+------------------+--------------+
| macOS 10.14 Mojave | 3.5, 3.6, 3.7, 3.8 | 7.2.0 |x86-64 |
| +----------------------------+------------------+ |
| | 2.7 | 6.0.0 | |
| +----------------------------+------------------+ |
| | 3.4 | 5.4.1 | |
+----------------------------------+----------------------------+------------------+--------------+
| macOS 10.13 High Sierra | 2.7, 3.4, 3.5, 3.6 | 4.2.1 |x86-64 |
+----------------------------------+----------------------------+------------------+--------------+
| macOS 10.12 Sierra | 2.7, 3.4, 3.5, 3.6 | 4.1.1 |x86-64 |
+----------------------------------+----------------------------+------------------+--------------+
| Mac OS X 10.11 El Capitan | 2.7, 3.4, 3.5, 3.6, 3.7 | 5.4.1 |x86-64 |
| +----------------------------+------------------+ |
| | 3.3 | 4.1.0 | |
+----------------------------------+----------------------------+------------------+--------------+
| Mac OS X 10.9 Mavericks | 2.7, 3.2, 3.3, 3.4 | 3.0.0 |x86-64 |
+----------------------------------+----------------------------+------------------+--------------+
| Mac OS X 10.8 Mountain Lion | 2.6, 2.7, 3.2, 3.3 | |x86-64 |
+----------------------------------+----------------------------+------------------+--------------+
| Redhat Linux 6 | 2.6 | |x86 |
+----------------------------------+----------------------------+------------------+--------------+
| CentOS 6.3 | 2.7, 3.3 | |x86 |
+----------------------------------+----------------------------+------------------+--------------+
| CentOS 8 | 3.9 | 9.0.0 |x86-64 |
+----------------------------------+----------------------------+------------------+--------------+
| Fedora 23 | 2.7, 3.4 | 3.1.0 |x86-64 |
+----------------------------------+----------------------------+------------------+--------------+
| Ubuntu Linux 12.04 LTS (Precise) | | 2.6, 3.2, 3.3, 3.4, 3.5 | 3.4.1 |x86,x86-64 |
| | | PyPy5.3.1, PyPy3 v2.4.0 | | |
| +----------------------------+------------------+--------------+
| | 2.7 | 4.3.0 |x86-64 |
| +----------------------------+------------------+--------------+
| | 2.7, 3.2 | 3.4.1 |ppc |
+----------------------------------+----------------------------+------------------+--------------+
| Ubuntu Linux 10.04 LTS (Lucid) | 2.6 | 2.3.0 |x86,x86-64 |
+----------------------------------+----------------------------+------------------+--------------+
| Debian 8.2 Jessie | 2.7, 3.4 | 3.1.0 |x86-64 |
+----------------------------------+----------------------------+------------------+--------------+
| Raspbian Jessie | 2.7, 3.4 | 3.1.0 |arm |
+----------------------------------+----------------------------+------------------+--------------+
| Raspbian Stretch | 2.7, 3.5 | 4.0.0 |arm |
+----------------------------------+----------------------------+------------------+--------------+
| Raspberry Pi OS | 3.6, 3.7, 3.8, 3.9 | 8.2.0 |arm |
| +----------------------------+------------------+ |
| | 2.7 | 6.2.2 | |
+----------------------------------+----------------------------+------------------+--------------+
| Gentoo Linux | 2.7, 3.2 | 2.1.0 |x86-64 |
+----------------------------------+----------------------------+------------------+--------------+
| FreeBSD 11.1 | 2.7, 3.4, 3.5, 3.6 | 4.3.0 |x86-64 |
+----------------------------------+----------------------------+------------------+--------------+
| FreeBSD 10.3 | 2.7, 3.4, 3.5 | 4.2.0 |x86-64 |
+----------------------------------+----------------------------+------------------+--------------+
| FreeBSD 10.2 | 2.7, 3.4 | 3.1.0 |x86-64 |
+----------------------------------+----------------------------+------------------+--------------+
| Windows 11 23H2 | 3.9, 3.10, 3.11, 3.12, 3.13| 11.0.0 |arm64 |
+----------------------------------+----------------------------+------------------+--------------+
| Windows 11 Pro | 3.11, 3.12 | 10.2.0 |x86-64 |
+----------------------------------+----------------------------+------------------+--------------+
| Windows 10 | 3.7 | 7.1.0 |x86-64 |
+----------------------------------+----------------------------+------------------+--------------+
| Windows 10/Cygwin 3.3 | 3.6, 3.7, 3.8, 3.9 | 8.4.0 |x86-64 |
+----------------------------------+----------------------------+------------------+--------------+
| Windows 8.1 Pro | 2.6, 2.7, 3.2, 3.3, 3.4 | 2.4.0 |x86,x86-64 |
+----------------------------------+----------------------------+------------------+--------------+
| Windows 8 Pro | 2.6, 2.7, 3.2, 3.3, 3.4a3 | 2.2.0 |x86,x86-64 |
+----------------------------------+----------------------------+------------------+--------------+
| Windows 7 Professional | 3.7 | 7.0.0 |x86,x86-64 |
+----------------------------------+----------------------------+------------------+--------------+
| Windows Server 2008 R2 Enterprise| 3.3 | |x86-64 |
+----------------------------------+----------------------------+------------------+--------------+
+----------------------------------+-----------------------------+------------------+--------------+
| Operating system | | Tested Python | | Latest tested | | Tested |
| | | versions | | Pillow version | | processors |
+==================================+=============================+==================+==============+
| macOS 26 Tahoe | 3.10, 3.11, 3.12, 3.13, 3.14| 12.0.0 |arm |
| +-----------------------------+------------------+ |
| | 3.9 | 11.3.0 | |
+----------------------------------+-----------------------------+------------------+--------------+
| macOS 15 Sequoia | 3.9, 3.10, 3.11, 3.12, 3.13 | 11.3.0 |arm |
| +-----------------------------+------------------+ |
| | 3.8 | 10.4.0 | |
+----------------------------------+-----------------------------+------------------+--------------+
| macOS 14 Sonoma | 3.8, 3.9, 3.10, 3.11, 3.12 | 10.4.0 |arm |
+----------------------------------+-----------------------------+------------------+--------------+
| macOS 13 Ventura | 3.8, 3.9, 3.10, 3.11 | 10.0.1 |arm |
| +-----------------------------+------------------+ |
| | 3.7 | 9.5.0 | |
+----------------------------------+-----------------------------+------------------+--------------+
| macOS 12 Monterey | 3.7, 3.8, 3.9, 3.10, 3.11 | 9.3.0 |arm |
+----------------------------------+-----------------------------+------------------+--------------+
| macOS 11 Big Sur | 3.7, 3.8, 3.9, 3.10 | 8.4.0 |arm |
| +-----------------------------+------------------+--------------+
| | 3.7, 3.8, 3.9, 3.10, 3.11 | 9.4.0 |x86-64 |
| +-----------------------------+------------------+ |
| | 3.6 | 8.4.0 | |
+----------------------------------+-----------------------------+------------------+--------------+
| macOS 10.15 Catalina | 3.6, 3.7, 3.8, 3.9 | 8.3.2 |x86-64 |
| +-----------------------------+------------------+ |
| | 3.5 | 7.2.0 | |
+----------------------------------+-----------------------------+------------------+--------------+
| macOS 10.14 Mojave | 3.5, 3.6, 3.7, 3.8 | 7.2.0 |x86-64 |
| +-----------------------------+------------------+ |
| | 2.7 | 6.0.0 | |
| +-----------------------------+------------------+ |
| | 3.4 | 5.4.1 | |
+----------------------------------+-----------------------------+------------------+--------------+
| macOS 10.13 High Sierra | 2.7, 3.4, 3.5, 3.6 | 4.2.1 |x86-64 |
+----------------------------------+-----------------------------+------------------+--------------+
| macOS 10.12 Sierra | 2.7, 3.4, 3.5, 3.6 | 4.1.1 |x86-64 |
+----------------------------------+-----------------------------+------------------+--------------+
| Mac OS X 10.11 El Capitan | 2.7, 3.4, 3.5, 3.6, 3.7 | 5.4.1 |x86-64 |
| +-----------------------------+------------------+ |
| | 3.3 | 4.1.0 | |
+----------------------------------+-----------------------------+------------------+--------------+
| Mac OS X 10.9 Mavericks | 2.7, 3.2, 3.3, 3.4 | 3.0.0 |x86-64 |
+----------------------------------+-----------------------------+------------------+--------------+
| Mac OS X 10.8 Mountain Lion | 2.6, 2.7, 3.2, 3.3 | |x86-64 |
+----------------------------------+-----------------------------+------------------+--------------+
| Redhat Linux 6 | 2.6 | |x86 |
+----------------------------------+-----------------------------+------------------+--------------+
| CentOS 6.3 | 2.7, 3.3 | |x86 |
+----------------------------------+-----------------------------+------------------+--------------+
| CentOS 8 | 3.9 | 9.0.0 |x86-64 |
+----------------------------------+-----------------------------+------------------+--------------+
| Fedora 23 | 2.7, 3.4 | 3.1.0 |x86-64 |
+----------------------------------+-----------------------------+------------------+--------------+
| Ubuntu Linux 12.04 LTS (Precise) | | 2.6, 3.2, 3.3, 3.4, 3.5 | 3.4.1 |x86,x86-64 |
| | | PyPy5.3.1, PyPy3 v2.4.0 | | |
| +-----------------------------+------------------+--------------+
| | 2.7 | 4.3.0 |x86-64 |
| +-----------------------------+------------------+--------------+
| | 2.7, 3.2 | 3.4.1 |ppc |
+----------------------------------+-----------------------------+------------------+--------------+
| Ubuntu Linux 10.04 LTS (Lucid) | 2.6 | 2.3.0 |x86,x86-64 |
+----------------------------------+-----------------------------+------------------+--------------+
| Debian 8.2 Jessie | 2.7, 3.4 | 3.1.0 |x86-64 |
+----------------------------------+-----------------------------+------------------+--------------+
| Raspbian Jessie | 2.7, 3.4 | 3.1.0 |arm |
+----------------------------------+-----------------------------+------------------+--------------+
| Raspbian Stretch | 2.7, 3.5 | 4.0.0 |arm |
+----------------------------------+-----------------------------+------------------+--------------+
| Raspberry Pi OS | 3.6, 3.7, 3.8, 3.9 | 8.2.0 |arm |
| +-----------------------------+------------------+ |
| | 2.7 | 6.2.2 | |
+----------------------------------+-----------------------------+------------------+--------------+
| Gentoo Linux | 2.7, 3.2 | 2.1.0 |x86-64 |
+----------------------------------+-----------------------------+------------------+--------------+
| FreeBSD 11.1 | 2.7, 3.4, 3.5, 3.6 | 4.3.0 |x86-64 |
+----------------------------------+-----------------------------+------------------+--------------+
| FreeBSD 10.3 | 2.7, 3.4, 3.5 | 4.2.0 |x86-64 |
+----------------------------------+-----------------------------+------------------+--------------+
| FreeBSD 10.2 | 2.7, 3.4 | 3.1.0 |x86-64 |
+----------------------------------+-----------------------------+------------------+--------------+
| Windows 11 23H2 | 3.9, 3.10, 3.11, 3.12, 3.13 | 11.0.0 |arm64 |
+----------------------------------+-----------------------------+------------------+--------------+
| Windows 11 Pro | 3.11, 3.12 | 10.2.0 |x86-64 |
+----------------------------------+-----------------------------+------------------+--------------+
| Windows 10 | 3.7 | 7.1.0 |x86-64 |
+----------------------------------+-----------------------------+------------------+--------------+
| Windows 10/Cygwin 3.3 | 3.6, 3.7, 3.8, 3.9 | 8.4.0 |x86-64 |
+----------------------------------+-----------------------------+------------------+--------------+
| Windows 8.1 Pro | 2.6, 2.7, 3.2, 3.3, 3.4 | 2.4.0 |x86,x86-64 |
+----------------------------------+-----------------------------+------------------+--------------+
| Windows 8 Pro | 2.6, 2.7, 3.2, 3.3, 3.4a3 | 2.2.0 |x86,x86-64 |
+----------------------------------+-----------------------------+------------------+--------------+
| Windows 7 Professional | 3.7 | 7.0.0 |x86,x86-64 |
+----------------------------------+-----------------------------+------------------+--------------+
| Windows Server 2008 R2 Enterprise| 3.3 | |x86-64 |
+----------------------------------+-----------------------------+------------------+--------------+

View File

@ -191,6 +191,7 @@ This helps to get the bounding box coordinates of the input image::
.. automethod:: PIL.Image.Image.getchannel
.. automethod:: PIL.Image.Image.getcolors
.. automethod:: PIL.Image.Image.getdata
.. automethod:: PIL.Image.Image.get_flattened_data
.. automethod:: PIL.Image.Image.getexif
.. automethod:: PIL.Image.Image.getextrema
.. automethod:: PIL.Image.Image.getpalette

View File

@ -44,9 +44,11 @@ or the clipboard to a PIL image memory.
.. versionadded:: 7.1.0
:param window:
HWND, to capture a single window. Windows only.
Capture a single window. On Windows, this is a HWND. On macOS, this is a
CGWindowID.
.. versionadded:: 11.2.1
.. versionadded:: 11.2.1 Windows support
.. versionadded:: 12.1.0 macOS support
:return: An image
.. py:function:: grabclipboard()

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