mirror of
https://github.com/python-pillow/Pillow.git
synced 2025-07-10 16:22:22 +03:00
Merge branch 'main' into gif_disposal
This commit is contained in:
commit
921c46679d
2
.github/workflows/stale.yml
vendored
2
.github/workflows/stale.yml
vendored
|
@ -20,7 +20,7 @@ jobs:
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: "Check issues"
|
- name: "Check issues"
|
||||||
uses: actions/stale@v6
|
uses: actions/stale@v7
|
||||||
with:
|
with:
|
||||||
repo-token: ${{ secrets.GITHUB_TOKEN }}
|
repo-token: ${{ secrets.GITHUB_TOKEN }}
|
||||||
only-labels: "Awaiting OP Action"
|
only-labels: "Awaiting OP Action"
|
||||||
|
|
4
.github/workflows/test-cygwin.yml
vendored
4
.github/workflows/test-cygwin.yml
vendored
|
@ -15,7 +15,7 @@ jobs:
|
||||||
strategy:
|
strategy:
|
||||||
fail-fast: false
|
fail-fast: false
|
||||||
matrix:
|
matrix:
|
||||||
python-minor-version: [7, 8, 9]
|
python-minor-version: [8, 9]
|
||||||
|
|
||||||
timeout-minutes: 40
|
timeout-minutes: 40
|
||||||
|
|
||||||
|
@ -48,7 +48,7 @@ jobs:
|
||||||
qt5-devel-tools subversion xorg-server-extra zlib-devel
|
qt5-devel-tools subversion xorg-server-extra zlib-devel
|
||||||
|
|
||||||
- name: Add Lapack to PATH
|
- name: Add Lapack to PATH
|
||||||
uses: egor-tensin/cleanup-path@v2
|
uses: egor-tensin/cleanup-path@v3
|
||||||
with:
|
with:
|
||||||
dirs: 'C:\cygwin\bin;C:\cygwin\lib\lapack'
|
dirs: 'C:\cygwin\bin;C:\cygwin\lib\lapack'
|
||||||
|
|
||||||
|
|
|
@ -1,18 +1,25 @@
|
||||||
repos:
|
repos:
|
||||||
- repo: https://github.com/psf/black
|
- repo: https://github.com/psf/black
|
||||||
rev: 22.10.0
|
rev: 22.12.0
|
||||||
hooks:
|
hooks:
|
||||||
- id: black
|
- id: black
|
||||||
args: ["--target-version", "py37"]
|
args: [--target-version=py37]
|
||||||
# Only .py files, until https://github.com/psf/black/issues/402 resolved
|
# Only .py files, until https://github.com/psf/black/issues/402 resolved
|
||||||
files: \.py$
|
files: \.py$
|
||||||
types: []
|
types: []
|
||||||
|
|
||||||
- repo: https://github.com/PyCQA/isort
|
- repo: https://github.com/PyCQA/isort
|
||||||
rev: 5.10.1
|
rev: 5.11.1
|
||||||
hooks:
|
hooks:
|
||||||
- id: isort
|
- id: isort
|
||||||
|
|
||||||
|
- repo: https://github.com/PyCQA/bandit
|
||||||
|
rev: 1.7.4
|
||||||
|
hooks:
|
||||||
|
- id: bandit
|
||||||
|
args: [--severity-level=high]
|
||||||
|
files: ^src/
|
||||||
|
|
||||||
- repo: https://github.com/asottile/yesqa
|
- repo: https://github.com/asottile/yesqa
|
||||||
rev: v1.4.0
|
rev: v1.4.0
|
||||||
hooks:
|
hooks:
|
||||||
|
@ -48,5 +55,10 @@ repos:
|
||||||
hooks:
|
hooks:
|
||||||
- id: sphinx-lint
|
- id: sphinx-lint
|
||||||
|
|
||||||
|
- repo: https://github.com/tox-dev/tox-ini-fmt
|
||||||
|
rev: 0.5.2
|
||||||
|
hooks:
|
||||||
|
- id: tox-ini-fmt
|
||||||
|
|
||||||
ci:
|
ci:
|
||||||
autoupdate_schedule: monthly
|
autoupdate_schedule: monthly
|
||||||
|
|
15
CHANGES.rst
15
CHANGES.rst
|
@ -5,6 +5,21 @@ Changelog (Pillow)
|
||||||
9.4.0 (unreleased)
|
9.4.0 (unreleased)
|
||||||
------------------
|
------------------
|
||||||
|
|
||||||
|
- Ignore non-opaque WebP background when saving as GIF #6792
|
||||||
|
[radarhere]
|
||||||
|
|
||||||
|
- Only set tile in ImageFile __setstate__ #6793
|
||||||
|
[radarhere]
|
||||||
|
|
||||||
|
- When reading BLP, do not trust JPEG decoder to determine image is CMYK #6767
|
||||||
|
[radarhere]
|
||||||
|
|
||||||
|
- Added IFD enum to ExifTags #6748
|
||||||
|
[radarhere]
|
||||||
|
|
||||||
|
- Fixed bug combining GIF frame durations #6779
|
||||||
|
[radarhere]
|
||||||
|
|
||||||
- Support saving JPEG comments #6774
|
- Support saving JPEG comments #6774
|
||||||
[smason, radarhere]
|
[smason, radarhere]
|
||||||
|
|
||||||
|
|
|
@ -1,7 +1,6 @@
|
||||||
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
|
||||||
|
@ -10,7 +9,6 @@ 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
|
||||||
|
|
22
Pipfile
22
Pipfile
|
@ -1,22 +0,0 @@
|
||||||
[[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
324
Pipfile.lock
generated
|
@ -1,324 +0,0 @@
|
||||||
{
|
|
||||||
"_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": {}
|
|
||||||
}
|
|
BIN
Tests/images/duplicate_frame.gif
Normal file
BIN
Tests/images/duplicate_frame.gif
Normal file
Binary file not shown.
After Width: | Height: | Size: 138 B |
BIN
Tests/images/flower_thumbnail.png
Normal file
BIN
Tests/images/flower_thumbnail.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 35 KiB |
|
@ -809,6 +809,22 @@ def test_roundtrip_info_duration(tmp_path):
|
||||||
] == duration_list
|
] == duration_list
|
||||||
|
|
||||||
|
|
||||||
|
def test_roundtrip_info_duration_combined(tmp_path):
|
||||||
|
out = str(tmp_path / "temp.gif")
|
||||||
|
with Image.open("Tests/images/duplicate_frame.gif") as im:
|
||||||
|
assert [frame.info["duration"] for frame in ImageSequence.Iterator(im)] == [
|
||||||
|
1000,
|
||||||
|
1000,
|
||||||
|
1000,
|
||||||
|
]
|
||||||
|
im.save(out, save_all=True)
|
||||||
|
|
||||||
|
with Image.open(out) as reloaded:
|
||||||
|
assert [
|
||||||
|
frame.info["duration"] for frame in ImageSequence.Iterator(reloaded)
|
||||||
|
] == [1000, 2000]
|
||||||
|
|
||||||
|
|
||||||
def test_identical_frames(tmp_path):
|
def test_identical_frames(tmp_path):
|
||||||
duration_list = [1000, 1500, 2000, 4000]
|
duration_list = [1000, 1500, 2000, 4000]
|
||||||
|
|
||||||
|
@ -877,12 +893,21 @@ def test_background(tmp_path):
|
||||||
im.info["background"] = 1
|
im.info["background"] = 1
|
||||||
im.save(out)
|
im.save(out)
|
||||||
with Image.open(out) as reread:
|
with Image.open(out) as reread:
|
||||||
|
|
||||||
assert reread.info["background"] == im.info["background"]
|
assert reread.info["background"] == im.info["background"]
|
||||||
|
|
||||||
|
|
||||||
|
def test_webp_background(tmp_path):
|
||||||
|
out = str(tmp_path / "temp.gif")
|
||||||
|
|
||||||
|
# Test opaque WebP background
|
||||||
if features.check("webp") and features.check("webp_anim"):
|
if features.check("webp") and features.check("webp_anim"):
|
||||||
with Image.open("Tests/images/hopper.webp") as im:
|
with Image.open("Tests/images/hopper.webp") as im:
|
||||||
assert isinstance(im.info["background"], tuple)
|
assert im.info["background"] == (255, 255, 255, 255)
|
||||||
|
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)
|
im.save(out)
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -442,6 +442,13 @@ class TestFileJpeg:
|
||||||
info = im._getexif()
|
info = im._getexif()
|
||||||
assert info[305] == "Adobe Photoshop CS Macintosh"
|
assert info[305] == "Adobe Photoshop CS Macintosh"
|
||||||
|
|
||||||
|
def test_get_child_images(self):
|
||||||
|
with Image.open("Tests/images/flower.jpg") as im:
|
||||||
|
ims = im.get_child_images()
|
||||||
|
|
||||||
|
assert len(ims) == 1
|
||||||
|
assert_image_equal_tofile(ims[0], "Tests/images/flower_thumbnail.png")
|
||||||
|
|
||||||
def test_mp(self):
|
def test_mp(self):
|
||||||
with Image.open("Tests/images/pil_sample_rgb.jpg") as im:
|
with Image.open("Tests/images/pil_sample_rgb.jpg") as im:
|
||||||
assert im._getmp() is None
|
assert im._getmp() is None
|
||||||
|
|
|
@ -7,7 +7,14 @@ import warnings
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
from PIL import Image, ImageDraw, ImagePalette, UnidentifiedImageError, features
|
from PIL import (
|
||||||
|
ExifTags,
|
||||||
|
Image,
|
||||||
|
ImageDraw,
|
||||||
|
ImagePalette,
|
||||||
|
UnidentifiedImageError,
|
||||||
|
features,
|
||||||
|
)
|
||||||
|
|
||||||
from .helper import (
|
from .helper import (
|
||||||
assert_image_equal,
|
assert_image_equal,
|
||||||
|
@ -808,6 +815,18 @@ class TestImage:
|
||||||
reloaded_exif.load(exif.tobytes())
|
reloaded_exif.load(exif.tobytes())
|
||||||
assert reloaded_exif.get_ifd(0xA005) == exif.get_ifd(0xA005)
|
assert reloaded_exif.get_ifd(0xA005) == exif.get_ifd(0xA005)
|
||||||
|
|
||||||
|
def test_exif_ifd1(self):
|
||||||
|
with Image.open("Tests/images/flower.jpg") as im:
|
||||||
|
exif = im.getexif()
|
||||||
|
assert exif.get_ifd(ExifTags.IFD.IFD1) == {
|
||||||
|
513: 2036,
|
||||||
|
514: 5448,
|
||||||
|
259: 6,
|
||||||
|
296: 2,
|
||||||
|
282: 180.0,
|
||||||
|
283: 180.0,
|
||||||
|
}
|
||||||
|
|
||||||
def test_exif_ifd(self):
|
def test_exif_ifd(self):
|
||||||
with Image.open("Tests/images/flower.jpg") as im:
|
with Image.open("Tests/images/flower.jpg") as im:
|
||||||
exif = im.getexif()
|
exif = im.getexif()
|
||||||
|
|
|
@ -34,7 +34,7 @@ def test_numpy_to_image():
|
||||||
|
|
||||||
# Check supported 1-bit integer formats
|
# Check supported 1-bit integer formats
|
||||||
assert_image(to_image(bool, 1, 1), "1", TEST_IMAGE_SIZE)
|
assert_image(to_image(bool, 1, 1), "1", TEST_IMAGE_SIZE)
|
||||||
assert_image(to_image(numpy.bool8, 1, 1), "1", TEST_IMAGE_SIZE)
|
assert_image(to_image(numpy.bool_, 1, 1), "1", TEST_IMAGE_SIZE)
|
||||||
|
|
||||||
# Check supported 8-bit integer formats
|
# Check supported 8-bit integer formats
|
||||||
assert_image(to_image(numpy.uint8), "L", TEST_IMAGE_SIZE)
|
assert_image(to_image(numpy.uint8), "L", TEST_IMAGE_SIZE)
|
||||||
|
@ -193,7 +193,7 @@ def test_putdata():
|
||||||
"dtype",
|
"dtype",
|
||||||
(
|
(
|
||||||
bool,
|
bool,
|
||||||
numpy.bool8,
|
numpy.bool_,
|
||||||
numpy.int8,
|
numpy.int8,
|
||||||
numpy.int16,
|
numpy.int16,
|
||||||
numpy.int32,
|
numpy.int32,
|
||||||
|
|
|
@ -460,7 +460,7 @@ These platforms are built and tested for every change.
|
||||||
| +----------------------------+---------------------+
|
| +----------------------------+---------------------+
|
||||||
| | 3.9 (MinGW) | x86, x86-64 |
|
| | 3.9 (MinGW) | x86, x86-64 |
|
||||||
| +----------------------------+---------------------+
|
| +----------------------------+---------------------+
|
||||||
| | 3.7, 3.8, 3.9 (Cygwin) | x86-64 |
|
| | 3.8, 3.9 (Cygwin) | x86-64 |
|
||||||
+----------------------------------+----------------------------+---------------------+
|
+----------------------------------+----------------------------+---------------------+
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -31,6 +31,13 @@ which provide constants and clear-text names for various well-known EXIF tags.
|
||||||
>>> Interop(4096).name
|
>>> Interop(4096).name
|
||||||
'RelatedImageFileFormat'
|
'RelatedImageFileFormat'
|
||||||
|
|
||||||
|
.. py:data:: IFD
|
||||||
|
|
||||||
|
>>> from PIL.ExifTags import IFD
|
||||||
|
>>> IFD.Exif.value
|
||||||
|
34665
|
||||||
|
>>> IFD(34665).name
|
||||||
|
'Exif'
|
||||||
|
|
||||||
Two of these values are also exposed as dictionaries.
|
Two of these values are also exposed as dictionaries.
|
||||||
|
|
||||||
|
|
|
@ -373,6 +373,9 @@ class BLP1Decoder(_BLPBaseDecoder):
|
||||||
data = BytesIO(data)
|
data = BytesIO(data)
|
||||||
image = JpegImageFile(data)
|
image = JpegImageFile(data)
|
||||||
Image._decompression_bomb_check(image.size)
|
Image._decompression_bomb_check(image.size)
|
||||||
|
if image.mode == "CMYK":
|
||||||
|
decoder_name, extents, offset, args = image.tile[0]
|
||||||
|
image.tile = [(decoder_name, extents, offset, (args[0], "CMYK"))]
|
||||||
r, g, b = image.convert("RGB").split()
|
r, g, b = image.convert("RGB").split()
|
||||||
image = Image.merge("RGB", (b, g, r))
|
image = Image.merge("RGB", (b, g, r))
|
||||||
self.set_as_raw(image.tobytes())
|
self.set_as_raw(image.tobytes())
|
||||||
|
|
|
@ -346,3 +346,11 @@ class Interop(IntEnum):
|
||||||
RelatedImageFileFormat = 4096
|
RelatedImageFileFormat = 4096
|
||||||
RelatedImageWidth = 4097
|
RelatedImageWidth = 4097
|
||||||
RleatedImageHeight = 4098
|
RleatedImageHeight = 4098
|
||||||
|
|
||||||
|
|
||||||
|
class IFD(IntEnum):
|
||||||
|
Exif = 34665
|
||||||
|
GPSInfo = 34853
|
||||||
|
Makernote = 37500
|
||||||
|
Interop = 40965
|
||||||
|
IFD1 = -1
|
||||||
|
|
|
@ -611,7 +611,7 @@ def _write_multiple_frames(im, fp, palette):
|
||||||
bbox = _getbbox(previous["im"], im_frame)
|
bbox = _getbbox(previous["im"], im_frame)
|
||||||
if not bbox:
|
if not bbox:
|
||||||
# This frame is identical to the previous frame
|
# This frame is identical to the previous frame
|
||||||
if duration:
|
if encoderinfo.get("duration"):
|
||||||
previous["encoderinfo"]["duration"] += encoderinfo["duration"]
|
previous["encoderinfo"]["duration"] += encoderinfo["duration"]
|
||||||
continue
|
continue
|
||||||
if encoderinfo.get("disposal") == 2:
|
if encoderinfo.get("disposal") == 2:
|
||||||
|
@ -888,20 +888,23 @@ def _get_palette_bytes(im):
|
||||||
def _get_background(im, info_background):
|
def _get_background(im, info_background):
|
||||||
background = 0
|
background = 0
|
||||||
if info_background:
|
if info_background:
|
||||||
background = info_background
|
if isinstance(info_background, tuple):
|
||||||
if isinstance(background, tuple):
|
|
||||||
# WebPImagePlugin stores an RGBA value in info["background"]
|
# WebPImagePlugin stores an RGBA value in info["background"]
|
||||||
# So it must be converted to the same format as GifImagePlugin's
|
# So it must be converted to the same format as GifImagePlugin's
|
||||||
# info["background"] - a global color table index
|
# info["background"] - a global color table index
|
||||||
try:
|
try:
|
||||||
background = im.palette.getcolor(background, im)
|
background = im.palette.getcolor(info_background, im)
|
||||||
except ValueError as e:
|
except ValueError as e:
|
||||||
if str(e) == "cannot allocate more than 256 colors":
|
if str(e) not in (
|
||||||
# If all 256 colors are in use,
|
# If all 256 colors are in use,
|
||||||
# then there is no need for the background color
|
# then there is no need for the background color
|
||||||
return 0
|
"cannot allocate more than 256 colors",
|
||||||
else:
|
# Ignore non-opaque WebP background
|
||||||
|
"cannot add non-opaque RGBA color to RGB palette",
|
||||||
|
):
|
||||||
raise
|
raise
|
||||||
|
else:
|
||||||
|
background = info_background
|
||||||
return background
|
return background
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -47,7 +47,14 @@ except ImportError:
|
||||||
# VERSION was removed in Pillow 6.0.0.
|
# VERSION was removed in Pillow 6.0.0.
|
||||||
# PILLOW_VERSION was removed in Pillow 9.0.0.
|
# PILLOW_VERSION was removed in Pillow 9.0.0.
|
||||||
# Use __version__ instead.
|
# Use __version__ instead.
|
||||||
from . import ImageMode, TiffTags, UnidentifiedImageError, __version__, _plugins
|
from . import (
|
||||||
|
ExifTags,
|
||||||
|
ImageMode,
|
||||||
|
TiffTags,
|
||||||
|
UnidentifiedImageError,
|
||||||
|
__version__,
|
||||||
|
_plugins,
|
||||||
|
)
|
||||||
from ._binary import i32le, o32be, o32le
|
from ._binary import i32le, o32be, o32le
|
||||||
from ._deprecate import deprecate
|
from ._deprecate import deprecate
|
||||||
from ._util import DeferredError, is_path
|
from ._util import DeferredError, is_path
|
||||||
|
@ -704,7 +711,6 @@ class Image:
|
||||||
|
|
||||||
def __setstate__(self, state):
|
def __setstate__(self, state):
|
||||||
Image.__init__(self)
|
Image.__init__(self)
|
||||||
self.tile = []
|
|
||||||
info, mode, size, palette, data = state
|
info, mode, size, palette, data = state
|
||||||
self.info = info
|
self.info = info
|
||||||
self.mode = mode
|
self.mode = mode
|
||||||
|
@ -1447,6 +1453,49 @@ class Image:
|
||||||
self._exif._loaded = False
|
self._exif._loaded = False
|
||||||
self.getexif()
|
self.getexif()
|
||||||
|
|
||||||
|
def get_child_images(self):
|
||||||
|
child_images = []
|
||||||
|
exif = self.getexif()
|
||||||
|
ifds = []
|
||||||
|
if ExifTags.Base.SubIFDs in exif:
|
||||||
|
subifd_offsets = exif[ExifTags.Base.SubIFDs]
|
||||||
|
if subifd_offsets:
|
||||||
|
if not isinstance(subifd_offsets, tuple):
|
||||||
|
subifd_offsets = (subifd_offsets,)
|
||||||
|
for subifd_offset in subifd_offsets:
|
||||||
|
ifds.append((exif._get_ifd_dict(subifd_offset), subifd_offset))
|
||||||
|
ifd1 = exif.get_ifd(ExifTags.IFD.IFD1)
|
||||||
|
if ifd1 and ifd1.get(513):
|
||||||
|
ifds.append((ifd1, exif._info.next))
|
||||||
|
|
||||||
|
offset = None
|
||||||
|
for ifd, ifd_offset in ifds:
|
||||||
|
current_offset = self.fp.tell()
|
||||||
|
if offset is None:
|
||||||
|
offset = current_offset
|
||||||
|
|
||||||
|
fp = self.fp
|
||||||
|
thumbnail_offset = ifd.get(513)
|
||||||
|
if thumbnail_offset is not None:
|
||||||
|
try:
|
||||||
|
thumbnail_offset += self._exif_offset
|
||||||
|
except AttributeError:
|
||||||
|
pass
|
||||||
|
self.fp.seek(thumbnail_offset)
|
||||||
|
data = self.fp.read(ifd.get(514))
|
||||||
|
fp = io.BytesIO(data)
|
||||||
|
|
||||||
|
with open(fp) as im:
|
||||||
|
if thumbnail_offset is None:
|
||||||
|
im._frame_pos = [ifd_offset]
|
||||||
|
im._seek(0)
|
||||||
|
im.load()
|
||||||
|
child_images.append(im)
|
||||||
|
|
||||||
|
if offset is not None:
|
||||||
|
self.fp.seek(offset)
|
||||||
|
return child_images
|
||||||
|
|
||||||
def getim(self):
|
def getim(self):
|
||||||
"""
|
"""
|
||||||
Returns a capsule that points to the internal image memory.
|
Returns a capsule that points to the internal image memory.
|
||||||
|
@ -3598,14 +3647,16 @@ class Exif(MutableMapping):
|
||||||
merged_dict = dict(self)
|
merged_dict = dict(self)
|
||||||
|
|
||||||
# get EXIF extension
|
# get EXIF extension
|
||||||
if 0x8769 in self:
|
if ExifTags.IFD.Exif in self:
|
||||||
ifd = self._get_ifd_dict(self[0x8769])
|
ifd = self._get_ifd_dict(self[ExifTags.IFD.Exif])
|
||||||
if ifd:
|
if ifd:
|
||||||
merged_dict.update(ifd)
|
merged_dict.update(ifd)
|
||||||
|
|
||||||
# GPS
|
# GPS
|
||||||
if 0x8825 in self:
|
if ExifTags.IFD.GPSInfo in self:
|
||||||
merged_dict[0x8825] = self._get_ifd_dict(self[0x8825])
|
merged_dict[ExifTags.IFD.GPSInfo] = self._get_ifd_dict(
|
||||||
|
self[ExifTags.IFD.GPSInfo]
|
||||||
|
)
|
||||||
|
|
||||||
return merged_dict
|
return merged_dict
|
||||||
|
|
||||||
|
@ -3615,31 +3666,34 @@ class Exif(MutableMapping):
|
||||||
head = self._get_head()
|
head = self._get_head()
|
||||||
ifd = TiffImagePlugin.ImageFileDirectory_v2(ifh=head)
|
ifd = TiffImagePlugin.ImageFileDirectory_v2(ifh=head)
|
||||||
for tag, value in self.items():
|
for tag, value in self.items():
|
||||||
if tag in [0x8769, 0x8225, 0x8825] and not isinstance(value, dict):
|
if tag in [
|
||||||
|
ExifTags.IFD.Exif,
|
||||||
|
ExifTags.IFD.GPSInfo,
|
||||||
|
] and not isinstance(value, dict):
|
||||||
value = self.get_ifd(tag)
|
value = self.get_ifd(tag)
|
||||||
if (
|
if (
|
||||||
tag == 0x8769
|
tag == ExifTags.IFD.Exif
|
||||||
and 0xA005 in value
|
and ExifTags.IFD.Interop in value
|
||||||
and not isinstance(value[0xA005], dict)
|
and not isinstance(value[ExifTags.IFD.Interop], dict)
|
||||||
):
|
):
|
||||||
value = value.copy()
|
value = value.copy()
|
||||||
value[0xA005] = self.get_ifd(0xA005)
|
value[ExifTags.IFD.Interop] = self.get_ifd(ExifTags.IFD.Interop)
|
||||||
ifd[tag] = value
|
ifd[tag] = value
|
||||||
return b"Exif\x00\x00" + head + ifd.tobytes(offset)
|
return b"Exif\x00\x00" + head + ifd.tobytes(offset)
|
||||||
|
|
||||||
def get_ifd(self, tag):
|
def get_ifd(self, tag):
|
||||||
if tag not in self._ifds:
|
if tag not in self._ifds:
|
||||||
if tag in [0x8769, 0x8825]:
|
if tag == ExifTags.IFD.IFD1:
|
||||||
# exif, gpsinfo
|
if self._info is not None:
|
||||||
|
self._ifds[tag] = self._get_ifd_dict(self._info.next)
|
||||||
|
elif tag in [ExifTags.IFD.Exif, ExifTags.IFD.GPSInfo]:
|
||||||
if tag in self:
|
if tag in self:
|
||||||
self._ifds[tag] = self._get_ifd_dict(self[tag])
|
self._ifds[tag] = self._get_ifd_dict(self[tag])
|
||||||
elif tag in [0xA005, 0x927C]:
|
elif tag in [ExifTags.IFD.Interop, ExifTags.IFD.Makernote]:
|
||||||
# interop, makernote
|
if ExifTags.IFD.Exif not in self._ifds:
|
||||||
if 0x8769 not in self._ifds:
|
self.get_ifd(ExifTags.IFD.Exif)
|
||||||
self.get_ifd(0x8769)
|
tag_data = self._ifds[ExifTags.IFD.Exif][tag]
|
||||||
tag_data = self._ifds[0x8769][tag]
|
if tag == ExifTags.IFD.Makernote:
|
||||||
if tag == 0x927C:
|
|
||||||
# makernote
|
|
||||||
from .TiffImagePlugin import ImageFileDirectory_v2
|
from .TiffImagePlugin import ImageFileDirectory_v2
|
||||||
|
|
||||||
if tag_data[:8] == b"FUJIFILM":
|
if tag_data[:8] == b"FUJIFILM":
|
||||||
|
@ -3715,7 +3769,7 @@ class Exif(MutableMapping):
|
||||||
makernote = {0x1101: dict(self._fixup_dict(camerainfo))}
|
makernote = {0x1101: dict(self._fixup_dict(camerainfo))}
|
||||||
self._ifds[tag] = makernote
|
self._ifds[tag] = makernote
|
||||||
else:
|
else:
|
||||||
# interop
|
# Interop
|
||||||
self._ifds[tag] = self._get_ifd_dict(tag_data)
|
self._ifds[tag] = self._get_ifd_dict(tag_data)
|
||||||
return self._ifds.get(tag, {})
|
return self._ifds.get(tag, {})
|
||||||
|
|
||||||
|
|
|
@ -137,6 +137,10 @@ class ImageFile(Image.Image):
|
||||||
if self.format is not None:
|
if self.format is not None:
|
||||||
return Image.MIME.get(self.format.upper())
|
return Image.MIME.get(self.format.upper())
|
||||||
|
|
||||||
|
def __setstate__(self, state):
|
||||||
|
self.tile = []
|
||||||
|
super().__setstate__(state)
|
||||||
|
|
||||||
def verify(self):
|
def verify(self):
|
||||||
"""Check file integrity"""
|
"""Check file integrity"""
|
||||||
|
|
||||||
|
|
|
@ -125,7 +125,7 @@ class Viewer:
|
||||||
path = options.pop("file")
|
path = options.pop("file")
|
||||||
else:
|
else:
|
||||||
raise TypeError("Missing required argument: 'path'")
|
raise TypeError("Missing required argument: 'path'")
|
||||||
os.system(self.get_command(path, **options))
|
os.system(self.get_command(path, **options)) # nosec
|
||||||
return 1
|
return 1
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -90,6 +90,7 @@ def APP(self, marker):
|
||||||
if "exif" not in self.info:
|
if "exif" not in self.info:
|
||||||
# extract EXIF information (incomplete)
|
# extract EXIF information (incomplete)
|
||||||
self.info["exif"] = s # FIXME: value will change
|
self.info["exif"] = s # FIXME: value will change
|
||||||
|
self._exif_offset = self.fp.tell() - n + 6
|
||||||
elif marker == 0xFFE2 and s[:5] == b"FPXR\0":
|
elif marker == 0xFFE2 and s[:5] == b"FPXR\0":
|
||||||
# extract FlashPix information (incomplete)
|
# extract FlashPix information (incomplete)
|
||||||
self.info["flashpix"] = s # FIXME: value will change
|
self.info["flashpix"] = s # FIXME: value will change
|
||||||
|
|
|
@ -22,7 +22,14 @@ import itertools
|
||||||
import os
|
import os
|
||||||
import struct
|
import struct
|
||||||
|
|
||||||
from . import Image, ImageFile, ImageSequence, JpegImagePlugin, TiffImagePlugin
|
from . import (
|
||||||
|
ExifTags,
|
||||||
|
Image,
|
||||||
|
ImageFile,
|
||||||
|
ImageSequence,
|
||||||
|
JpegImagePlugin,
|
||||||
|
TiffImagePlugin,
|
||||||
|
)
|
||||||
from ._binary import i16be as i16
|
from ._binary import i16be as i16
|
||||||
from ._binary import o32le
|
from ._binary import o32le
|
||||||
|
|
||||||
|
@ -137,7 +144,7 @@ class MpoImageFile(JpegImagePlugin.JpegImageFile):
|
||||||
|
|
||||||
mptype = self.mpinfo[0xB002][frame]["Attribute"]["MPType"]
|
mptype = self.mpinfo[0xB002][frame]["Attribute"]["MPType"]
|
||||||
if mptype.startswith("Large Thumbnail"):
|
if mptype.startswith("Large Thumbnail"):
|
||||||
exif = self.getexif().get_ifd(0x8769)
|
exif = self.getexif().get_ifd(ExifTags.IFD.Exif)
|
||||||
if 40962 in exif and 40963 in exif:
|
if 40962 in exif and 40963 in exif:
|
||||||
self._size = (exif[40962], exif[40963])
|
self._size = (exif[40962], exif[40963])
|
||||||
elif "exif" in self.info:
|
elif "exif" in self.info:
|
||||||
|
|
|
@ -208,7 +208,9 @@ class PpmPlainDecoder(ImageFile.PyDecoder):
|
||||||
tokens = b"".join(block.split())
|
tokens = b"".join(block.split())
|
||||||
for token in tokens:
|
for token in tokens:
|
||||||
if token not in (48, 49):
|
if token not in (48, 49):
|
||||||
raise ValueError(f"Invalid token for this mode: {bytes([token])}")
|
raise ValueError(
|
||||||
|
b"Invalid token for this mode: %s" % bytes([token])
|
||||||
|
)
|
||||||
data = (data + tokens)[:total_bytes]
|
data = (data + tokens)[:total_bytes]
|
||||||
invert = bytes.maketrans(b"01", b"\xFF\x00")
|
invert = bytes.maketrans(b"01", b"\xFF\x00")
|
||||||
return data.translate(invert)
|
return data.translate(invert)
|
||||||
|
@ -242,13 +244,13 @@ class PpmPlainDecoder(ImageFile.PyDecoder):
|
||||||
half_token = tokens.pop() # save half token for later
|
half_token = tokens.pop() # save half token for later
|
||||||
if len(half_token) > max_len: # prevent buildup of half_token
|
if len(half_token) > max_len: # prevent buildup of half_token
|
||||||
raise ValueError(
|
raise ValueError(
|
||||||
f"Token too long found in data: {half_token[:max_len + 1]}"
|
b"Token too long found in data: %s" % half_token[: max_len + 1]
|
||||||
)
|
)
|
||||||
|
|
||||||
for token in tokens:
|
for token in tokens:
|
||||||
if len(token) > max_len:
|
if len(token) > max_len:
|
||||||
raise ValueError(
|
raise ValueError(
|
||||||
f"Token too long found in data: {token[:max_len + 1]}"
|
b"Token too long found in data: %s" % token[: max_len + 1]
|
||||||
)
|
)
|
||||||
value = int(token)
|
value = int(token)
|
||||||
if value > maxval:
|
if value > maxval:
|
||||||
|
|
|
@ -1153,39 +1153,6 @@ class TiffImageFile(ImageFile.ImageFile):
|
||||||
"""Return the current frame number"""
|
"""Return the current frame number"""
|
||||||
return self.__frame
|
return self.__frame
|
||||||
|
|
||||||
def get_child_images(self):
|
|
||||||
if SUBIFD not in self.tag_v2:
|
|
||||||
return []
|
|
||||||
child_images = []
|
|
||||||
exif = self.getexif()
|
|
||||||
offset = None
|
|
||||||
for im_offset in self.tag_v2[SUBIFD]:
|
|
||||||
# reset buffered io handle in case fp
|
|
||||||
# was passed to libtiff, invalidating the buffer
|
|
||||||
current_offset = self._fp.tell()
|
|
||||||
if offset is None:
|
|
||||||
offset = current_offset
|
|
||||||
|
|
||||||
fp = self._fp
|
|
||||||
ifd = exif._get_ifd_dict(im_offset)
|
|
||||||
jpegInterchangeFormat = ifd.get(513)
|
|
||||||
if jpegInterchangeFormat is not None:
|
|
||||||
fp.seek(jpegInterchangeFormat)
|
|
||||||
jpeg_data = fp.read(ifd.get(514))
|
|
||||||
|
|
||||||
fp = io.BytesIO(jpeg_data)
|
|
||||||
|
|
||||||
with Image.open(fp) as im:
|
|
||||||
if jpegInterchangeFormat is None:
|
|
||||||
im._frame_pos = [im_offset]
|
|
||||||
im._seek(0)
|
|
||||||
im.load()
|
|
||||||
child_images.append(im)
|
|
||||||
|
|
||||||
if offset is not None:
|
|
||||||
self._fp.seek(offset)
|
|
||||||
return child_images
|
|
||||||
|
|
||||||
def getxmp(self):
|
def getxmp(self):
|
||||||
"""
|
"""
|
||||||
Returns a dictionary containing the XMP tags.
|
Returns a dictionary containing the XMP tags.
|
||||||
|
|
25
tox.ini
25
tox.ini
|
@ -1,15 +1,13 @@
|
||||||
# Tox (https://tox.readthedocs.io/en/latest/) is a tool for running tests
|
|
||||||
# in multiple virtualenvs. This configuration file will run the
|
|
||||||
# test suite on all supported python versions. To use it,
|
|
||||||
# "python3 -m pip install tox" and then run "tox" from this directory.
|
|
||||||
|
|
||||||
[tox]
|
[tox]
|
||||||
envlist =
|
envlist =
|
||||||
lint
|
lint
|
||||||
py{37,38,39,310,311,py3}
|
py{py3, 311, 310, 39, 38, 37}
|
||||||
minversion = 1.9
|
minversion = 1.9
|
||||||
|
|
||||||
[testenv]
|
[testenv]
|
||||||
|
deps =
|
||||||
|
cffi
|
||||||
|
numpy
|
||||||
extras =
|
extras =
|
||||||
tests
|
tests
|
||||||
commands =
|
commands =
|
||||||
|
@ -17,16 +15,15 @@ commands =
|
||||||
{envpython} -m pip install --global-option="build_ext" --global-option="--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 =
|
allowlist_externals = make
|
||||||
cffi
|
|
||||||
numpy
|
|
||||||
|
|
||||||
[testenv:lint]
|
[testenv:lint]
|
||||||
|
passenv =
|
||||||
|
PRE_COMMIT_COLOR
|
||||||
|
skip_install = true
|
||||||
|
deps =
|
||||||
|
check-manifest
|
||||||
|
pre-commit
|
||||||
commands =
|
commands =
|
||||||
pre-commit run --all-files --show-diff-on-failure
|
pre-commit run --all-files --show-diff-on-failure
|
||||||
check-manifest
|
check-manifest
|
||||||
deps =
|
|
||||||
pre-commit
|
|
||||||
check-manifest
|
|
||||||
skip_install = true
|
|
||||||
passenv = PRE_COMMIT_COLOR
|
|
||||||
|
|
|
@ -152,9 +152,9 @@ deps = {
|
||||||
"libs": [r"*.lib"],
|
"libs": [r"*.lib"],
|
||||||
},
|
},
|
||||||
"xz": {
|
"xz": {
|
||||||
"url": SF_PROJECTS + "/lzmautils/files/xz-5.2.9.tar.gz/download",
|
"url": SF_PROJECTS + "/lzmautils/files/xz-5.4.0.tar.gz/download",
|
||||||
"filename": "xz-5.2.9.tar.gz",
|
"filename": "xz-5.4.0.tar.gz",
|
||||||
"dir": "xz-5.2.9",
|
"dir": "xz-5.4.0",
|
||||||
"license": "COPYING",
|
"license": "COPYING",
|
||||||
"patch": {
|
"patch": {
|
||||||
r"src\liblzma\api\lzma.h": {
|
r"src\liblzma\api\lzma.h": {
|
||||||
|
@ -355,9 +355,9 @@ deps = {
|
||||||
"libs": [r"imagequant.lib"],
|
"libs": [r"imagequant.lib"],
|
||||||
},
|
},
|
||||||
"harfbuzz": {
|
"harfbuzz": {
|
||||||
"url": "https://github.com/harfbuzz/harfbuzz/archive/5.3.1.zip",
|
"url": "https://github.com/harfbuzz/harfbuzz/archive/6.0.0.zip",
|
||||||
"filename": "harfbuzz-5.3.1.zip",
|
"filename": "harfbuzz-6.0.0.zip",
|
||||||
"dir": "harfbuzz-5.3.1",
|
"dir": "harfbuzz-6.0.0",
|
||||||
"license": "COPYING",
|
"license": "COPYING",
|
||||||
"build": [
|
"build": [
|
||||||
cmd_set("CXXFLAGS", "-d2FH4-"),
|
cmd_set("CXXFLAGS", "-d2FH4-"),
|
||||||
|
|
Loading…
Reference in New Issue
Block a user