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 pushd depends && ./install_extra_test_images.sh && popd
- name: Build Pillow - 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 - name: Test Pillow
run: | run: |

View File

@ -9,7 +9,7 @@ jobs:
fail-fast: false fail-fast: false
matrix: matrix:
os: [ os: [
"macOS-latest", "macos-latest",
"ubuntu-latest", "ubuntu-latest",
] ]
python-version: [ python-version: [
@ -29,7 +29,7 @@ jobs:
# Include new variables for Codecov # Include new variables for Codecov
- os: ubuntu-latest - os: ubuntu-latest
codecov-flag: GHA_Ubuntu codecov-flag: GHA_Ubuntu
- os: macOS-latest - os: macos-latest
codecov-flag: GHA_macOS codecov-flag: GHA_macOS
runs-on: ${{ matrix.os }} 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) 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 - Convert subsequent GIF frames to RGB or RGBA #5857
[radarhere] [radarhere]

View File

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

View File

@ -50,16 +50,16 @@ help:
.PHONY: inplace .PHONY: inplace
inplace: clean inplace: clean
python3 setup.py develop build_ext --inplace python3 -m pip install -e --global-option="build_ext" --global-option="--inplace" .
.PHONY: install .PHONY: install
install: install:
python3 setup.py install python3 -m pip install .
python3 selftest.py python3 selftest.py
.PHONY: install-coverage .PHONY: install-coverage
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 python3 selftest.py
.PHONY: debug .PHONY: debug
@ -68,7 +68,7 @@ debug:
# for our stuff, kills optimization, and redirects to dev null so we # for our stuff, kills optimization, and redirects to dev null so we
# see any build failures. # see any build failures.
make clean > /dev/null 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 .PHONY: install-req
install-req: install-req:
@ -83,10 +83,10 @@ install-venv:
.PHONY: release-test .PHONY: release-test
release-test: release-test:
$(MAKE) install-req $(MAKE) install-req
python3 setup.py develop python3 -m pip install -e .
python3 selftest.py python3 selftest.py
python3 -m pytest Tests python3 -m pytest Tests
python3 setup.py install python3 -m pip install .
-rm dist/*.egg -rm dist/*.egg
-rmdir dist -rmdir dist
python3 -m pytest -qq 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 <a href="https://codecov.io/gh/python-pillow/Pillow"><img
alt="Code coverage" alt="Code coverage"
src="https://codecov.io/gh/python-pillow/Pillow/branch/main/graph/badge.svg"></a> 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> </td>
</tr> </tr>
<tr> <tr>

View File

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

View File

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

View File

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

View File

@ -2,7 +2,7 @@ import pytest
from PIL import ImageQt from PIL import ImageQt
from .helper import hopper from .helper import assert_image_similar, hopper
pytestmark = pytest.mark.skipif( pytestmark = pytest.mark.skipif(
not ImageQt.qt_is_installed, reason="Qt bindings are not installed" not ImageQt.qt_is_installed, reason="Qt bindings are not installed"
@ -42,8 +42,17 @@ def test_rgb():
def test_image(): def test_image():
for mode in ("1", "RGB", "RGBA", "L", "P"): modes = ["1", "RGB", "RGBA", "L", "P"]
ImageQt.ImageQt(hopper(mode)) 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(): def test_closed_file():

View File

@ -497,6 +497,43 @@ Reading from a tar archive
fp = TarIO.TarIO("Tests/images/hopper.tar", "hopper.jpg") fp = TarIO.TarIO("Tests/images/hopper.tar", "hopper.jpg")
im = Image.open(fp) 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 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 :target: https://tidelift.com/subscription/pkg/pypi-pillow?utm_source=pypi-pillow&utm_medium=badge
:alt: Tidelift :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 .. image:: https://img.shields.io/pypi/v/pillow.svg
:target: https://pypi.org/project/Pillow/ :target: https://pypi.org/project/Pillow/
:alt: Latest PyPI version :alt: Latest PyPI version

View File

@ -275,10 +275,6 @@ Build Options
Sample usage:: 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]" 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:: or from within the uncompressed source directory::
python3 setup.py install python3 -m pip install .
Building on Windows 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: 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.DisplayViewer
.. autoclass:: PIL.ImageShow.GmDisplayViewer .. autoclass:: PIL.ImageShow.GmDisplayViewer
.. autoclass:: PIL.ImageShow.EogViewer .. autoclass:: PIL.ImageShow.EogViewer

View File

@ -1,6 +1,29 @@
9.0.0 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 Backwards Incompatible Changes
============================== ==============================
@ -44,14 +67,6 @@ ImageFile.raise_ioerror
has been removed. Use ``ImageFile.raise_oserror`` instead. has been removed. Use ``ImageFile.raise_oserror`` instead.
Deprecations
============
TODO
^^^^
TODO
API Changes 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``. An optional line ``width`` parameter has been added to ``ImageDraw.Draw.polygon``.
TODO
API Additions 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 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 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 # calculate bytes per line and the extra padding if needed
bits_per_line = bits_per_pixel * width bits_per_line = bits_per_pixel * width
@ -167,6 +167,10 @@ def _toqclass_helper(im):
elif im.mode == "RGBA": elif im.mode == "RGBA":
data = im.tobytes("raw", "BGRA") data = im.tobytes("raw", "BGRA")
format = qt_format.Format_ARGB32 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: else:
if exclusive_fp: if exclusive_fp:
im.close() im.close()

View File

@ -186,6 +186,16 @@ class UnixViewer(Viewer):
return 1 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): class DisplayViewer(UnixViewer):
""" """
The ImageMagick ``display`` command. The ImageMagick ``display`` command.
@ -233,6 +243,8 @@ class XVViewer(UnixViewer):
if sys.platform not in ("win32", "darwin"): # unixoids if sys.platform not in ("win32", "darwin"): # unixoids
if shutil.which("xdg-open"):
register(XDGViewer)
if shutil.which("display"): if shutil.which("display"):
register(DisplayViewer) register(DisplayViewer)
if shutil.which("gm"): if shutil.which("gm"):

View File

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

View File

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

View File

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