Merge branch 'main' of upstream into add-cygwin-to-ci

This commit is contained in:
DWesl 2021-12-26 14:28:29 -05:00
commit fa536b4d34
23 changed files with 518 additions and 55 deletions

View File

@ -60,7 +60,7 @@ jobs:
pushd depends && ./install_extra_test_images.sh && popd
- name: Build Pillow
run: CFLAGS="-coverage" python3 setup.py build_ext install
run: CFLAGS="-coverage" python3 -m pip install --global-option="build_ext" .
- name: Test Pillow
run: |

View File

@ -9,7 +9,7 @@ jobs:
fail-fast: false
matrix:
os: [
"macOS-latest",
"macos-latest",
"ubuntu-latest",
]
python-version: [
@ -29,7 +29,7 @@ jobs:
# Include new variables for Codecov
- os: ubuntu-latest
codecov-flag: GHA_Ubuntu
- os: macOS-latest
- os: macos-latest
codecov-flag: GHA_macOS
runs-on: ${{ matrix.os }}

26
.github/workflows/tidelift.yml vendored Normal file
View File

@ -0,0 +1,26 @@
name: Tidelift Align
on:
schedule:
- cron: "30 2 * * *" # daily at 02:30 UTC
push:
paths:
- ".github/workflows/tidelift.yml"
pull_request:
paths:
- ".github/workflows/tidelift.yml"
workflow_dispatch:
jobs:
build:
if: github.repository_owner == 'python-pillow'
name: Run Tidelift to ensure approved open source packages are in use
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v2
- name: Scan
uses: tidelift/alignment-action@main
env:
TIDELIFT_API_KEY: ${{ secrets.TIDELIFT_API_KEY }}
TIDELIFT_ORGANIZATION: team/aclark4life
TIDELIFT_PROJECT: pillow

View File

@ -5,6 +5,12 @@ Changelog (Pillow)
9.0.0 (unreleased)
------------------
- Added ImageShow support for xdg-open #5897
[m-shinder, radarhere]
- Support 16-bit grayscale ImageQt conversion #5856
[cmbruns, radarhere]
- Convert subsequent GIF frames to RGB or RGBA #5857
[radarhere]

View File

@ -1,6 +1,7 @@
include *.c
include *.h
include *.in
include *.lock
include *.md
include *.py
include *.rst
@ -9,6 +10,7 @@ include *.txt
include *.yaml
include LICENSE
include Makefile
include Pipfile
include tox.ini
graft Tests
graft src

View File

@ -50,16 +50,16 @@ help:
.PHONY: inplace
inplace: clean
python3 setup.py develop build_ext --inplace
python3 -m pip install -e --global-option="build_ext" --global-option="--inplace" .
.PHONY: install
install:
python3 setup.py install
python3 -m pip install .
python3 selftest.py
.PHONY: install-coverage
install-coverage:
CFLAGS="-coverage -Werror=implicit-function-declaration" python3 setup.py build_ext install
CFLAGS="-coverage -Werror=implicit-function-declaration" python3 -m pip install --global-option="build_ext" .
python3 selftest.py
.PHONY: debug
@ -68,7 +68,7 @@ debug:
# for our stuff, kills optimization, and redirects to dev null so we
# see any build failures.
make clean > /dev/null
CFLAGS='-g -O0' python3 setup.py build_ext install > /dev/null
CFLAGS='-g -O0' python3 -m pip install --global-option="build_ext" . > /dev/null
.PHONY: install-req
install-req:
@ -83,10 +83,10 @@ install-venv:
.PHONY: release-test
release-test:
$(MAKE) install-req
python3 setup.py develop
python3 -m pip install -e .
python3 selftest.py
python3 -m pytest Tests
python3 setup.py install
python3 -m pip install .
-rm dist/*.egg
-rmdir dist
python3 -m pytest -qq

22
Pipfile Normal file
View File

@ -0,0 +1,22 @@
[[source]]
url = "https://pypi.org/simple"
verify_ssl = true
name = "pypi"
[packages]
black = "*"
check-manifest = "*"
coverage = "*"
defusedxml = "*"
packaging = "*"
markdown2 = "*"
olefile = "*"
pyroma = "*"
pytest = "*"
pytest-cov = "*"
pytest-timeout = "*"
[dev-packages]
[requires]
python_version = "3.9"

324
Pipfile.lock generated Normal file
View File

@ -0,0 +1,324 @@
{
"_meta": {
"hash": {
"sha256": "e5cad23bf4187647d53b613a64dc4792b7064bf86b08dfb5737580e32943f54d"
},
"pipfile-spec": 6,
"requires": {
"python_version": "3.9"
},
"sources": [
{
"name": "pypi",
"url": "https://pypi.org/simple",
"verify_ssl": true
}
]
},
"default": {
"attrs": {
"hashes": [
"sha256:149e90d6d8ac20db7a955ad60cf0e6881a3f20d37096140088356da6c716b0b1",
"sha256:ef6aaac3ca6cd92904cdd0d83f629a15f18053ec84e6432106f7a4d04ae4f5fb"
],
"markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4'",
"version": "==21.2.0"
},
"black": {
"hashes": [
"sha256:77b80f693a569e2e527958459634f18df9b0ba2625ba4e0c2d5da5be42e6f2b3",
"sha256:a615e69ae185e08fdd73e4715e260e2479c861b5740057fde6e8b4e3b7dd589f"
],
"index": "pypi",
"version": "==21.12b0"
},
"build": {
"hashes": [
"sha256:1aaadcd69338252ade4f7ec1265e1a19184bf916d84c9b7df095f423948cb89f",
"sha256:21b7ebbd1b22499c4dac536abc7606696ea4d909fd755e00f09f3c0f2c05e3c8"
],
"markers": "python_version >= '3.6'",
"version": "==0.7.0"
},
"certifi": {
"hashes": [
"sha256:78884e7c1d4b00ce3cea67b44566851c4343c120abd683433ce934a68ea58872",
"sha256:d62a0163eb4c2344ac042ab2bdf75399a71a2d8c7d47eac2e2ee91b9d6339569"
],
"version": "==2021.10.8"
},
"charset-normalizer": {
"hashes": [
"sha256:1eecaa09422db5be9e29d7fc65664e6c33bd06f9ced7838578ba40d58bdf3721",
"sha256:b0b883e8e874edfdece9c28f314e3dd5badf067342e42fb162203335ae61aa2c"
],
"markers": "python_version >= '3'",
"version": "==2.0.9"
},
"check-manifest": {
"hashes": [
"sha256:365c94d65de4c927d9d8b505371d08ee19f9f369c86b9ac3db97c2754c827c95",
"sha256:56dadd260a9c7d550b159796d2894b6d0bcc176a94cbc426d9bb93e5e48d12ce"
],
"index": "pypi",
"version": "==0.47"
},
"click": {
"hashes": [
"sha256:353f466495adaeb40b6b5f592f9f91cb22372351c84caeb068132442a4518ef3",
"sha256:410e932b050f5eed773c4cda94de75971c89cdb3155a72a0831139a79e5ecb5b"
],
"markers": "python_version >= '3.6'",
"version": "==8.0.3"
},
"coverage": {
"hashes": [
"sha256:01774a2c2c729619760320270e42cd9e797427ecfddd32c2a7b639cdc481f3c0",
"sha256:03b20e52b7d31be571c9c06b74746746d4eb82fc260e594dc662ed48145e9efd",
"sha256:0a7726f74ff63f41e95ed3a89fef002916c828bb5fcae83b505b49d81a066884",
"sha256:1219d760ccfafc03c0822ae2e06e3b1248a8e6d1a70928966bafc6838d3c9e48",
"sha256:13362889b2d46e8d9f97c421539c97c963e34031ab0cb89e8ca83a10cc71ac76",
"sha256:174cf9b4bef0db2e8244f82059a5a72bd47e1d40e71c68ab055425172b16b7d0",
"sha256:17e6c11038d4ed6e8af1407d9e89a2904d573be29d51515f14262d7f10ef0a64",
"sha256:215f8afcc02a24c2d9a10d3790b21054b58d71f4b3c6f055d4bb1b15cecce685",
"sha256:22e60a3ca5acba37d1d4a2ee66e051f5b0e1b9ac950b5b0cf4aa5366eda41d47",
"sha256:2641f803ee9f95b1f387f3e8f3bf28d83d9b69a39e9911e5bfee832bea75240d",
"sha256:276651978c94a8c5672ea60a2656e95a3cce2a3f31e9fb2d5ebd4c215d095840",
"sha256:3f7c17209eef285c86f819ff04a6d4cbee9b33ef05cbcaae4c0b4e8e06b3ec8f",
"sha256:3feac4084291642165c3a0d9eaebedf19ffa505016c4d3db15bfe235718d4971",
"sha256:49dbff64961bc9bdd2289a2bda6a3a5a331964ba5497f694e2cbd540d656dc1c",
"sha256:4e547122ca2d244f7c090fe3f4b5a5861255ff66b7ab6d98f44a0222aaf8671a",
"sha256:5829192582c0ec8ca4a2532407bc14c2f338d9878a10442f5d03804a95fac9de",
"sha256:5d6b09c972ce9200264c35a1d53d43ca55ef61836d9ec60f0d44273a31aa9f17",
"sha256:600617008aa82032ddeace2535626d1bc212dfff32b43989539deda63b3f36e4",
"sha256:619346d57c7126ae49ac95b11b0dc8e36c1dd49d148477461bb66c8cf13bb521",
"sha256:63c424e6f5b4ab1cf1e23a43b12f542b0ec2e54f99ec9f11b75382152981df57",
"sha256:6dbc1536e105adda7a6312c778f15aaabe583b0e9a0b0a324990334fd458c94b",
"sha256:6e1394d24d5938e561fbeaa0cd3d356207579c28bd1792f25a068743f2d5b282",
"sha256:86f2e78b1eff847609b1ca8050c9e1fa3bd44ce755b2ec30e70f2d3ba3844644",
"sha256:8bdfe9ff3a4ea37d17f172ac0dff1e1c383aec17a636b9b35906babc9f0f5475",
"sha256:8e2c35a4c1f269704e90888e56f794e2d9c0262fb0c1b1c8c4ee44d9b9e77b5d",
"sha256:92b8c845527eae547a2a6617d336adc56394050c3ed8a6918683646328fbb6da",
"sha256:9365ed5cce5d0cf2c10afc6add145c5037d3148585b8ae0e77cc1efdd6aa2953",
"sha256:9a29311bd6429be317c1f3fe4bc06c4c5ee45e2fa61b2a19d4d1d6111cb94af2",
"sha256:9a2b5b52be0a8626fcbffd7e689781bf8c2ac01613e77feda93d96184949a98e",
"sha256:a4bdeb0a52d1d04123b41d90a4390b096f3ef38eee35e11f0b22c2d031222c6c",
"sha256:a9c8c4283e17690ff1a7427123ffb428ad6a52ed720d550e299e8291e33184dc",
"sha256:b637c57fdb8be84e91fac60d9325a66a5981f8086c954ea2772efe28425eaf64",
"sha256:bf154ba7ee2fd613eb541c2bc03d3d9ac667080a737449d1a3fb342740eb1a74",
"sha256:c254b03032d5a06de049ce8bca8338a5185f07fb76600afff3c161e053d88617",
"sha256:c332d8f8d448ded473b97fefe4a0983265af21917d8b0cdcb8bb06b2afe632c3",
"sha256:c7912d1526299cb04c88288e148c6c87c0df600eca76efd99d84396cfe00ef1d",
"sha256:cfd9386c1d6f13b37e05a91a8583e802f8059bebfccde61a418c5808dea6bbfa",
"sha256:d5d2033d5db1d58ae2d62f095e1aefb6988af65b4b12cb8987af409587cc0739",
"sha256:dca38a21e4423f3edb821292e97cec7ad38086f84313462098568baedf4331f8",
"sha256:e2cad8093172b7d1595b4ad66f24270808658e11acf43a8f95b41276162eb5b8",
"sha256:e3db840a4dee542e37e09f30859f1612da90e1c5239a6a2498c473183a50e781",
"sha256:edcada2e24ed68f019175c2b2af2a8b481d3d084798b8c20d15d34f5c733fa58",
"sha256:f467bbb837691ab5a8ca359199d3429a11a01e6dfb3d9dcc676dc035ca93c0a9",
"sha256:f506af4f27def639ba45789fa6fde45f9a217da0be05f8910458e4557eed020c",
"sha256:f614fc9956d76d8a88a88bb41ddc12709caa755666f580af3a688899721efecd",
"sha256:f9afb5b746781fc2abce26193d1c817b7eb0e11459510fba65d2bd77fe161d9e",
"sha256:fb8b8ee99b3fffe4fd86f4c81b35a6bf7e4462cba019997af2fe679365db0c49"
],
"index": "pypi",
"version": "==6.2"
},
"defusedxml": {
"hashes": [
"sha256:1bb3032db185915b62d7c6209c5a8792be6a32ab2fedacc84e01b52c51aa3e69",
"sha256:a352e7e428770286cc899e2542b6cdaedb2b4953ff269a210103ec58f6198a61"
],
"index": "pypi",
"version": "==0.7.1"
},
"docutils": {
"hashes": [
"sha256:23010f129180089fbcd3bc08cfefccb3b890b0050e1ca00c867036e9d161b98c",
"sha256:679987caf361a7539d76e584cbeddc311e3aee937877c87346f31debc63e9d06"
],
"markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4'",
"version": "==0.18.1"
},
"idna": {
"hashes": [
"sha256:84d9dd047ffa80596e0f246e2eab0b391788b0503584e8945f2368256d2735ff",
"sha256:9d643ff0a55b762d5cdb124b8eaa99c66322e2157b69160bc32796e824360e6d"
],
"markers": "python_version >= '3'",
"version": "==3.3"
},
"iniconfig": {
"hashes": [
"sha256:011e24c64b7f47f6ebd835bb12a743f2fbe9a26d4cecaa7f53bc4f35ee9da8b3",
"sha256:bc3af051d7d14b2ee5ef9969666def0cd1a000e121eaea580d4a313df4b37f32"
],
"version": "==1.1.1"
},
"markdown2": {
"hashes": [
"sha256:8f4ac8d9a124ab408c67361090ed512deda746c04362c36c2ec16190c720c2b0",
"sha256:91113caf23aa662570fe21984f08fe74f814695c0a0ea8e863a8b4c4f63f9f6e"
],
"index": "pypi",
"version": "==2.4.2"
},
"mypy-extensions": {
"hashes": [
"sha256:090fedd75945a69ae91ce1303b5824f428daf5a028d2f6ab8a299250a846f15d",
"sha256:2d82818f5bb3e369420cb3c4060a7970edba416647068eb4c5343488a6c604a8"
],
"version": "==0.4.3"
},
"olefile": {
"hashes": [
"sha256:133b031eaf8fd2c9399b78b8bc5b8fcbe4c31e85295749bb17a87cba8f3c3964"
],
"index": "pypi",
"version": "==0.46"
},
"packaging": {
"hashes": [
"sha256:dd47c42927d89ab911e606518907cc2d3a1f38bbd026385970643f9c5b8ecfeb",
"sha256:ef103e05f519cdc783ae24ea4e2e0f508a9c99b2d4969652eed6a2e1ea5bd522"
],
"index": "pypi",
"version": "==21.3"
},
"pathspec": {
"hashes": [
"sha256:7d15c4ddb0b5c802d161efc417ec1a2558ea2653c2e8ad9c19098201dc1c993a",
"sha256:e564499435a2673d586f6b2130bb5b95f04a3ba06f81b8f895b651a3c76aabb1"
],
"version": "==0.9.0"
},
"pep517": {
"hashes": [
"sha256:931378d93d11b298cf511dd634cf5ea4cb249a28ef84160b3247ee9afb4e8ab0",
"sha256:dd884c326898e2c6e11f9e0b64940606a93eb10ea022a2e067959f3a110cf161"
],
"version": "==0.12.0"
},
"platformdirs": {
"hashes": [
"sha256:367a5e80b3d04d2428ffa76d33f124cf11e8fff2acdaa9b43d545f5c7d661ef2",
"sha256:8868bbe3c3c80d42f20156f22e7131d2fb321f5bc86a2a345375c6481a67021d"
],
"markers": "python_version >= '3.6'",
"version": "==2.4.0"
},
"pluggy": {
"hashes": [
"sha256:4224373bacce55f955a878bf9cfa763c1e360858e330072059e10bad68531159",
"sha256:74134bbf457f031a36d68416e1509f34bd5ccc019f0bcc952c7b909d06b37bd3"
],
"markers": "python_version >= '3.6'",
"version": "==1.0.0"
},
"py": {
"hashes": [
"sha256:51c75c4126074b472f746a24399ad32f6053d1b34b68d2fa41e558e6f4a98719",
"sha256:607c53218732647dff4acdfcd50cb62615cedf612e72d1724fb1a0cc6405b378"
],
"markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4'",
"version": "==1.11.0"
},
"pygments": {
"hashes": [
"sha256:b8e67fe6af78f492b3c4b3e2970c0624cbf08beb1e493b2c99b9fa1b67a20380",
"sha256:f398865f7eb6874156579fdf36bc840a03cab64d1cde9e93d68f46a425ec52c6"
],
"markers": "python_version >= '3.5'",
"version": "==2.10.0"
},
"pyparsing": {
"hashes": [
"sha256:04ff808a5b90911829c55c4e26f75fa5ca8a2f5f36aa3a51f68e27033341d3e4",
"sha256:d9bdec0013ef1eb5a84ab39a3b3868911598afa494f5faa038647101504e2b81"
],
"markers": "python_version >= '3.6'",
"version": "==3.0.6"
},
"pyroma": {
"hashes": [
"sha256:0fba67322913026091590e68e0d9e0d4fbd6420fcf34d315b2ad6985ab104d65",
"sha256:f8c181e0d5d292f11791afc18f7d0218a83c85cf64d6f8fb1571ce9d29a24e4a"
],
"index": "pypi",
"version": "==3.2"
},
"pytest": {
"hashes": [
"sha256:131b36680866a76e6781d13f101efb86cf674ebb9762eb70d3082b6f29889e89",
"sha256:7310f8d27bc79ced999e760ca304d69f6ba6c6649c0b60fb0e04a4a77cacc134"
],
"index": "pypi",
"version": "==6.2.5"
},
"pytest-cov": {
"hashes": [
"sha256:578d5d15ac4a25e5f961c938b85a05b09fdaae9deef3bb6de9a6e766622ca7a6",
"sha256:e7f0f5b1617d2210a2cabc266dfe2f4c75a8d32fb89eafb7ad9d06f6d076d470"
],
"index": "pypi",
"version": "==3.0.0"
},
"pytest-timeout": {
"hashes": [
"sha256:e6f98b54dafde8d70e4088467ff621260b641eb64895c4195b6e5c8f45638112",
"sha256:fe9c3d5006c053bb9e062d60f641e6a76d6707aedb645350af9593e376fcc717"
],
"index": "pypi",
"version": "==2.0.2"
},
"requests": {
"hashes": [
"sha256:6c1246513ecd5ecd4528a0906f910e8f0f9c6b8ec72030dc9fd154dc1a6efd24",
"sha256:b8aa58f8cf793ffd8782d3d8cb19e66ef36f7aba4353eec859e74678b01b07a7"
],
"markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4, 3.5'",
"version": "==2.26.0"
},
"setuptools": {
"hashes": [
"sha256:5ec2bbb534ed160b261acbbdd1b463eb3cf52a8d223d96a8ab9981f63798e85c",
"sha256:75fd345a47ce3d79595b27bf57e6f49c2ca7904f3c7ce75f8a87012046c86b0b"
],
"markers": "python_version >= '3.7'",
"version": "==60.0.0"
},
"toml": {
"hashes": [
"sha256:806143ae5bfb6a3c6e736a764057db0e6a0e05e338b5630894a5f779cabb4f9b",
"sha256:b3bda1d108d5dd99f4a20d24d9c348e91c4db7ab1b749200bded2f839ccbe68f"
],
"markers": "python_version >= '2.6' and python_version not in '3.0, 3.1, 3.2'",
"version": "==0.10.2"
},
"tomli": {
"hashes": [
"sha256:05b6166bff487dc068d322585c7ea4ef78deed501cc124060e0f238e89a9231f",
"sha256:e3069e4be3ead9668e21cb9b074cd948f7b3113fd9c8bba083f48247aab8b11c"
],
"markers": "python_version >= '3.6'",
"version": "==1.2.3"
},
"typing-extensions": {
"hashes": [
"sha256:4ca091dea149f945ec56afb48dae714f21e8692ef22a395223bcd328961b6a0e",
"sha256:7f001e5ac290a0c0401508864c7ec868be4e701886d5b573a9528ed3973d9d3b"
],
"markers": "python_version >= '3.6'",
"version": "==4.0.1"
},
"urllib3": {
"hashes": [
"sha256:4987c65554f7a2dbf30c18fd48778ef124af6fab771a377103da0585e2336ece",
"sha256:c4fdf4019605b6e5423637e01bc9fe4daef873709a7973e195ceba0a62bbc844"
],
"markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4' and python_version < '4'",
"version": "==1.26.7"
}
},
"develop": {}
}

View File

@ -48,6 +48,9 @@ As of 2019, Pillow development is
<a href="https://codecov.io/gh/python-pillow/Pillow"><img
alt="Code coverage"
src="https://codecov.io/gh/python-pillow/Pillow/branch/main/graph/badge.svg"></a>
<a href="https://github.com/python-pillow/Pillow/actions/workflows/tidelift.yml"><img
alt="Tidelift Align"
src="https://github.com/python-pillow/Pillow/actions/workflows/tidelift.yml/badge.svg"></a>
</td>
</tr>
<tr>

View File

@ -41,7 +41,7 @@ def test_sanity():
def test_default():
im = hopper("P")
assert_image(im, "P", im.size)
assert im.mode == "P"
converted_im = im.convert()
assert_image(converted_im, "RGB", im.size)
converted_im = im.convert()

View File

@ -2,18 +2,18 @@ import pytest
from PIL import Image
from .helper import assert_image, assert_image_similar, hopper, is_ppc64le
from .helper import assert_image_similar, hopper, is_ppc64le
def test_sanity():
image = hopper()
converted = image.quantize()
assert_image(converted, "P", converted.size)
assert converted.mode == "P"
assert_image_similar(converted.convert("RGB"), image, 10)
image = hopper()
converted = image.quantize(palette=hopper("P"))
assert_image(converted, "P", converted.size)
assert converted.mode == "P"
assert_image_similar(converted.convert("RGB"), image, 60)
@ -27,7 +27,7 @@ def test_libimagequant_quantize():
pytest.skip("libimagequant support not available")
else:
raise
assert_image(converted, "P", converted.size)
assert converted.mode == "P"
assert_image_similar(converted.convert("RGB"), image, 15)
assert len(converted.getcolors()) == 100
@ -35,7 +35,7 @@ def test_libimagequant_quantize():
def test_octree_quantize():
image = hopper()
converted = image.quantize(100, Image.FASTOCTREE)
assert_image(converted, "P", converted.size)
assert converted.mode == "P"
assert_image_similar(converted.convert("RGB"), image, 20)
assert len(converted.getcolors()) == 100
@ -52,7 +52,7 @@ def test_quantize():
with Image.open("Tests/images/caption_6_33_22.png") as image:
image = image.convert("RGB")
converted = image.quantize()
assert_image(converted, "P", converted.size)
assert converted.mode == "P"
assert_image_similar(converted.convert("RGB"), image, 1)
@ -62,7 +62,7 @@ def test_quantize_no_dither():
palette = palette.convert("P")
converted = image.quantize(dither=0, palette=palette)
assert_image(converted, "P", converted.size)
assert converted.mode == "P"
assert converted.palette.palette == palette.palette.palette

View File

@ -6,7 +6,7 @@ import pytest
from PIL import Image, ImageGrab
from .helper import assert_image, assert_image_equal_tofile, skip_unless_feature
from .helper import assert_image_equal_tofile, skip_unless_feature
class TestImageGrab:
@ -14,25 +14,20 @@ class TestImageGrab:
sys.platform not in ("win32", "darwin"), reason="requires Windows or macOS"
)
def test_grab(self):
for im in [
ImageGrab.grab(),
ImageGrab.grab(include_layered_windows=True),
ImageGrab.grab(all_screens=True),
]:
assert_image(im, im.mode, im.size)
ImageGrab.grab()
ImageGrab.grab(include_layered_windows=True)
ImageGrab.grab(all_screens=True)
im = ImageGrab.grab(bbox=(10, 20, 50, 80))
assert_image(im, im.mode, (40, 60))
assert im.size == (40, 60)
@skip_unless_feature("xcb")
def test_grab_x11(self):
try:
if sys.platform not in ("win32", "darwin"):
im = ImageGrab.grab()
assert_image(im, im.mode, im.size)
ImageGrab.grab()
im2 = ImageGrab.grab(xdisplay="")
assert_image(im2, im2.mode, im2.size)
ImageGrab.grab(xdisplay="")
except OSError as e:
pytest.skip(str(e))
@ -71,8 +66,7 @@ $bmp = New-Object Drawing.Bitmap 200, 200
assert str(e.value) == "ImageGrab.grabclipboard() is macOS and Windows only"
return
im = ImageGrab.grabclipboard()
assert_image(im, im.mode, im.size)
ImageGrab.grabclipboard()
@pytest.mark.skipif(sys.platform != "win32", reason="Windows only")
def test_grabclipboard_file(self):

View File

@ -2,7 +2,7 @@ import pytest
from PIL import ImageQt
from .helper import hopper
from .helper import assert_image_similar, hopper
pytestmark = pytest.mark.skipif(
not ImageQt.qt_is_installed, reason="Qt bindings are not installed"
@ -42,8 +42,17 @@ def test_rgb():
def test_image():
for mode in ("1", "RGB", "RGBA", "L", "P"):
ImageQt.ImageQt(hopper(mode))
modes = ["1", "RGB", "RGBA", "L", "P"]
qt_format = ImageQt.QImage.Format if ImageQt.qt_version == "6" else ImageQt.QImage
if hasattr(qt_format, "Format_Grayscale16"): # Qt 5.13+
modes.append("I;16")
for mode in modes:
im = hopper(mode)
roundtripped_im = ImageQt.fromqimage(ImageQt.ImageQt(im))
if mode not in ("RGB", "RGBA"):
im = im.convert("RGB")
assert_image_similar(roundtripped_im, im, 1)
def test_closed_file():

View File

@ -497,6 +497,43 @@ Reading from a tar archive
fp = TarIO.TarIO("Tests/images/hopper.tar", "hopper.jpg")
im = Image.open(fp)
Batch processing
^^^^^^^^^^^^^^^^
Operations can be applied to multiple image files. For example, all PNG images
in the current directory can be saved as JPEGs at reduced quality.
::
import glob
from PIL import Image
def compress_image(source_path, dest_path):
with Image.open(source_path) as img:
if img.mode != "RGB":
img = img.convert("RGB")
img.save(dest_path, "JPEG", optimize=True, quality=80)
paths = glob.glob("*.png")
for path in paths:
compress_image(path, path[:-4] + ".jpg")
Since images can also be opened from a ``Path`` from the ``pathlib`` module,
the example could be modified to use ``pathlib`` instead of the ``glob``
module.
::
from pathlib import Path
paths = Path(".").glob("*.png")
for path in paths:
compress_image(path, path.stem + ".jpg")
Controlling the decoder
-----------------------

View File

@ -49,6 +49,10 @@ Pillow for enterprise is available via the Tidelift Subscription. `Learn more <h
:target: https://tidelift.com/subscription/pkg/pypi-pillow?utm_source=pypi-pillow&utm_medium=badge
:alt: Tidelift
.. image:: https://github.com/python-pillow/Pillow/actions/workflows/tidelift.yml/badge.svg
:target: https://github.com/python-pillow/Pillow/actions/workflows/tidelift.yml
:alt: Tidelift Align
.. image:: https://img.shields.io/pypi/v/pillow.svg
:target: https://pypi.org/project/Pillow/
:alt: Latest PyPI version

View File

@ -275,10 +275,6 @@ Build Options
Sample usage::
MAX_CONCURRENCY=1 python3 setup.py build_ext --enable-[feature] install
or using pip::
python3 -m pip install --upgrade Pillow --global-option="build_ext" --global-option="--enable-[feature]"
@ -310,7 +306,7 @@ Now install Pillow with::
or from within the uncompressed source directory::
python3 setup.py install
python3 -m pip install .
Building on Windows
^^^^^^^^^^^^^^^^^^^

View File

@ -17,6 +17,7 @@ All default viewers convert the image to be shown to PNG format.
The following viewers may be registered on Unix-based systems, if the given command is found:
.. autoclass:: PIL.ImageShow.XDGViewer
.. autoclass:: PIL.ImageShow.DisplayViewer
.. autoclass:: PIL.ImageShow.GmDisplayViewer
.. autoclass:: PIL.ImageShow.EogViewer

View File

@ -1,6 +1,29 @@
9.0.0
-----
Fredrik Lundh
=============
This release is dedicated to the memory of Fredrik Lundh, aka Effbot, who died in
November 2021. Fredrik created PIL in 1995 and he was instrumental in the early
success of Python.
`Guido wrote <https://mail.python.org/archives/list/python-dev@python.org/thread/36Q5QBILL3QIFIA3KHNGFBNJQKXKN7SD/>`_:
Fredrik was an early Python contributor (e.g. Elementtree and the 're'
module) and his enthusiasm for the language and community were inspiring
for all who encountered him or his work. He spent countless hours on
comp.lang.python answering questions from newbies and advanced users alike.
He also co-founded an early Python startup, Secret Labs AB, which among
other software released an IDE named PythonWorks. Fredrik also created the
Python Imaging Library (PIL) which is still THE way to interact with images
in Python, now most often through its Pillow fork. His effbot.org site was
a valuable resource for generations of Python users, especially its Tkinter
documentation.
Thank you, Fredrik.
Backwards Incompatible Changes
==============================
@ -44,14 +67,6 @@ ImageFile.raise_ioerror
has been removed. Use ``ImageFile.raise_oserror`` instead.
Deprecations
============
TODO
^^^^
TODO
API Changes
===========
@ -60,11 +75,19 @@ Added line width parameter to ImageDraw polygon
An optional line ``width`` parameter has been added to ``ImageDraw.Draw.polygon``.
TODO
API Additions
=============
ImageShow.XDGViewer
^^^^^^^^^^^^^^^^^^^
If ``xdg-open`` is present on Linux, this new :py:class:`PIL.ImageShow.Viewer` subclass
will be registered. It displays images using the application selected by the system.
It is higher in priority than the other default :py:class:`PIL.ImageShow.Viewer`
instances, so it will be preferred by ``im.show()`` or :py:func:`.ImageShow.show()`.
Added support for "title" argument to DisplayViewer
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

View File

@ -108,7 +108,7 @@ def align8to32(bytes, width, mode):
converts each scanline of data from 8 bit to 32 bit aligned
"""
bits_per_pixel = {"1": 1, "L": 8, "P": 8}[mode]
bits_per_pixel = {"1": 1, "L": 8, "P": 8, "I;16": 16}[mode]
# calculate bytes per line and the extra padding if needed
bits_per_line = bits_per_pixel * width
@ -167,6 +167,10 @@ def _toqclass_helper(im):
elif im.mode == "RGBA":
data = im.tobytes("raw", "BGRA")
format = qt_format.Format_ARGB32
elif im.mode == "I;16" and hasattr(qt_format, "Format_Grayscale16"): # Qt 5.13+
im = im.point(lambda i: i * 256)
format = qt_format.Format_Grayscale16
else:
if exclusive_fp:
im.close()

View File

@ -186,6 +186,16 @@ class UnixViewer(Viewer):
return 1
class XDGViewer(UnixViewer):
"""
The freedesktop.org ``xdg-open`` command.
"""
def get_command_ex(self, file, **options):
command = executable = "xdg-open"
return command, executable
class DisplayViewer(UnixViewer):
"""
The ImageMagick ``display`` command.
@ -233,6 +243,8 @@ class XVViewer(UnixViewer):
if sys.platform not in ("win32", "darwin"): # unixoids
if shutil.which("xdg-open"):
register(XDGViewer)
if shutil.which("display"):
register(DisplayViewer)
if shutil.which("gm"):

View File

@ -486,7 +486,7 @@ static struct PyMethodDef _anim_encoder_methods[] = {
{NULL, NULL} /* sentinel */
};
// WebPAnimDecoder type definition
// WebPAnimEncoder type definition
static PyTypeObject WebPAnimEncoder_Type = {
PyVarObject_HEAD_INIT(NULL, 0) "WebPAnimEncoder", /*tp_name */
sizeof(WebPAnimEncoderObject), /*tp_size */

View File

@ -11,8 +11,8 @@ minversion = 1.9
[testenv]
commands =
{envpython} setup.py clean
{envpython} setup.py build_ext --inplace
make clean
{envpython} -m pip install --global-option="build_ext" --global-option="--inplace" .
{envpython} selftest.py
{envpython} -m pytest -W always {posargs}
deps =

View File

@ -278,9 +278,9 @@ deps = {
"libs": [r"imagequant.lib"],
},
"harfbuzz": {
"url": "https://github.com/harfbuzz/harfbuzz/archive/3.1.2.zip",
"filename": "harfbuzz-3.1.2.zip",
"dir": "harfbuzz-3.1.2",
"url": "https://github.com/harfbuzz/harfbuzz/archive/3.2.0.zip",
"filename": "harfbuzz-3.2.0.zip",
"dir": "harfbuzz-3.2.0",
"build": [
cmd_cmake("-DHB_HAVE_FREETYPE:BOOL=TRUE"),
cmd_nmake(target="clean"),