Merge branch 'master' of https://github.com/python-pillow/Pillow
| 
						 | 
					@ -52,7 +52,7 @@ install:
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
      else
 | 
					      else
 | 
				
			||||||
      {
 | 
					      {
 | 
				
			||||||
        c:\python34\python.exe c:\pillow\winbuild\build_dep.py
 | 
					        c:\python37\python.exe c:\pillow\winbuild\build_dep.py
 | 
				
			||||||
        c:\pillow\winbuild\build_deps.cmd
 | 
					        c:\pillow\winbuild\build_deps.cmd
 | 
				
			||||||
        $host.SetShouldExit(0)
 | 
					        $host.SetShouldExit(0)
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
							
								
								
									
										2
									
								
								.github/CONTRIBUTING.md
									
									
									
									
										vendored
									
									
								
							
							
						
						| 
						 | 
					@ -9,7 +9,7 @@ Please send a pull request to the master branch. Please include [documentation](
 | 
				
			||||||
- Fork the Pillow repository.
 | 
					- Fork the Pillow repository.
 | 
				
			||||||
- Create a branch from master.
 | 
					- Create a branch from master.
 | 
				
			||||||
- Develop bug fixes, features, tests, etc.
 | 
					- Develop bug fixes, features, tests, etc.
 | 
				
			||||||
- Run the test suite on Python 2.7 and 3.x. You can enable [Travis CI](https://travis-ci.org/profile/) and [AppVeyor](https://ci.appveyor.com/projects/new) on your repo to catch test failures prior to the pull request, and [Coveralls](https://coveralls.io/repos/new) to see if the changed code is covered by tests.
 | 
					- Run the test suite on Python 2.7 and 3.x. You can enable [Travis CI](https://travis-ci.org/profile/) and [AppVeyor](https://ci.appveyor.com/projects/new) on your repo to catch test failures prior to the pull request, and [Codecov](https://codecov.io/gh) to see if the changed code is covered by tests.
 | 
				
			||||||
- Create a pull request to pull the changes from your branch to the Pillow master.
 | 
					- Create a pull request to pull the changes from your branch to the Pillow master.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
### Guidelines
 | 
					### Guidelines
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
							
								
								
									
										30
									
								
								.travis.yml
									
									
									
									
									
								
							
							
						
						| 
						 | 
					@ -18,41 +18,27 @@ matrix:
 | 
				
			||||||
      env: LINT="true"
 | 
					      env: LINT="true"
 | 
				
			||||||
    - python: "pypy2.7-6.0"
 | 
					    - python: "pypy2.7-6.0"
 | 
				
			||||||
      name: "PyPy2 Xenial"
 | 
					      name: "PyPy2 Xenial"
 | 
				
			||||||
      dist: xenial
 | 
					 | 
				
			||||||
    - python: "pypy3.5-6.0"
 | 
					    - python: "pypy3.5-6.0"
 | 
				
			||||||
      name: "PyPy3 Xenial"
 | 
					      name: "PyPy3 Xenial"
 | 
				
			||||||
      dist: xenial
 | 
					 | 
				
			||||||
    - python: '3.7'
 | 
					    - python: '3.7'
 | 
				
			||||||
      name: "3.7 Xenial"
 | 
					      name: "3.7 Xenial"
 | 
				
			||||||
    - python: '2.7'
 | 
					    - python: '2.7'
 | 
				
			||||||
      name: "2.7 Xenial"
 | 
					      name: "2.7 Xenial"
 | 
				
			||||||
    - python: '2.7'
 | 
					 | 
				
			||||||
      name: "2.7 Trusty"
 | 
					 | 
				
			||||||
      dist: trusty
 | 
					 | 
				
			||||||
    - python:  "2.7_with_system_site_packages" # For PyQt4
 | 
					    - python:  "2.7_with_system_site_packages" # For PyQt4
 | 
				
			||||||
      name: "2.7_with_system_site_packages Xenial"
 | 
					      name: "2.7_with_system_site_packages Xenial"
 | 
				
			||||||
      services: xvfb
 | 
					      services: xvfb
 | 
				
			||||||
    - python:  "2.7_with_system_site_packages" # For PyQt4
 | 
					 | 
				
			||||||
      name: "2.7_with_system_site_packages Trusty"
 | 
					 | 
				
			||||||
      dist: trusty
 | 
					 | 
				
			||||||
    - python: '3.6'
 | 
					    - python: '3.6'
 | 
				
			||||||
      name: "3.6 Xenial"
 | 
					      name: "3.6 Xenial PYTHONOPTIMIZE=1"
 | 
				
			||||||
    - python: '3.6'
 | 
					 | 
				
			||||||
      name: "3.6 Trusty PYTHONOPTIMIZE=1"
 | 
					 | 
				
			||||||
      dist: trusty
 | 
					 | 
				
			||||||
      env: PYTHONOPTIMIZE=1
 | 
					      env: PYTHONOPTIMIZE=1
 | 
				
			||||||
    - python: '3.5'
 | 
					    - python: '3.5'
 | 
				
			||||||
      name: "3.5 Xenial"
 | 
					      name: "3.5 Xenial PYTHONOPTIMIZE=2"
 | 
				
			||||||
    - python: '3.5'
 | 
					 | 
				
			||||||
      name: "3.5 Trusty PYTHONOPTIMIZE=2"
 | 
					 | 
				
			||||||
      dist: trusty
 | 
					 | 
				
			||||||
      env: PYTHONOPTIMIZE=2
 | 
					      env: PYTHONOPTIMIZE=2
 | 
				
			||||||
    - python: "3.8-dev"
 | 
					    - python: "3.8-dev"
 | 
				
			||||||
      name: "3.8-dev Xenial"
 | 
					      name: "3.8-dev Xenial"
 | 
				
			||||||
    - env: DOCKER="alpine" DOCKER_TAG="master"
 | 
					    - env: DOCKER="alpine" DOCKER_TAG="master"
 | 
				
			||||||
    - env: DOCKER="arch" DOCKER_TAG="master" # contains PyQt5
 | 
					    - env: DOCKER="arch" DOCKER_TAG="master" # contains PyQt5
 | 
				
			||||||
    - env: DOCKER="ubuntu-trusty-x86" DOCKER_TAG="master"
 | 
					    - env: DOCKER="ubuntu-16.04-xenial-amd64" DOCKER_TAG="master"
 | 
				
			||||||
    - env: DOCKER="ubuntu-xenial-amd64" DOCKER_TAG="master"
 | 
					    - env: DOCKER="ubuntu-18.04-bionic-amd64" DOCKER_TAG="master"
 | 
				
			||||||
    - env: DOCKER="debian-stretch-x86" DOCKER_TAG="master"
 | 
					    - env: DOCKER="debian-stretch-x86" DOCKER_TAG="master"
 | 
				
			||||||
    - env: DOCKER="centos-6-amd64" DOCKER_TAG="master"
 | 
					    - env: DOCKER="centos-6-amd64" DOCKER_TAG="master"
 | 
				
			||||||
    - env: DOCKER="centos-7-amd64" DOCKER_TAG="master"
 | 
					    - env: DOCKER="centos-7-amd64" DOCKER_TAG="master"
 | 
				
			||||||
| 
						 | 
					@ -75,14 +61,6 @@ install:
 | 
				
			||||||
      .travis/install.sh;
 | 
					      .travis/install.sh;
 | 
				
			||||||
    fi
 | 
					    fi
 | 
				
			||||||
 | 
					
 | 
				
			||||||
before_script:
 | 
					 | 
				
			||||||
# Qt needs a display for some of the tests, and it's only run on the system site packages install
 | 
					 | 
				
			||||||
- |
 | 
					 | 
				
			||||||
  if [ "$TRAVIS_JOB_NAME" == "2.7_with_system_site_packages Trusty" ]; then
 | 
					 | 
				
			||||||
    export DISPLAY=:99.0
 | 
					 | 
				
			||||||
    sh -e /etc/init.d/xvfb start
 | 
					 | 
				
			||||||
  fi
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
script:
 | 
					script:
 | 
				
			||||||
- |
 | 
					- |
 | 
				
			||||||
  if [ "$LINT" == "true" ]; then
 | 
					  if [ "$LINT" == "true" ]; then
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
							
								
								
									
										94
									
								
								CHANGES.rst
									
									
									
									
									
								
							
							
						
						| 
						 | 
					@ -2,12 +2,102 @@
 | 
				
			||||||
Changelog (Pillow)
 | 
					Changelog (Pillow)
 | 
				
			||||||
==================
 | 
					==================
 | 
				
			||||||
 | 
					
 | 
				
			||||||
6.0.0 (unreleased)
 | 
					6.0.0 (2019-04-01)
 | 
				
			||||||
------------------
 | 
					------------------
 | 
				
			||||||
 | 
					
 | 
				
			||||||
- Python 2.7 support will be removed in Pillow 7.0.0 #3682
 | 
					- Python 2.7 support will be removed in Pillow 7.0.0 #3682
 | 
				
			||||||
  [hugovk]
 | 
					  [hugovk]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					- Add EXIF class #3625
 | 
				
			||||||
 | 
					  [radarhere]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					- Add ImageOps exif_transpose method #3687
 | 
				
			||||||
 | 
					  [radarhere]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					- Added warnings to deprecated CMSProfile attributes #3615
 | 
				
			||||||
 | 
					  [hugovk]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					- Documented reading TIFF multiframe images #3720
 | 
				
			||||||
 | 
					  [akuchling]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					- Improved speed of opening an MPO file #3658
 | 
				
			||||||
 | 
					  [Glandos]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					- Update palette in quantize #3721
 | 
				
			||||||
 | 
					  [radarhere]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					- Improvements to TIFF is_animated and n_frames #3714
 | 
				
			||||||
 | 
					  [radarhere]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					- Fixed incompatible pointer type warnings #3754
 | 
				
			||||||
 | 
					  [radarhere]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					- Improvements to PA and LA conversion and palette operations #3728
 | 
				
			||||||
 | 
					  [radarhere]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					- Consistent DPI rounding #3709
 | 
				
			||||||
 | 
					  [radarhere]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					- Change size of MPO image to match frame #3588
 | 
				
			||||||
 | 
					  [radarhere]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					- Read Photoshop resolution data #3701
 | 
				
			||||||
 | 
					  [radarhere]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					- Ensure image is mutable before saving #3724
 | 
				
			||||||
 | 
					  [radarhere]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					- Correct remap_palette documentation #3740
 | 
				
			||||||
 | 
					  [radarhere]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					- Promote P images to PA in putalpha #3726
 | 
				
			||||||
 | 
					  [radarhere]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					- Allow RGB and RGBA values for new P images #3719
 | 
				
			||||||
 | 
					  [radarhere]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					- Fixed TIFF bug when seeking backwards and then forwards #3713
 | 
				
			||||||
 | 
					  [radarhere]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					- Cache EXIF information #3498
 | 
				
			||||||
 | 
					  [Glandos]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					- Added transparency for all PNG greyscale modes #3744
 | 
				
			||||||
 | 
					  [radarhere]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					- Fix deprecation warnings in Python 3.8 #3749
 | 
				
			||||||
 | 
					  [radarhere]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					- Fixed GIF bug when rewinding to a non-zero frame #3716
 | 
				
			||||||
 | 
					  [radarhere]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					- Only close original fp in __del__ and __exit__ if original fp is exclusive #3683
 | 
				
			||||||
 | 
					  [radarhere]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					- Fix BytesWarning in Tests/test_numpy.py #3725
 | 
				
			||||||
 | 
					  [jdufresne]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					- Add missing MIME types and extensions #3520
 | 
				
			||||||
 | 
					  [pirate486743186]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					- Add I;16 PNG save #3566
 | 
				
			||||||
 | 
					  [radarhere]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					- Add support for BMP RGBA bitfield compression #3705
 | 
				
			||||||
 | 
					  [radarhere]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					- Added ability to set language for text rendering #3693
 | 
				
			||||||
 | 
					  [iwsfutcmd]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					- Only close exclusive fp on Image __exit__ #3698
 | 
				
			||||||
 | 
					  [radarhere]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					- Changed EPS subprocess stdout from devnull to None #3635
 | 
				
			||||||
 | 
					  [radarhere]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					- Add reading old-JPEG compressed TIFFs #3489
 | 
				
			||||||
 | 
					  [kkopachev]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
- Add EXIF support for PNG #3674
 | 
					- Add EXIF support for PNG #3674
 | 
				
			||||||
  [radarhere]
 | 
					  [radarhere]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -38,7 +128,7 @@ Changelog (Pillow)
 | 
				
			||||||
- Make ContainerIO.isatty() return a bool, not int #3568
 | 
					- Make ContainerIO.isatty() return a bool, not int #3568
 | 
				
			||||||
  [jdufresne]
 | 
					  [jdufresne]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
- Add support for I;16 modes for more transpose operations #3563
 | 
					- Add support to all transpose operations for I;16 modes #3563, #3741
 | 
				
			||||||
  [radarhere]
 | 
					  [radarhere]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
- Deprecate support for PyQt4 and PySide #3655
 | 
					- Deprecate support for PyQt4 and PySide #3655
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
							
								
								
									
										49
									
								
								README.rst
									
									
									
									
									
								
							
							
						
						| 
						 | 
					@ -20,6 +20,30 @@ Pillow is the friendly PIL fork by `Alex Clark and Contributors <https://github.
 | 
				
			||||||
    * - social
 | 
					    * - social
 | 
				
			||||||
      - |gitter| |twitter|
 | 
					      - |gitter| |twitter|
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.. end-badges
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					More Information
 | 
				
			||||||
 | 
					----------------
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					- `Documentation <https://pillow.readthedocs.io/>`_
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  - `Installation <https://pillow.readthedocs.io/en/latest/installation.html>`_
 | 
				
			||||||
 | 
					  - `Handbook <https://pillow.readthedocs.io/en/latest/handbook/index.html>`_
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					- `Contribute <https://github.com/python-pillow/Pillow/blob/master/.github/CONTRIBUTING.md>`_
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  - `Issues <https://github.com/python-pillow/Pillow/issues>`_
 | 
				
			||||||
 | 
					  - `Pull requests <https://github.com/python-pillow/Pillow/pulls>`_
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					- `Changelog <https://github.com/python-pillow/Pillow/blob/master/CHANGES.rst>`_
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  - `Pre-fork <https://github.com/python-pillow/Pillow/blob/master/CHANGES.rst#pre-fork>`_
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Report a Vulnerability
 | 
				
			||||||
 | 
					----------------------
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					To report a security vulnerability, please follow the procedure described in the `Tidelift security policy <https://tidelift.com/docs/security>`_.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
.. |docs| image:: https://readthedocs.org/projects/pillow/badge/?version=latest
 | 
					.. |docs| image:: https://readthedocs.org/projects/pillow/badge/?version=latest
 | 
				
			||||||
   :target: https://pillow.readthedocs.io/?badge=latest
 | 
					   :target: https://pillow.readthedocs.io/?badge=latest
 | 
				
			||||||
   :alt: Documentation Status
 | 
					   :alt: Documentation Status
 | 
				
			||||||
| 
						 | 
					@ -36,8 +60,8 @@ Pillow is the friendly PIL fork by `Alex Clark and Contributors <https://github.
 | 
				
			||||||
   :target: https://ci.appveyor.com/project/python-pillow/Pillow
 | 
					   :target: https://ci.appveyor.com/project/python-pillow/Pillow
 | 
				
			||||||
   :alt: AppVeyor CI build status (Windows)
 | 
					   :alt: AppVeyor CI build status (Windows)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
.. |coverage| image:: https://coveralls.io/repos/python-pillow/Pillow/badge.svg?branch=master&service=github
 | 
					.. |coverage| image:: https://codecov.io/gh/python-pillow/Pillow/branch/master/graph/badge.svg
 | 
				
			||||||
   :target: https://coveralls.io/github/python-pillow/Pillow?branch=master
 | 
					   :target: https://codecov.io/gh/python-pillow/Pillow
 | 
				
			||||||
   :alt: Code coverage
 | 
					   :alt: Code coverage
 | 
				
			||||||
 | 
					
 | 
				
			||||||
.. |zenodo| image:: https://zenodo.org/badge/17549/python-pillow/Pillow.svg
 | 
					.. |zenodo| image:: https://zenodo.org/badge/17549/python-pillow/Pillow.svg
 | 
				
			||||||
| 
						 | 
					@ -61,24 +85,3 @@ Pillow is the friendly PIL fork by `Alex Clark and Contributors <https://github.
 | 
				
			||||||
.. |twitter| image:: https://img.shields.io/badge/tweet-on%20Twitter-00aced.svg
 | 
					.. |twitter| image:: https://img.shields.io/badge/tweet-on%20Twitter-00aced.svg
 | 
				
			||||||
   :target: https://twitter.com/PythonPillow
 | 
					   :target: https://twitter.com/PythonPillow
 | 
				
			||||||
   :alt: Follow on https://twitter.com/PythonPillow
 | 
					   :alt: Follow on https://twitter.com/PythonPillow
 | 
				
			||||||
 | 
					 | 
				
			||||||
.. end-badges
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
More Information
 | 
					 | 
				
			||||||
----------------
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
- `Documentation <https://pillow.readthedocs.io/>`_
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  - `Installation <https://pillow.readthedocs.io/en/latest/installation.html>`_
 | 
					 | 
				
			||||||
  - `Handbook <https://pillow.readthedocs.io/en/latest/handbook/index.html>`_
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
- `Contribute <https://github.com/python-pillow/Pillow/blob/master/.github/CONTRIBUTING.md>`_
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  - `Issues <https://github.com/python-pillow/Pillow/issues>`_
 | 
					 | 
				
			||||||
  - `Pull requests <https://github.com/python-pillow/Pillow/pulls>`_
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
- `Changelog <https://github.com/python-pillow/Pillow/blob/master/CHANGES.rst>`_
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  - `Pre-fork <https://github.com/python-pillow/Pillow/blob/master/CHANGES.rst#pre-fork>`_
 | 
					 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -102,4 +102,4 @@ Released as needed privately to individual vendors for critical security-related
 | 
				
			||||||
 | 
					
 | 
				
			||||||
## Documentation
 | 
					## Documentation
 | 
				
			||||||
 | 
					
 | 
				
			||||||
* [ ] Make sure the default version for Read the Docs is the latest tagged release e.g. `d2d43879` (5.4.0)
 | 
					* [ ] Make sure the [default version for Read the Docs](https://pillow.readthedocs.io/en/stable/) is up-to-date with the release changes
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
							
								
								
									
										
											BIN
										
									
								
								Tests/images/1_trns.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| 
		 After Width: | Height: | Size: 612 B  | 
							
								
								
									
										
											BIN
										
									
								
								Tests/images/drawing_roundDown.emf
									
									
									
									
									
										Normal file
									
								
							
							
						
						
							
								
								
									
										
											BIN
										
									
								
								Tests/images/fujifilm.mpo
									
									
									
									
									
										Normal file
									
								
							
							
						
						| 
		 After Width: | Height: | Size: 9.5 MiB  | 
							
								
								
									
										
											BIN
										
									
								
								Tests/images/hopper.pnm
									
									
									
									
									
										Normal file
									
								
							
							
						
						
							
								
								
									
										
											BIN
										
									
								
								Tests/images/hopper_orientation_2.jpg
									
									
									
									
									
										Normal file
									
								
							
							
						
						| 
		 After Width: | Height: | Size: 4.6 KiB  | 
							
								
								
									
										
											BIN
										
									
								
								Tests/images/hopper_orientation_2.webp
									
									
									
									
									
										Normal file
									
								
							
							
						
						| 
		 After Width: | Height: | Size: 3.6 KiB  | 
							
								
								
									
										
											BIN
										
									
								
								Tests/images/hopper_orientation_3.jpg
									
									
									
									
									
										Normal file
									
								
							
							
						
						| 
		 After Width: | Height: | Size: 4.6 KiB  | 
							
								
								
									
										
											BIN
										
									
								
								Tests/images/hopper_orientation_3.webp
									
									
									
									
									
										Normal file
									
								
							
							
						
						| 
		 After Width: | Height: | Size: 3.7 KiB  | 
							
								
								
									
										
											BIN
										
									
								
								Tests/images/hopper_orientation_4.jpg
									
									
									
									
									
										Normal file
									
								
							
							
						
						| 
		 After Width: | Height: | Size: 4.6 KiB  | 
							
								
								
									
										
											BIN
										
									
								
								Tests/images/hopper_orientation_4.webp
									
									
									
									
									
										Normal file
									
								
							
							
						
						| 
		 After Width: | Height: | Size: 3.6 KiB  | 
							
								
								
									
										
											BIN
										
									
								
								Tests/images/hopper_orientation_5.jpg
									
									
									
									
									
										Normal file
									
								
							
							
						
						| 
		 After Width: | Height: | Size: 4.8 KiB  | 
							
								
								
									
										
											BIN
										
									
								
								Tests/images/hopper_orientation_5.webp
									
									
									
									
									
										Normal file
									
								
							
							
						
						| 
		 After Width: | Height: | Size: 3.5 KiB  | 
							
								
								
									
										
											BIN
										
									
								
								Tests/images/hopper_orientation_6.jpg
									
									
									
									
									
										Normal file
									
								
							
							
						
						| 
		 After Width: | Height: | Size: 4.8 KiB  | 
							
								
								
									
										
											BIN
										
									
								
								Tests/images/hopper_orientation_6.webp
									
									
									
									
									
										Normal file
									
								
							
							
						
						| 
		 After Width: | Height: | Size: 3.6 KiB  | 
							
								
								
									
										
											BIN
										
									
								
								Tests/images/hopper_orientation_7.jpg
									
									
									
									
									
										Normal file
									
								
							
							
						
						| 
		 After Width: | Height: | Size: 4.8 KiB  | 
							
								
								
									
										
											BIN
										
									
								
								Tests/images/hopper_orientation_7.webp
									
									
									
									
									
										Normal file
									
								
							
							
						
						| 
		 After Width: | Height: | Size: 3.6 KiB  | 
							
								
								
									
										
											BIN
										
									
								
								Tests/images/hopper_orientation_8.jpg
									
									
									
									
									
										Normal file
									
								
							
							
						
						| 
		 After Width: | Height: | Size: 4.8 KiB  | 
							
								
								
									
										
											BIN
										
									
								
								Tests/images/hopper_orientation_8.webp
									
									
									
									
									
										Normal file
									
								
							
							
						
						| 
		 After Width: | Height: | Size: 3.6 KiB  | 
							
								
								
									
										
											BIN
										
									
								
								Tests/images/hopper_roundDown.bmp
									
									
									
									
									
										Normal file
									
								
							
							
						
						| 
		 After Width: | Height: | Size: 48 KiB  | 
							
								
								
									
										
											BIN
										
									
								
								Tests/images/hopper_roundDown_2.tif
									
									
									
									
									
										Normal file
									
								
							
							
						
						
							
								
								
									
										
											BIN
										
									
								
								Tests/images/hopper_roundDown_3.tif
									
									
									
									
									
										Normal file
									
								
							
							
						
						
							
								
								
									
										
											BIN
										
									
								
								Tests/images/hopper_roundDown_None.tif
									
									
									
									
									
										Normal file
									
								
							
							
						
						
							
								
								
									
										
											BIN
										
									
								
								Tests/images/hopper_roundUp_2.tif
									
									
									
									
									
										Normal file
									
								
							
							
						
						
							
								
								
									
										
											BIN
										
									
								
								Tests/images/hopper_roundUp_3.tif
									
									
									
									
									
										Normal file
									
								
							
							
						
						
							
								
								
									
										
											BIN
										
									
								
								Tests/images/hopper_roundUp_None.tif
									
									
									
									
									
										Normal file
									
								
							
							
						
						
							
								
								
									
										
											BIN
										
									
								
								Tests/images/i_trns.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| 
		 After Width: | Height: | Size: 1.4 KiB  | 
							
								
								
									
										
											BIN
										
									
								
								Tests/images/iptc_roundDown.jpg
									
									
									
									
									
										Normal file
									
								
							
							
						
						| 
		 After Width: | Height: | Size: 20 KiB  | 
							
								
								
									
										
											BIN
										
									
								
								Tests/images/iptc_roundUp.jpg
									
									
									
									
									
										Normal file
									
								
							
							
						
						| 
		 After Width: | Height: | Size: 20 KiB  | 
							
								
								
									
										
											BIN
										
									
								
								Tests/images/old-style-jpeg-compression.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| 
		 After Width: | Height: | Size: 1.4 MiB  | 
							
								
								
									
										
											BIN
										
									
								
								Tests/images/old-style-jpeg-compression.tif
									
									
									
									
									
										Normal file
									
								
							
							
						
						
							
								
								
									
										
											BIN
										
									
								
								Tests/images/rgb32bf-rgba.bmp
									
									
									
									
									
										Normal file
									
								
							
							
						
						| 
		 After Width: | Height: | Size: 32 KiB  | 
							
								
								
									
										
											BIN
										
									
								
								Tests/images/sugarshack_frame_size.mpo
									
									
									
									
									
										Normal file
									
								
							
							
						
						| 
		 After Width: | Height: | Size: 117 KiB  | 
							
								
								
									
										
											BIN
										
									
								
								Tests/images/test_language.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| 
		 After Width: | Height: | Size: 777 B  | 
| 
						 | 
					@ -71,6 +71,27 @@ class TestFileBmp(PillowTestCase):
 | 
				
			||||||
        self.assertEqual(im.size, reloaded.size)
 | 
					        self.assertEqual(im.size, reloaded.size)
 | 
				
			||||||
        self.assertEqual(reloaded.format, "JPEG")
 | 
					        self.assertEqual(reloaded.format, "JPEG")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def test_load_dpi_rounding(self):
 | 
				
			||||||
 | 
					        # Round up
 | 
				
			||||||
 | 
					        im = Image.open('Tests/images/hopper.bmp')
 | 
				
			||||||
 | 
					        self.assertEqual(im.info["dpi"], (96, 96))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        # Round down
 | 
				
			||||||
 | 
					        im = Image.open('Tests/images/hopper_roundDown.bmp')
 | 
				
			||||||
 | 
					        self.assertEqual(im.info["dpi"], (72, 72))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def test_save_dpi_rounding(self):
 | 
				
			||||||
 | 
					        outfile = self.tempfile("temp.bmp")
 | 
				
			||||||
 | 
					        im = Image.open('Tests/images/hopper.bmp')
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        im.save(outfile, dpi=(72.2, 72.2))
 | 
				
			||||||
 | 
					        reloaded = Image.open(outfile)
 | 
				
			||||||
 | 
					        self.assertEqual(reloaded.info["dpi"], (72, 72))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        im.save(outfile, dpi=(72.8, 72.8))
 | 
				
			||||||
 | 
					        reloaded = Image.open(outfile)
 | 
				
			||||||
 | 
					        self.assertEqual(reloaded.info["dpi"], (73, 73))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def test_load_dib(self):
 | 
					    def test_load_dib(self):
 | 
				
			||||||
        # test for #1293, Imagegrab returning Unsupported Bitfields Format
 | 
					        # test for #1293, Imagegrab returning Unsupported Bitfields Format
 | 
				
			||||||
        im = Image.open('Tests/images/clipboard.dib')
 | 
					        im = Image.open('Tests/images/clipboard.dib')
 | 
				
			||||||
| 
						 | 
					@ -90,3 +111,15 @@ class TestFileBmp(PillowTestCase):
 | 
				
			||||||
        self.assertEqual(reloaded.format, "DIB")
 | 
					        self.assertEqual(reloaded.format, "DIB")
 | 
				
			||||||
        self.assertEqual(reloaded.get_format_mimetype(), "image/bmp")
 | 
					        self.assertEqual(reloaded.get_format_mimetype(), "image/bmp")
 | 
				
			||||||
        self.assert_image_equal(im, reloaded)
 | 
					        self.assert_image_equal(im, reloaded)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def test_rgba_bitfields(self):
 | 
				
			||||||
 | 
					        # This test image has been manually hexedited
 | 
				
			||||||
 | 
					        # to change the bitfield compression in the header from XBGR to RGBA
 | 
				
			||||||
 | 
					        im = Image.open("Tests/images/rgb32bf-rgba.bmp")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        # So before the comparing the image, swap the channels
 | 
				
			||||||
 | 
					        b, g, r = im.split()[1:]
 | 
				
			||||||
 | 
					        im = Image.merge("RGB", (r, g, b))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        target = Image.open("Tests/images/bmp/q/rgb32bf-xbgr.bmp")
 | 
				
			||||||
 | 
					        self.assert_image_equal(im, target)
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -230,6 +230,15 @@ class TestFileGif(PillowTestCase):
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        self.assertEqual(im.info, info)
 | 
					        self.assertEqual(im.info, info)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def test_seek_rewind(self):
 | 
				
			||||||
 | 
					        im = Image.open("Tests/images/iss634.gif")
 | 
				
			||||||
 | 
					        im.seek(2)
 | 
				
			||||||
 | 
					        im.seek(1)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        expected = Image.open("Tests/images/iss634.gif")
 | 
				
			||||||
 | 
					        expected.seek(1)
 | 
				
			||||||
 | 
					        self.assert_image_equal(im, expected)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def test_n_frames(self):
 | 
					    def test_n_frames(self):
 | 
				
			||||||
        for path, n_frames in [
 | 
					        for path, n_frames in [
 | 
				
			||||||
            [TEST_GIF, 1],
 | 
					            [TEST_GIF, 1],
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -14,6 +14,7 @@ class TestFileIco(PillowTestCase):
 | 
				
			||||||
        self.assertEqual(im.mode, "RGBA")
 | 
					        self.assertEqual(im.mode, "RGBA")
 | 
				
			||||||
        self.assertEqual(im.size, (16, 16))
 | 
					        self.assertEqual(im.size, (16, 16))
 | 
				
			||||||
        self.assertEqual(im.format, "ICO")
 | 
					        self.assertEqual(im.format, "ICO")
 | 
				
			||||||
 | 
					        self.assertEqual(im.get_format_mimetype(), "image/x-icon")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def test_invalid_file(self):
 | 
					    def test_invalid_file(self):
 | 
				
			||||||
        with open("Tests/images/flower.jpg", "rb") as fp:
 | 
					        with open("Tests/images/flower.jpg", "rb") as fp:
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -524,6 +524,27 @@ class TestFileJpeg(PillowTestCase):
 | 
				
			||||||
        reloaded.load()
 | 
					        reloaded.load()
 | 
				
			||||||
        self.assertEqual(im.info['dpi'], reloaded.info['dpi'])
 | 
					        self.assertEqual(im.info['dpi'], reloaded.info['dpi'])
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def test_load_dpi_rounding(self):
 | 
				
			||||||
 | 
					        # Round up
 | 
				
			||||||
 | 
					        im = Image.open('Tests/images/iptc_roundUp.jpg')
 | 
				
			||||||
 | 
					        self.assertEqual(im.info["dpi"], (44, 44))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        # Round down
 | 
				
			||||||
 | 
					        im = Image.open('Tests/images/iptc_roundDown.jpg')
 | 
				
			||||||
 | 
					        self.assertEqual(im.info["dpi"], (2, 2))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def test_save_dpi_rounding(self):
 | 
				
			||||||
 | 
					        outfile = self.tempfile("temp.jpg")
 | 
				
			||||||
 | 
					        im = Image.open('Tests/images/hopper.jpg')
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        im.save(outfile, dpi=(72.2, 72.2))
 | 
				
			||||||
 | 
					        reloaded = Image.open(outfile)
 | 
				
			||||||
 | 
					        self.assertEqual(reloaded.info["dpi"], (72, 72))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        im.save(outfile, dpi=(72.8, 72.8))
 | 
				
			||||||
 | 
					        reloaded = Image.open(outfile)
 | 
				
			||||||
 | 
					        self.assertEqual(reloaded.info["dpi"], (73, 73))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def test_dpi_tuple_from_exif(self):
 | 
					    def test_dpi_tuple_from_exif(self):
 | 
				
			||||||
        # Arrange
 | 
					        # Arrange
 | 
				
			||||||
        # This Photoshop CC 2017 image has DPI in EXIF not metadata
 | 
					        # This Photoshop CC 2017 image has DPI in EXIF not metadata
 | 
				
			||||||
| 
						 | 
					@ -590,6 +611,15 @@ class TestFileJpeg(PillowTestCase):
 | 
				
			||||||
        # Act / Assert
 | 
					        # Act / Assert
 | 
				
			||||||
        self.assertEqual(im._getexif()[306], '2017:03:13 23:03:09')
 | 
					        self.assertEqual(im._getexif()[306], '2017:03:13 23:03:09')
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def test_photoshop(self):
 | 
				
			||||||
 | 
					        im = Image.open("Tests/images/photoshop-200dpi.jpg")
 | 
				
			||||||
 | 
					        self.assertEqual(im.info["photoshop"][0x03ed], {
 | 
				
			||||||
 | 
					            'XResolution': 200.0,
 | 
				
			||||||
 | 
					            'DisplayedUnitsX': 1,
 | 
				
			||||||
 | 
					            'YResolution': 200.0,
 | 
				
			||||||
 | 
					            'DisplayedUnitsY': 1,
 | 
				
			||||||
 | 
					        })
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@unittest.skipUnless(sys.platform.startswith('win32'), "Windows only")
 | 
					@unittest.skipUnless(sys.platform.startswith('win32'), "Windows only")
 | 
				
			||||||
class TestFileCloseW32(PillowTestCase):
 | 
					class TestFileCloseW32(PillowTestCase):
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -716,3 +716,10 @@ class TestFileLibTiff(LibTiffTestCase):
 | 
				
			||||||
        im = Image.open(infile)
 | 
					        im = Image.open(infile)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        self.assert_image_similar_tofile(im, "Tests/images/flower.jpg", 0.5)
 | 
					        self.assert_image_similar_tofile(im, "Tests/images/flower.jpg", 0.5)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def test_old_style_jpeg(self):
 | 
				
			||||||
 | 
					        infile = "Tests/images/old-style-jpeg-compression.tif"
 | 
				
			||||||
 | 
					        im = Image.open(infile)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        self.assert_image_equal_tofile(im,
 | 
				
			||||||
 | 
					                                       "Tests/images/old-style-jpeg-compression.png")
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -55,6 +55,27 @@ class TestFileMpo(PillowTestCase):
 | 
				
			||||||
            self.assertEqual(info[296], 2)
 | 
					            self.assertEqual(info[296], 2)
 | 
				
			||||||
            self.assertEqual(info[34665], 188)
 | 
					            self.assertEqual(info[34665], 188)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def test_frame_size(self):
 | 
				
			||||||
 | 
					        # This image has been hexedited to contain a different size
 | 
				
			||||||
 | 
					        # in the EXIF data of the second frame
 | 
				
			||||||
 | 
					        im = Image.open("Tests/images/sugarshack_frame_size.mpo")
 | 
				
			||||||
 | 
					        self.assertEqual(im.size, (640, 480))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        im.seek(1)
 | 
				
			||||||
 | 
					        self.assertEqual(im.size, (680, 480))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def test_parallax(self):
 | 
				
			||||||
 | 
					        # Nintendo
 | 
				
			||||||
 | 
					        im = Image.open("Tests/images/sugarshack.mpo")
 | 
				
			||||||
 | 
					        exif = im.getexif()
 | 
				
			||||||
 | 
					        self.assertEqual(exif.get_ifd(0x927c)[0x1101]["Parallax"], -44.798187255859375)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        # Fujifilm
 | 
				
			||||||
 | 
					        im = Image.open("Tests/images/fujifilm.mpo")
 | 
				
			||||||
 | 
					        im.seek(1)
 | 
				
			||||||
 | 
					        exif = im.getexif()
 | 
				
			||||||
 | 
					        self.assertEqual(exif.get_ifd(0x927c)[0xb211], -3.125)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def test_mp(self):
 | 
					    def test_mp(self):
 | 
				
			||||||
        for test_file in test_files:
 | 
					        for test_file in test_files:
 | 
				
			||||||
            im = Image.open(test_file)
 | 
					            im = Image.open(test_file)
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -13,6 +13,7 @@ class TestFilePcx(PillowTestCase):
 | 
				
			||||||
        self.assertEqual(im2.mode, im.mode)
 | 
					        self.assertEqual(im2.mode, im.mode)
 | 
				
			||||||
        self.assertEqual(im2.size, im.size)
 | 
					        self.assertEqual(im2.size, im.size)
 | 
				
			||||||
        self.assertEqual(im2.format, "PCX")
 | 
					        self.assertEqual(im2.format, "PCX")
 | 
				
			||||||
 | 
					        self.assertEqual(im2.get_format_mimetype(), "image/x-pcx")
 | 
				
			||||||
        self.assert_image_equal(im2, im)
 | 
					        self.assert_image_equal(im2, im)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def test_sanity(self):
 | 
					    def test_sanity(self):
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -88,20 +88,13 @@ class TestFilePng(PillowTestCase):
 | 
				
			||||||
        self.assertEqual(im.format, "PNG")
 | 
					        self.assertEqual(im.format, "PNG")
 | 
				
			||||||
        self.assertEqual(im.get_format_mimetype(), 'image/png')
 | 
					        self.assertEqual(im.get_format_mimetype(), 'image/png')
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        hopper("1").save(test_file)
 | 
					        for mode in ["1", "L", "P", "RGB", "I", "I;16"]:
 | 
				
			||||||
        Image.open(test_file)
 | 
					            im = hopper(mode)
 | 
				
			||||||
 | 
					            im.save(test_file)
 | 
				
			||||||
        hopper("L").save(test_file)
 | 
					            reloaded = Image.open(test_file)
 | 
				
			||||||
        Image.open(test_file)
 | 
					            if mode == "I;16":
 | 
				
			||||||
 | 
					                reloaded = reloaded.convert(mode)
 | 
				
			||||||
        hopper("P").save(test_file)
 | 
					            self.assert_image_equal(reloaded, im)
 | 
				
			||||||
        Image.open(test_file)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        hopper("RGB").save(test_file)
 | 
					 | 
				
			||||||
        Image.open(test_file)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        hopper("I").save(test_file)
 | 
					 | 
				
			||||||
        Image.open(test_file)
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def test_invalid_file(self):
 | 
					    def test_invalid_file(self):
 | 
				
			||||||
        invalid_file = "Tests/images/flower.jpg"
 | 
					        invalid_file = "Tests/images/flower.jpg"
 | 
				
			||||||
| 
						 | 
					@ -298,30 +291,32 @@ class TestFilePng(PillowTestCase):
 | 
				
			||||||
        self.assert_image(im, "RGBA", (10, 10))
 | 
					        self.assert_image(im, "RGBA", (10, 10))
 | 
				
			||||||
        self.assertEqual(im.getcolors(), [(100, (0, 0, 0, 0))])
 | 
					        self.assertEqual(im.getcolors(), [(100, (0, 0, 0, 0))])
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def test_save_l_transparency(self):
 | 
					    def test_save_greyscale_transparency(self):
 | 
				
			||||||
        # There are 559 transparent pixels in l_trns.png.
 | 
					        for mode, num_transparent in {
 | 
				
			||||||
        num_transparent = 559
 | 
					            "1": 1994,
 | 
				
			||||||
 | 
					            "L": 559,
 | 
				
			||||||
 | 
					            "I": 559,
 | 
				
			||||||
 | 
					        }.items():
 | 
				
			||||||
 | 
					            in_file = "Tests/images/"+mode.lower()+"_trns.png"
 | 
				
			||||||
 | 
					            im = Image.open(in_file)
 | 
				
			||||||
 | 
					            self.assertEqual(im.mode, mode)
 | 
				
			||||||
 | 
					            self.assertEqual(im.info["transparency"], 255)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        in_file = "Tests/images/l_trns.png"
 | 
					            im_rgba = im.convert('RGBA')
 | 
				
			||||||
        im = Image.open(in_file)
 | 
					            self.assertEqual(
 | 
				
			||||||
        self.assertEqual(im.mode, "L")
 | 
					                im_rgba.getchannel("A").getcolors()[0][0], num_transparent)
 | 
				
			||||||
        self.assertEqual(im.info["transparency"], 255)
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
        im_rgba = im.convert('RGBA')
 | 
					            test_file = self.tempfile("temp.png")
 | 
				
			||||||
        self.assertEqual(
 | 
					            im.save(test_file)
 | 
				
			||||||
            im_rgba.getchannel("A").getcolors()[0][0], num_transparent)
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
        test_file = self.tempfile("temp.png")
 | 
					            test_im = Image.open(test_file)
 | 
				
			||||||
        im.save(test_file)
 | 
					            self.assertEqual(test_im.mode, mode)
 | 
				
			||||||
 | 
					            self.assertEqual(test_im.info["transparency"], 255)
 | 
				
			||||||
 | 
					            self.assert_image_equal(im, test_im)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        test_im = Image.open(test_file)
 | 
					            test_im_rgba = test_im.convert('RGBA')
 | 
				
			||||||
        self.assertEqual(test_im.mode, "L")
 | 
					            self.assertEqual(
 | 
				
			||||||
        self.assertEqual(test_im.info["transparency"], 255)
 | 
					                test_im_rgba.getchannel('A').getcolors()[0][0], num_transparent)
 | 
				
			||||||
        self.assert_image_equal(im, test_im)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        test_im_rgba = test_im.convert('RGBA')
 | 
					 | 
				
			||||||
        self.assertEqual(
 | 
					 | 
				
			||||||
            test_im_rgba.getchannel('A').getcolors()[0][0], num_transparent)
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def test_save_rgb_single_transparency(self):
 | 
					    def test_save_rgb_single_transparency(self):
 | 
				
			||||||
        in_file = "Tests/images/caption_6_33_22.png"
 | 
					        in_file = "Tests/images/caption_6_33_22.png"
 | 
				
			||||||
| 
						 | 
					@ -394,6 +389,24 @@ class TestFilePng(PillowTestCase):
 | 
				
			||||||
        im = roundtrip(im, dpi=(100, 100))
 | 
					        im = roundtrip(im, dpi=(100, 100))
 | 
				
			||||||
        self.assertEqual(im.info["dpi"], (100, 100))
 | 
					        self.assertEqual(im.info["dpi"], (100, 100))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def test_load_dpi_rounding(self):
 | 
				
			||||||
 | 
					        # Round up
 | 
				
			||||||
 | 
					        im = Image.open(TEST_PNG_FILE)
 | 
				
			||||||
 | 
					        self.assertEqual(im.info["dpi"], (96, 96))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        # Round down
 | 
				
			||||||
 | 
					        im = Image.open("Tests/images/icc_profile_none.png")
 | 
				
			||||||
 | 
					        self.assertEqual(im.info["dpi"], (72, 72))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def test_save_dpi_rounding(self):
 | 
				
			||||||
 | 
					        im = Image.open(TEST_PNG_FILE)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        im = roundtrip(im, dpi=(72.2, 72.2))
 | 
				
			||||||
 | 
					        self.assertEqual(im.info["dpi"], (72, 72))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        im = roundtrip(im, dpi=(72.8, 72.8))
 | 
				
			||||||
 | 
					        self.assertEqual(im.info["dpi"], (73, 73))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def test_roundtrip_text(self):
 | 
					    def test_roundtrip_text(self):
 | 
				
			||||||
        # Check text roundtripping
 | 
					        # Check text roundtripping
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1,4 +1,4 @@
 | 
				
			||||||
from .helper import PillowTestCase
 | 
					from .helper import PillowTestCase, hopper
 | 
				
			||||||
 | 
					
 | 
				
			||||||
from PIL import Image
 | 
					from PIL import Image
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -36,6 +36,16 @@ class TestFilePpm(PillowTestCase):
 | 
				
			||||||
        reloaded = Image.open(f)
 | 
					        reloaded = Image.open(f)
 | 
				
			||||||
        self.assert_image_equal(im, reloaded)
 | 
					        self.assert_image_equal(im, reloaded)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def test_pnm(self):
 | 
				
			||||||
 | 
					        im = Image.open('Tests/images/hopper.pnm')
 | 
				
			||||||
 | 
					        self.assert_image_similar(im, hopper(), 0.0001)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        f = self.tempfile('temp.pnm')
 | 
				
			||||||
 | 
					        im.save(f)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        reloaded = Image.open(f)
 | 
				
			||||||
 | 
					        self.assert_image_equal(im, reloaded)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def test_truncated_file(self):
 | 
					    def test_truncated_file(self):
 | 
				
			||||||
        path = self.tempfile('temp.pgm')
 | 
					        path = self.tempfile('temp.pgm')
 | 
				
			||||||
        with open(path, 'w') as f:
 | 
					        with open(path, 'w') as f:
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -37,6 +37,8 @@ class TestFileTga(PillowTestCase):
 | 
				
			||||||
                        path_no_ext, origin, "rle" if rle else "raw")
 | 
					                        path_no_ext, origin, "rle" if rle else "raw")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                    original_im = Image.open(tga_path)
 | 
					                    original_im = Image.open(tga_path)
 | 
				
			||||||
 | 
					                    self.assertEqual(original_im.format, "TGA")
 | 
				
			||||||
 | 
					                    self.assertEqual(original_im.get_format_mimetype(), "image/x-tga")
 | 
				
			||||||
                    if rle:
 | 
					                    if rle:
 | 
				
			||||||
                        self.assertEqual(
 | 
					                        self.assertEqual(
 | 
				
			||||||
                            original_im.info["compression"], "tga_rle")
 | 
					                            original_im.info["compression"], "tga_rle")
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -126,6 +126,30 @@ class TestFileTiff(PillowTestCase):
 | 
				
			||||||
        im._setup()
 | 
					        im._setup()
 | 
				
			||||||
        self.assertEqual(im.info['dpi'], (71., 71.))
 | 
					        self.assertEqual(im.info['dpi'], (71., 71.))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def test_load_dpi_rounding(self):
 | 
				
			||||||
 | 
					        for resolutionUnit, dpi in ((None, (72, 73)),
 | 
				
			||||||
 | 
					                                    (2, (72, 73)),
 | 
				
			||||||
 | 
					                                    (3, (183, 185))):
 | 
				
			||||||
 | 
					            im = Image.open(
 | 
				
			||||||
 | 
					                "Tests/images/hopper_roundDown_"+str(resolutionUnit)+".tif")
 | 
				
			||||||
 | 
					            self.assertEqual(im.tag_v2.get(RESOLUTION_UNIT), resolutionUnit)
 | 
				
			||||||
 | 
					            self.assertEqual(im.info['dpi'], (dpi[0], dpi[0]))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            im = Image.open("Tests/images/hopper_roundUp_"+str(resolutionUnit)+".tif")
 | 
				
			||||||
 | 
					            self.assertEqual(im.tag_v2.get(RESOLUTION_UNIT), resolutionUnit)
 | 
				
			||||||
 | 
					            self.assertEqual(im.info['dpi'], (dpi[1], dpi[1]))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def test_save_dpi_rounding(self):
 | 
				
			||||||
 | 
					        outfile = self.tempfile("temp.tif")
 | 
				
			||||||
 | 
					        im = Image.open("Tests/images/hopper.tif")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        for dpi in (72.2, 72.8):
 | 
				
			||||||
 | 
					            im.save(outfile, dpi=(dpi, dpi))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            reloaded = Image.open(outfile)
 | 
				
			||||||
 | 
					            reloaded.load()
 | 
				
			||||||
 | 
					            self.assertEqual((round(dpi), round(dpi)), reloaded.info['dpi'])
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def test_save_setting_missing_resolution(self):
 | 
					    def test_save_setting_missing_resolution(self):
 | 
				
			||||||
        b = BytesIO()
 | 
					        b = BytesIO()
 | 
				
			||||||
        Image.open("Tests/images/10ct_32bit_128.tiff").save(
 | 
					        Image.open("Tests/images/10ct_32bit_128.tiff").save(
 | 
				
			||||||
| 
						 | 
					@ -229,11 +253,6 @@ class TestFileTiff(PillowTestCase):
 | 
				
			||||||
            ['Tests/images/multipage-lastframe.tif', 1],
 | 
					            ['Tests/images/multipage-lastframe.tif', 1],
 | 
				
			||||||
            ['Tests/images/multipage.tiff', 3]
 | 
					            ['Tests/images/multipage.tiff', 3]
 | 
				
			||||||
        ]:
 | 
					        ]:
 | 
				
			||||||
            # Test is_animated before n_frames
 | 
					 | 
				
			||||||
            im = Image.open(path)
 | 
					 | 
				
			||||||
            self.assertEqual(im.is_animated, n_frames != 1)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            # Test is_animated after n_frames
 | 
					 | 
				
			||||||
            im = Image.open(path)
 | 
					            im = Image.open(path)
 | 
				
			||||||
            self.assertEqual(im.n_frames, n_frames)
 | 
					            self.assertEqual(im.n_frames, n_frames)
 | 
				
			||||||
            self.assertEqual(im.is_animated, n_frames != 1)
 | 
					            self.assertEqual(im.is_animated, n_frames != 1)
 | 
				
			||||||
| 
						 | 
					@ -263,6 +282,11 @@ class TestFileTiff(PillowTestCase):
 | 
				
			||||||
        self.assertEqual(im.size, (10, 10))
 | 
					        self.assertEqual(im.size, (10, 10))
 | 
				
			||||||
        self.assertEqual(im.convert('RGB').getpixel((0, 0)), (255, 0, 0))
 | 
					        self.assertEqual(im.convert('RGB').getpixel((0, 0)), (255, 0, 0))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        im.seek(0)
 | 
				
			||||||
 | 
					        im.load()
 | 
				
			||||||
 | 
					        self.assertEqual(im.size, (10, 10))
 | 
				
			||||||
 | 
					        self.assertEqual(im.convert('RGB').getpixel((0, 0)), (0, 128, 0))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        im.seek(2)
 | 
					        im.seek(2)
 | 
				
			||||||
        im.load()
 | 
					        im.load()
 | 
				
			||||||
        self.assertEqual(im.size, (20, 20))
 | 
					        self.assertEqual(im.size, (20, 20))
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -45,6 +45,15 @@ class TestFileWmf(PillowTestCase):
 | 
				
			||||||
        # Restore the state before this test
 | 
					        # Restore the state before this test
 | 
				
			||||||
        WmfImagePlugin.register_handler(None)
 | 
					        WmfImagePlugin.register_handler(None)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def test_load_dpi_rounding(self):
 | 
				
			||||||
 | 
					        # Round up
 | 
				
			||||||
 | 
					        im = Image.open('Tests/images/drawing.emf')
 | 
				
			||||||
 | 
					        self.assertEqual(im.info["dpi"], 1424)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        # Round down
 | 
				
			||||||
 | 
					        im = Image.open('Tests/images/drawing_roundDown.emf')
 | 
				
			||||||
 | 
					        self.assertEqual(im.info["dpi"], 1426)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def test_save(self):
 | 
					    def test_save(self):
 | 
				
			||||||
        im = hopper()
 | 
					        im = hopper()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -3,6 +3,8 @@ from .helper import unittest, PillowTestCase, hopper
 | 
				
			||||||
from PIL import Image
 | 
					from PIL import Image
 | 
				
			||||||
from PIL._util import py3
 | 
					from PIL._util import py3
 | 
				
			||||||
import os
 | 
					import os
 | 
				
			||||||
 | 
					import sys
 | 
				
			||||||
 | 
					import shutil
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class TestImage(PillowTestCase):
 | 
					class TestImage(PillowTestCase):
 | 
				
			||||||
| 
						 | 
					@ -121,6 +123,16 @@ class TestImage(PillowTestCase):
 | 
				
			||||||
        im.paste(0, (0, 0, 100, 100))
 | 
					        im.paste(0, (0, 0, 100, 100))
 | 
				
			||||||
        self.assertFalse(im.readonly)
 | 
					        self.assertFalse(im.readonly)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    @unittest.skipIf(sys.platform.startswith('win32'),
 | 
				
			||||||
 | 
					                     "Test requires opening tempfile twice")
 | 
				
			||||||
 | 
					    def test_readonly_save(self):
 | 
				
			||||||
 | 
					        temp_file = self.tempfile("temp.bmp")
 | 
				
			||||||
 | 
					        shutil.copy("Tests/images/rgb32bf-rgba.bmp", temp_file)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        im = Image.open(temp_file)
 | 
				
			||||||
 | 
					        self.assertTrue(im.readonly)
 | 
				
			||||||
 | 
					        im.save(temp_file)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def test_dump(self):
 | 
					    def test_dump(self):
 | 
				
			||||||
        im = Image.new("L", (10, 10))
 | 
					        im = Image.new("L", (10, 10))
 | 
				
			||||||
        im._dump(self.tempfile("temp_L.ppm"))
 | 
					        im._dump(self.tempfile("temp_L.ppm"))
 | 
				
			||||||
| 
						 | 
					@ -522,6 +534,16 @@ class TestImage(PillowTestCase):
 | 
				
			||||||
        _make_new(im, blank_p, ImagePalette.ImagePalette())
 | 
					        _make_new(im, blank_p, ImagePalette.ImagePalette())
 | 
				
			||||||
        _make_new(im, blank_pa, ImagePalette.ImagePalette())
 | 
					        _make_new(im, blank_pa, ImagePalette.ImagePalette())
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def test_p_from_rgb_rgba(self):
 | 
				
			||||||
 | 
					        for mode, color in [
 | 
				
			||||||
 | 
					            ("RGB", '#DDEEFF'),
 | 
				
			||||||
 | 
					            ("RGB", (221, 238, 255)),
 | 
				
			||||||
 | 
					            ("RGBA", (221, 238, 255, 255))
 | 
				
			||||||
 | 
					        ]:
 | 
				
			||||||
 | 
					            im = Image.new("P", (100, 100), color)
 | 
				
			||||||
 | 
					            expected = Image.new(mode, (100, 100), color)
 | 
				
			||||||
 | 
					            self.assert_image_equal(im.convert(mode), expected)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def test_no_resource_warning_on_save(self):
 | 
					    def test_no_resource_warning_on_save(self):
 | 
				
			||||||
        # https://github.com/python-pillow/Pillow/issues/835
 | 
					        # https://github.com/python-pillow/Pillow/issues/835
 | 
				
			||||||
        # Arrange
 | 
					        # Arrange
 | 
				
			||||||
| 
						 | 
					@ -532,6 +554,18 @@ class TestImage(PillowTestCase):
 | 
				
			||||||
        with Image.open(test_file) as im:
 | 
					        with Image.open(test_file) as im:
 | 
				
			||||||
            self.assert_warning(None, im.save, temp_file)
 | 
					            self.assert_warning(None, im.save, temp_file)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def test_load_on_nonexclusive_multiframe(self):
 | 
				
			||||||
 | 
					        with open("Tests/images/frozenpond.mpo", "rb") as fp:
 | 
				
			||||||
 | 
					            def act(fp):
 | 
				
			||||||
 | 
					                im = Image.open(fp)
 | 
				
			||||||
 | 
					                im.load()
 | 
				
			||||||
 | 
					            act(fp)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            with Image.open(fp) as im:
 | 
				
			||||||
 | 
					                im.load()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            self.assertFalse(fp.closed)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class MockEncoder(object):
 | 
					class MockEncoder(object):
 | 
				
			||||||
    pass
 | 
					    pass
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -12,7 +12,8 @@ class TestImageConvert(PillowTestCase):
 | 
				
			||||||
            self.assertEqual(out.mode, mode)
 | 
					            self.assertEqual(out.mode, mode)
 | 
				
			||||||
            self.assertEqual(out.size, im.size)
 | 
					            self.assertEqual(out.size, im.size)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        modes = "1", "L", "I", "F", "RGB", "RGBA", "RGBX", "CMYK", "YCbCr"
 | 
					        modes = ("1", "L", "LA", "P", "PA", "I", "F",
 | 
				
			||||||
 | 
					                 "RGB", "RGBA", "RGBX", "CMYK", "YCbCr")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        for mode in modes:
 | 
					        for mode in modes:
 | 
				
			||||||
            im = hopper(mode)
 | 
					            im = hopper(mode)
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -28,3 +28,10 @@ class TestImageLoad(PillowTestCase):
 | 
				
			||||||
            os.fstat(fn)
 | 
					            os.fstat(fn)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        self.assertRaises(OSError, os.fstat, fn)
 | 
					        self.assertRaises(OSError, os.fstat, fn)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def test_contextmanager_non_exclusive_fp(self):
 | 
				
			||||||
 | 
					        with open("Tests/images/hopper.gif", "rb") as fp:
 | 
				
			||||||
 | 
					            with Image.open(fp):
 | 
				
			||||||
 | 
					                pass
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            self.assertFalse(fp.closed)
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -42,7 +42,7 @@ class TestImageMode(PillowTestCase):
 | 
				
			||||||
            self.assertEqual(signature, result)
 | 
					            self.assertEqual(signature, result)
 | 
				
			||||||
        check("1", "L", "L", 1, ("1",))
 | 
					        check("1", "L", "L", 1, ("1",))
 | 
				
			||||||
        check("L", "L", "L", 1, ("L",))
 | 
					        check("L", "L", "L", 1, ("L",))
 | 
				
			||||||
        check("P", "RGB", "L", 1, ("P",))
 | 
					        check("P", "P", "L", 1, ("P",))
 | 
				
			||||||
        check("I", "L", "I", 1, ("I",))
 | 
					        check("I", "L", "I", 1, ("I",))
 | 
				
			||||||
        check("F", "L", "F", 1, ("F",))
 | 
					        check("F", "L", "F", 1, ("F",))
 | 
				
			||||||
        check("RGB", "RGB", "L", 3, ("R", "G", "B"))
 | 
					        check("RGB", "RGB", "L", 3, ("R", "G", "B"))
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -28,6 +28,13 @@ class TestImagePutAlpha(PillowTestCase):
 | 
				
			||||||
        self.assertEqual(im.mode, 'LA')
 | 
					        self.assertEqual(im.mode, 'LA')
 | 
				
			||||||
        self.assertEqual(im.getpixel((0, 0)), (1, 2))
 | 
					        self.assertEqual(im.getpixel((0, 0)), (1, 2))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        im = Image.new("P", (1, 1), 1)
 | 
				
			||||||
 | 
					        self.assertEqual(im.getpixel((0, 0)), 1)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        im.putalpha(2)
 | 
				
			||||||
 | 
					        self.assertEqual(im.mode, 'PA')
 | 
				
			||||||
 | 
					        self.assertEqual(im.getpixel((0, 0)), (1, 2))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        im = Image.new("RGB", (1, 1), (1, 2, 3))
 | 
					        im = Image.new("RGB", (1, 1), (1, 2, 3))
 | 
				
			||||||
        self.assertEqual(im.getpixel((0, 0)), (1, 2, 3))
 | 
					        self.assertEqual(im.getpixel((0, 0)), (1, 2, 3))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -14,8 +14,10 @@ class TestImagePutPalette(PillowTestCase):
 | 
				
			||||||
                return im.mode, p[:10]
 | 
					                return im.mode, p[:10]
 | 
				
			||||||
            return im.mode
 | 
					            return im.mode
 | 
				
			||||||
        self.assertRaises(ValueError, palette, "1")
 | 
					        self.assertRaises(ValueError, palette, "1")
 | 
				
			||||||
        self.assertEqual(palette("L"), ("P", [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]))
 | 
					        for mode in ["L", "LA", "P", "PA"]:
 | 
				
			||||||
        self.assertEqual(palette("P"), ("P", [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]))
 | 
					            self.assertEqual(palette(mode),
 | 
				
			||||||
 | 
					                             ("PA" if "A" in mode else "P",
 | 
				
			||||||
 | 
					                              [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]))
 | 
				
			||||||
        self.assertRaises(ValueError, palette, "I")
 | 
					        self.assertRaises(ValueError, palette, "I")
 | 
				
			||||||
        self.assertRaises(ValueError, palette, "F")
 | 
					        self.assertRaises(ValueError, palette, "F")
 | 
				
			||||||
        self.assertRaises(ValueError, palette, "RGB")
 | 
					        self.assertRaises(ValueError, palette, "RGB")
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -38,9 +38,10 @@ class TestImageQuantize(PillowTestCase):
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def test_rgba_quantize(self):
 | 
					    def test_rgba_quantize(self):
 | 
				
			||||||
        image = hopper('RGBA')
 | 
					        image = hopper('RGBA')
 | 
				
			||||||
        image.quantize()
 | 
					 | 
				
			||||||
        self.assertRaises(ValueError, image.quantize, method=0)
 | 
					        self.assertRaises(ValueError, image.quantize, method=0)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        self.assertEqual(image.quantize().convert().mode, "RGBA")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def test_quantize(self):
 | 
					    def test_quantize(self):
 | 
				
			||||||
        image = Image.open('Tests/images/caption_6_33_22.png').convert('RGB')
 | 
					        image = Image.open('Tests/images/caption_6_33_22.png').convert('RGB')
 | 
				
			||||||
        converted = image.quantize()
 | 
					        converted = image.quantize()
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -24,7 +24,7 @@ class TestImageTranspose(PillowTestCase):
 | 
				
			||||||
            self.assertEqual(im.getpixel((1, y-2)), out.getpixel((x-2, y-2)))
 | 
					            self.assertEqual(im.getpixel((1, y-2)), out.getpixel((x-2, y-2)))
 | 
				
			||||||
            self.assertEqual(im.getpixel((x-2, y-2)), out.getpixel((1, y-2)))
 | 
					            self.assertEqual(im.getpixel((x-2, y-2)), out.getpixel((1, y-2)))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        for mode in ("L", "RGB", "I;16", "I;16L", "I;16B"):
 | 
					        for mode in self.hopper:
 | 
				
			||||||
            transpose(mode)
 | 
					            transpose(mode)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def test_flip_top_bottom(self):
 | 
					    def test_flip_top_bottom(self):
 | 
				
			||||||
| 
						 | 
					@ -40,7 +40,7 @@ class TestImageTranspose(PillowTestCase):
 | 
				
			||||||
            self.assertEqual(im.getpixel((1, y-2)), out.getpixel((1, 1)))
 | 
					            self.assertEqual(im.getpixel((1, y-2)), out.getpixel((1, 1)))
 | 
				
			||||||
            self.assertEqual(im.getpixel((x-2, y-2)), out.getpixel((x-2, 1)))
 | 
					            self.assertEqual(im.getpixel((x-2, y-2)), out.getpixel((x-2, 1)))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        for mode in ("L", "RGB", "I;16", "I;16L", "I;16B"):
 | 
					        for mode in self.hopper:
 | 
				
			||||||
            transpose(mode)
 | 
					            transpose(mode)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def test_rotate_90(self):
 | 
					    def test_rotate_90(self):
 | 
				
			||||||
| 
						 | 
					@ -56,7 +56,7 @@ class TestImageTranspose(PillowTestCase):
 | 
				
			||||||
            self.assertEqual(im.getpixel((1, y-2)), out.getpixel((y-2, x-2)))
 | 
					            self.assertEqual(im.getpixel((1, y-2)), out.getpixel((y-2, x-2)))
 | 
				
			||||||
            self.assertEqual(im.getpixel((x-2, y-2)), out.getpixel((y-2, 1)))
 | 
					            self.assertEqual(im.getpixel((x-2, y-2)), out.getpixel((y-2, 1)))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        for mode in ("L", "RGB"):
 | 
					        for mode in self.hopper:
 | 
				
			||||||
            transpose(mode)
 | 
					            transpose(mode)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def test_rotate_180(self):
 | 
					    def test_rotate_180(self):
 | 
				
			||||||
| 
						 | 
					@ -72,7 +72,7 @@ class TestImageTranspose(PillowTestCase):
 | 
				
			||||||
            self.assertEqual(im.getpixel((1, y-2)), out.getpixel((x-2, 1)))
 | 
					            self.assertEqual(im.getpixel((1, y-2)), out.getpixel((x-2, 1)))
 | 
				
			||||||
            self.assertEqual(im.getpixel((x-2, y-2)), out.getpixel((1, 1)))
 | 
					            self.assertEqual(im.getpixel((x-2, y-2)), out.getpixel((1, 1)))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        for mode in ("L", "RGB", "I;16", "I;16L", "I;16B"):
 | 
					        for mode in self.hopper:
 | 
				
			||||||
            transpose(mode)
 | 
					            transpose(mode)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def test_rotate_270(self):
 | 
					    def test_rotate_270(self):
 | 
				
			||||||
| 
						 | 
					@ -88,7 +88,7 @@ class TestImageTranspose(PillowTestCase):
 | 
				
			||||||
            self.assertEqual(im.getpixel((1, y-2)), out.getpixel((1, 1)))
 | 
					            self.assertEqual(im.getpixel((1, y-2)), out.getpixel((1, 1)))
 | 
				
			||||||
            self.assertEqual(im.getpixel((x-2, y-2)), out.getpixel((1, x-2)))
 | 
					            self.assertEqual(im.getpixel((x-2, y-2)), out.getpixel((1, x-2)))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        for mode in ("L", "RGB"):
 | 
					        for mode in self.hopper:
 | 
				
			||||||
            transpose(mode)
 | 
					            transpose(mode)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def test_transpose(self):
 | 
					    def test_transpose(self):
 | 
				
			||||||
| 
						 | 
					@ -104,7 +104,7 @@ class TestImageTranspose(PillowTestCase):
 | 
				
			||||||
            self.assertEqual(im.getpixel((1, y-2)), out.getpixel((y-2, 1)))
 | 
					            self.assertEqual(im.getpixel((1, y-2)), out.getpixel((y-2, 1)))
 | 
				
			||||||
            self.assertEqual(im.getpixel((x-2, y-2)), out.getpixel((y-2, x-2)))
 | 
					            self.assertEqual(im.getpixel((x-2, y-2)), out.getpixel((y-2, x-2)))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        for mode in ("L", "RGB"):
 | 
					        for mode in self.hopper:
 | 
				
			||||||
            transpose(mode)
 | 
					            transpose(mode)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def test_tranverse(self):
 | 
					    def test_tranverse(self):
 | 
				
			||||||
| 
						 | 
					@ -120,28 +120,29 @@ class TestImageTranspose(PillowTestCase):
 | 
				
			||||||
            self.assertEqual(im.getpixel((1, y-2)), out.getpixel((1, x-2)))
 | 
					            self.assertEqual(im.getpixel((1, y-2)), out.getpixel((1, x-2)))
 | 
				
			||||||
            self.assertEqual(im.getpixel((x-2, y-2)), out.getpixel((1, 1)))
 | 
					            self.assertEqual(im.getpixel((x-2, y-2)), out.getpixel((1, 1)))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        for mode in ("L", "RGB"):
 | 
					        for mode in self.hopper:
 | 
				
			||||||
            transpose(mode)
 | 
					            transpose(mode)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def test_roundtrip(self):
 | 
					    def test_roundtrip(self):
 | 
				
			||||||
        im = self.hopper['L']
 | 
					        for mode in self.hopper:
 | 
				
			||||||
 | 
					            im = self.hopper[mode]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        def transpose(first, second):
 | 
					            def transpose(first, second):
 | 
				
			||||||
            return im.transpose(first).transpose(second)
 | 
					                return im.transpose(first).transpose(second)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        self.assert_image_equal(
 | 
					            self.assert_image_equal(
 | 
				
			||||||
            im, transpose(FLIP_LEFT_RIGHT, FLIP_LEFT_RIGHT))
 | 
					                im, transpose(FLIP_LEFT_RIGHT, FLIP_LEFT_RIGHT))
 | 
				
			||||||
        self.assert_image_equal(
 | 
					            self.assert_image_equal(
 | 
				
			||||||
            im, transpose(FLIP_TOP_BOTTOM, FLIP_TOP_BOTTOM))
 | 
					                im, transpose(FLIP_TOP_BOTTOM, FLIP_TOP_BOTTOM))
 | 
				
			||||||
        self.assert_image_equal(im, transpose(ROTATE_90, ROTATE_270))
 | 
					            self.assert_image_equal(im, transpose(ROTATE_90, ROTATE_270))
 | 
				
			||||||
        self.assert_image_equal(im, transpose(ROTATE_180, ROTATE_180))
 | 
					            self.assert_image_equal(im, transpose(ROTATE_180, ROTATE_180))
 | 
				
			||||||
        self.assert_image_equal(
 | 
					            self.assert_image_equal(
 | 
				
			||||||
            im.transpose(TRANSPOSE), transpose(ROTATE_90, FLIP_TOP_BOTTOM))
 | 
					                im.transpose(TRANSPOSE), transpose(ROTATE_90, FLIP_TOP_BOTTOM))
 | 
				
			||||||
        self.assert_image_equal(
 | 
					            self.assert_image_equal(
 | 
				
			||||||
            im.transpose(TRANSPOSE), transpose(ROTATE_270, FLIP_LEFT_RIGHT))
 | 
					                im.transpose(TRANSPOSE), transpose(ROTATE_270, FLIP_LEFT_RIGHT))
 | 
				
			||||||
        self.assert_image_equal(
 | 
					            self.assert_image_equal(
 | 
				
			||||||
            im.transpose(TRANSVERSE), transpose(ROTATE_90, FLIP_LEFT_RIGHT))
 | 
					                im.transpose(TRANSVERSE), transpose(ROTATE_90, FLIP_LEFT_RIGHT))
 | 
				
			||||||
        self.assert_image_equal(
 | 
					            self.assert_image_equal(
 | 
				
			||||||
            im.transpose(TRANSVERSE), transpose(ROTATE_270, FLIP_TOP_BOTTOM))
 | 
					                im.transpose(TRANSVERSE), transpose(ROTATE_270, FLIP_TOP_BOTTOM))
 | 
				
			||||||
        self.assert_image_equal(
 | 
					            self.assert_image_equal(
 | 
				
			||||||
            im.transpose(TRANSVERSE), transpose(ROTATE_180, TRANSPOSE))
 | 
					                im.transpose(TRANSVERSE), transpose(ROTATE_180, TRANSPOSE))
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -309,7 +309,7 @@ class TestImageCms(PillowTestCase):
 | 
				
			||||||
            2: (False, False, True),
 | 
					            2: (False, False, True),
 | 
				
			||||||
            3: (False, False, True)
 | 
					            3: (False, False, True)
 | 
				
			||||||
        })
 | 
					        })
 | 
				
			||||||
        self.assertEqual(p.color_space, 'RGB')
 | 
					
 | 
				
			||||||
        self.assertIsNone(p.colorant_table)
 | 
					        self.assertIsNone(p.colorant_table)
 | 
				
			||||||
        self.assertIsNone(p.colorant_table_out)
 | 
					        self.assertIsNone(p.colorant_table_out)
 | 
				
			||||||
        self.assertIsNone(p.colorimetric_intent)
 | 
					        self.assertIsNone(p.colorimetric_intent)
 | 
				
			||||||
| 
						 | 
					@ -361,16 +361,9 @@ class TestImageCms(PillowTestCase):
 | 
				
			||||||
            (5000.722328847392,))
 | 
					            (5000.722328847392,))
 | 
				
			||||||
        self.assertEqual(p.model,
 | 
					        self.assertEqual(p.model,
 | 
				
			||||||
                         'IEC 61966-2-1 Default RGB Colour Space - sRGB')
 | 
					                         'IEC 61966-2-1 Default RGB Colour Space - sRGB')
 | 
				
			||||||
        self.assertEqual(p.pcs, 'XYZ')
 | 
					
 | 
				
			||||||
        self.assertIsNone(p.perceptual_rendering_intent_gamut)
 | 
					        self.assertIsNone(p.perceptual_rendering_intent_gamut)
 | 
				
			||||||
        self.assertEqual(p.product_copyright,
 | 
					
 | 
				
			||||||
                         'Copyright International Color Consortium, 2009')
 | 
					 | 
				
			||||||
        self.assertEqual(p.product_desc, 'sRGB IEC61966-2-1 black scaled')
 | 
					 | 
				
			||||||
        self.assertEqual(p.product_description,
 | 
					 | 
				
			||||||
                         'sRGB IEC61966-2-1 black scaled')
 | 
					 | 
				
			||||||
        self.assertEqual(p.product_manufacturer, '')
 | 
					 | 
				
			||||||
        self.assertEqual(
 | 
					 | 
				
			||||||
            p.product_model, 'IEC 61966-2-1 Default RGB Colour Space - sRGB')
 | 
					 | 
				
			||||||
        self.assertEqual(
 | 
					        self.assertEqual(
 | 
				
			||||||
            p.profile_description, 'sRGB IEC61966-2-1 black scaled')
 | 
					            p.profile_description, 'sRGB IEC61966-2-1 black scaled')
 | 
				
			||||||
        self.assertEqual(
 | 
					        self.assertEqual(
 | 
				
			||||||
| 
						 | 
					@ -393,6 +386,40 @@ class TestImageCms(PillowTestCase):
 | 
				
			||||||
                         'Reference Viewing Condition in IEC 61966-2-1')
 | 
					                         'Reference Viewing Condition in IEC 61966-2-1')
 | 
				
			||||||
        self.assertEqual(p.xcolor_space, 'RGB ')
 | 
					        self.assertEqual(p.xcolor_space, 'RGB ')
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def test_deprecations(self):
 | 
				
			||||||
 | 
					        self.skip_missing()
 | 
				
			||||||
 | 
					        o = ImageCms.getOpenProfile(SRGB)
 | 
				
			||||||
 | 
					        p = o.profile
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        def helper_deprecated(attr, expected):
 | 
				
			||||||
 | 
					            result = self.assert_warning(DeprecationWarning, getattr, p, attr)
 | 
				
			||||||
 | 
					            self.assertEqual(result, expected)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        # p.color_space
 | 
				
			||||||
 | 
					        helper_deprecated("color_space", "RGB")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        # p.pcs
 | 
				
			||||||
 | 
					        helper_deprecated("pcs", "XYZ")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        # p.product_copyright
 | 
				
			||||||
 | 
					        helper_deprecated(
 | 
				
			||||||
 | 
					            "product_copyright", "Copyright International Color Consortium, 2009"
 | 
				
			||||||
 | 
					        )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        # p.product_desc
 | 
				
			||||||
 | 
					        helper_deprecated("product_desc", "sRGB IEC61966-2-1 black scaled")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        # p.product_description
 | 
				
			||||||
 | 
					        helper_deprecated("product_description", "sRGB IEC61966-2-1 black scaled")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        # p.product_manufacturer
 | 
				
			||||||
 | 
					        helper_deprecated("product_manufacturer", "")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        # p.product_model
 | 
				
			||||||
 | 
					        helper_deprecated(
 | 
				
			||||||
 | 
					            "product_model", "IEC 61966-2-1 Default RGB Colour Space - sRGB"
 | 
				
			||||||
 | 
					        )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def test_profile_typesafety(self):
 | 
					    def test_profile_typesafety(self):
 | 
				
			||||||
        """ Profile init type safety
 | 
					        """ Profile init type safety
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1,4 +1,4 @@
 | 
				
			||||||
from .helper import PillowTestCase, hopper, fromstring, tostring
 | 
					from .helper import unittest, PillowTestCase, hopper, fromstring, tostring
 | 
				
			||||||
 | 
					
 | 
				
			||||||
from io import BytesIO
 | 
					from io import BytesIO
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -6,6 +6,12 @@ from PIL import Image
 | 
				
			||||||
from PIL import ImageFile
 | 
					from PIL import ImageFile
 | 
				
			||||||
from PIL import EpsImagePlugin
 | 
					from PIL import EpsImagePlugin
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					try:
 | 
				
			||||||
 | 
					    from PIL import _webp
 | 
				
			||||||
 | 
					    HAVE_WEBP = True
 | 
				
			||||||
 | 
					except ImportError:
 | 
				
			||||||
 | 
					    HAVE_WEBP = False
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
codecs = dir(Image.core)
 | 
					codecs = dir(Image.core)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -233,3 +239,97 @@ class TestPyDecoder(PillowTestCase):
 | 
				
			||||||
        im = MockImageFile(buf)
 | 
					        im = MockImageFile(buf)
 | 
				
			||||||
        self.assertIsNone(im.format)
 | 
					        self.assertIsNone(im.format)
 | 
				
			||||||
        self.assertIsNone(im.get_format_mimetype())
 | 
					        self.assertIsNone(im.get_format_mimetype())
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def test_exif_jpeg(self):
 | 
				
			||||||
 | 
					        im = Image.open("Tests/images/exif-72dpi-int.jpg")  # Little endian
 | 
				
			||||||
 | 
					        exif = im.getexif()
 | 
				
			||||||
 | 
					        self.assertNotIn(258, exif)
 | 
				
			||||||
 | 
					        self.assertIn(40960, exif)
 | 
				
			||||||
 | 
					        self.assertEqual(exif[40963], 450)
 | 
				
			||||||
 | 
					        self.assertEqual(exif[11], "gThumb 3.0.1")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        out = self.tempfile('temp.jpg')
 | 
				
			||||||
 | 
					        exif[258] = 8
 | 
				
			||||||
 | 
					        del exif[40960]
 | 
				
			||||||
 | 
					        exif[40963] = 455
 | 
				
			||||||
 | 
					        exif[11] = "Pillow test"
 | 
				
			||||||
 | 
					        im.save(out, exif=exif)
 | 
				
			||||||
 | 
					        reloaded = Image.open(out)
 | 
				
			||||||
 | 
					        reloaded_exif = reloaded.getexif()
 | 
				
			||||||
 | 
					        self.assertEqual(reloaded_exif[258], 8)
 | 
				
			||||||
 | 
					        self.assertNotIn(40960, exif)
 | 
				
			||||||
 | 
					        self.assertEqual(reloaded_exif[40963], 455)
 | 
				
			||||||
 | 
					        self.assertEqual(exif[11], "Pillow test")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        im = Image.open("Tests/images/no-dpi-in-exif.jpg")  # Big endian
 | 
				
			||||||
 | 
					        exif = im.getexif()
 | 
				
			||||||
 | 
					        self.assertNotIn(258, exif)
 | 
				
			||||||
 | 
					        self.assertIn(40962, exif)
 | 
				
			||||||
 | 
					        self.assertEqual(exif[40963], 200)
 | 
				
			||||||
 | 
					        self.assertEqual(exif[305], "Adobe Photoshop CC 2017 (Macintosh)")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        out = self.tempfile('temp.jpg')
 | 
				
			||||||
 | 
					        exif[258] = 8
 | 
				
			||||||
 | 
					        del exif[34665]
 | 
				
			||||||
 | 
					        exif[40963] = 455
 | 
				
			||||||
 | 
					        exif[305] = "Pillow test"
 | 
				
			||||||
 | 
					        im.save(out, exif=exif)
 | 
				
			||||||
 | 
					        reloaded = Image.open(out)
 | 
				
			||||||
 | 
					        reloaded_exif = reloaded.getexif()
 | 
				
			||||||
 | 
					        self.assertEqual(reloaded_exif[258], 8)
 | 
				
			||||||
 | 
					        self.assertNotIn(40960, exif)
 | 
				
			||||||
 | 
					        self.assertEqual(reloaded_exif[40963], 455)
 | 
				
			||||||
 | 
					        self.assertEqual(exif[305], "Pillow test")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    @unittest.skipIf(not HAVE_WEBP or not _webp.HAVE_WEBPANIM,
 | 
				
			||||||
 | 
					                     "WebP support not installed with animation")
 | 
				
			||||||
 | 
					    def test_exif_webp(self):
 | 
				
			||||||
 | 
					        im = Image.open("Tests/images/hopper.webp")
 | 
				
			||||||
 | 
					        exif = im.getexif()
 | 
				
			||||||
 | 
					        self.assertEqual(exif, {})
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        out = self.tempfile('temp.webp')
 | 
				
			||||||
 | 
					        exif[258] = 8
 | 
				
			||||||
 | 
					        exif[40963] = 455
 | 
				
			||||||
 | 
					        exif[305] = "Pillow test"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        def check_exif():
 | 
				
			||||||
 | 
					            reloaded = Image.open(out)
 | 
				
			||||||
 | 
					            reloaded_exif = reloaded.getexif()
 | 
				
			||||||
 | 
					            self.assertEqual(reloaded_exif[258], 8)
 | 
				
			||||||
 | 
					            self.assertEqual(reloaded_exif[40963], 455)
 | 
				
			||||||
 | 
					            self.assertEqual(exif[305], "Pillow test")
 | 
				
			||||||
 | 
					        im.save(out, exif=exif)
 | 
				
			||||||
 | 
					        check_exif()
 | 
				
			||||||
 | 
					        im.save(out, exif=exif, save_all=True)
 | 
				
			||||||
 | 
					        check_exif()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def test_exif_png(self):
 | 
				
			||||||
 | 
					        im = Image.open("Tests/images/exif.png")
 | 
				
			||||||
 | 
					        exif = im.getexif()
 | 
				
			||||||
 | 
					        self.assertEqual(exif, {274: 1})
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        out = self.tempfile('temp.png')
 | 
				
			||||||
 | 
					        exif[258] = 8
 | 
				
			||||||
 | 
					        del exif[274]
 | 
				
			||||||
 | 
					        exif[40963] = 455
 | 
				
			||||||
 | 
					        exif[305] = "Pillow test"
 | 
				
			||||||
 | 
					        im.save(out, exif=exif)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        reloaded = Image.open(out)
 | 
				
			||||||
 | 
					        reloaded_exif = reloaded.getexif()
 | 
				
			||||||
 | 
					        self.assertEqual(reloaded_exif, {
 | 
				
			||||||
 | 
					            258: 8,
 | 
				
			||||||
 | 
					            40963: 455,
 | 
				
			||||||
 | 
					            305: 'Pillow test',
 | 
				
			||||||
 | 
					        })
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def test_exif_interop(self):
 | 
				
			||||||
 | 
					        im = Image.open("Tests/images/flower.jpg")
 | 
				
			||||||
 | 
					        exif = im.getexif()
 | 
				
			||||||
 | 
					        self.assertEqual(exif.get_ifd(0xa005), {
 | 
				
			||||||
 | 
					            1: 'R98',
 | 
				
			||||||
 | 
					            2: b'0100',
 | 
				
			||||||
 | 
					            4097: 2272,
 | 
				
			||||||
 | 
					            4098: 1704,
 | 
				
			||||||
 | 
					        })
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -6,6 +6,8 @@ from io import BytesIO
 | 
				
			||||||
import os
 | 
					import os
 | 
				
			||||||
import sys
 | 
					import sys
 | 
				
			||||||
import copy
 | 
					import copy
 | 
				
			||||||
 | 
					import re
 | 
				
			||||||
 | 
					import distutils.version
 | 
				
			||||||
 | 
					
 | 
				
			||||||
FONT_PATH = "Tests/fonts/FreeMono.ttf"
 | 
					FONT_PATH = "Tests/fonts/FreeMono.ttf"
 | 
				
			||||||
FONT_SIZE = 20
 | 
					FONT_SIZE = 20
 | 
				
			||||||
| 
						 | 
					@ -49,29 +51,40 @@ class TestImageFont(PillowTestCase):
 | 
				
			||||||
    # Freetype has different metrics depending on the version.
 | 
					    # Freetype has different metrics depending on the version.
 | 
				
			||||||
    # (and, other things, but first things first)
 | 
					    # (and, other things, but first things first)
 | 
				
			||||||
    METRICS = {
 | 
					    METRICS = {
 | 
				
			||||||
                ('2', '3'): {'multiline': 30,
 | 
					                ('>=2.3', '<2.4'): {
 | 
				
			||||||
                             'textsize': 12,
 | 
					                    'multiline': 30,
 | 
				
			||||||
                             'getters': (13, 16)},
 | 
					                    'textsize': 12,
 | 
				
			||||||
                ('2', '7'): {'multiline': 6.2,
 | 
					                    'getters': (13, 16)},
 | 
				
			||||||
                             'textsize': 2.5,
 | 
					                ('>=2.7',): {
 | 
				
			||||||
                             'getters': (12, 16)},
 | 
					                    'multiline': 6.2,
 | 
				
			||||||
                ('2', '8'): {'multiline': 6.2,
 | 
					                    'textsize': 2.5,
 | 
				
			||||||
                             'textsize': 2.5,
 | 
					                    'getters': (12, 16)},
 | 
				
			||||||
                             'getters': (12, 16)},
 | 
					                'Default': {
 | 
				
			||||||
                ('2', '9'): {'multiline': 6.2,
 | 
					                    'multiline': 0.5,
 | 
				
			||||||
                             'textsize': 2.5,
 | 
					                    'textsize': 0.5,
 | 
				
			||||||
                             'getters': (12, 16)},
 | 
					                    'getters': (12, 16)},
 | 
				
			||||||
                'Default': {'multiline': 0.5,
 | 
					 | 
				
			||||||
                            'textsize': 0.5,
 | 
					 | 
				
			||||||
                            'getters': (12, 16)},
 | 
					 | 
				
			||||||
                }
 | 
					                }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def setUp(self):
 | 
					    def setUp(self):
 | 
				
			||||||
        freetype_version = tuple(
 | 
					        freetype = distutils.version.StrictVersion(ImageFont.core.freetype2_version)
 | 
				
			||||||
            ImageFont.core.freetype2_version.split('.')
 | 
					
 | 
				
			||||||
        )[:2]
 | 
					        self.metrics = self.METRICS['Default']
 | 
				
			||||||
        self.metrics = self.METRICS.get(freetype_version,
 | 
					        for conditions, metrics in self.METRICS.items():
 | 
				
			||||||
                                        self.METRICS['Default'])
 | 
					            if not isinstance(conditions, tuple):
 | 
				
			||||||
 | 
					                continue
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            for condition in conditions:
 | 
				
			||||||
 | 
					                version = re.sub('[<=>]', '', condition)
 | 
				
			||||||
 | 
					                if (condition.startswith('>=') and freetype >= version) or \
 | 
				
			||||||
 | 
					                   (condition.startswith('<') and freetype < version):
 | 
				
			||||||
 | 
					                    # Condition was met
 | 
				
			||||||
 | 
					                    continue
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                # Condition failed
 | 
				
			||||||
 | 
					                break
 | 
				
			||||||
 | 
					            else:
 | 
				
			||||||
 | 
					                # All conditions were met
 | 
				
			||||||
 | 
					                self.metrics = metrics
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def get_font(self):
 | 
					    def get_font(self):
 | 
				
			||||||
        return ImageFont.truetype(FONT_PATH, FONT_SIZE,
 | 
					        return ImageFont.truetype(FONT_PATH, FONT_SIZE,
 | 
				
			||||||
| 
						 | 
					@ -525,6 +538,15 @@ class TestImageFont(PillowTestCase):
 | 
				
			||||||
        self.assertEqual(t.getsize_multiline('ABC\nA'), (36, 36))
 | 
					        self.assertEqual(t.getsize_multiline('ABC\nA'), (36, 36))
 | 
				
			||||||
        self.assertEqual(t.getsize_multiline('ABC\nAaaa'), (48, 36))
 | 
					        self.assertEqual(t.getsize_multiline('ABC\nAaaa'), (48, 36))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def test_complex_font_settings(self):
 | 
				
			||||||
 | 
					        # Arrange
 | 
				
			||||||
 | 
					        t = self.get_font()
 | 
				
			||||||
 | 
					        # Act / Assert
 | 
				
			||||||
 | 
					        if t.layout_engine == ImageFont.LAYOUT_BASIC:
 | 
				
			||||||
 | 
					            self.assertRaises(KeyError, t.getmask, 'абвг', direction='rtl')
 | 
				
			||||||
 | 
					            self.assertRaises(KeyError, t.getmask, 'абвг', features=['-kern'])
 | 
				
			||||||
 | 
					            self.assertRaises(KeyError, t.getmask, 'абвг', language='sr')
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@unittest.skipUnless(HAS_RAQM, "Raqm not Available")
 | 
					@unittest.skipUnless(HAS_RAQM, "Raqm not Available")
 | 
				
			||||||
class TestImageFont_RaqmLayout(TestImageFont):
 | 
					class TestImageFont_RaqmLayout(TestImageFont):
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -130,3 +130,16 @@ class TestImagecomplextext(PillowTestCase):
 | 
				
			||||||
        target_img = Image.open(target)
 | 
					        target_img = Image.open(target)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        self.assert_image_similar(im, target_img, .5)
 | 
					        self.assert_image_similar(im, target_img, .5)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def test_language(self):
 | 
				
			||||||
 | 
					        ttf = ImageFont.truetype(FONT_PATH, FONT_SIZE)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        im = Image.new(mode='RGB', size=(300, 100))
 | 
				
			||||||
 | 
					        draw = ImageDraw.Draw(im)
 | 
				
			||||||
 | 
					        draw.text((0, 0), 'абвг', font=ttf, fill=500,
 | 
				
			||||||
 | 
					                  language='sr')
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        target = 'Tests/images/test_language.png'
 | 
				
			||||||
 | 
					        target_img = Image.open(target)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        self.assert_image_similar(im, target_img, .5)
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1,7 +1,13 @@
 | 
				
			||||||
from .helper import PillowTestCase, hopper
 | 
					from .helper import PillowTestCase, hopper
 | 
				
			||||||
 | 
					
 | 
				
			||||||
from PIL import ImageOps
 | 
					 | 
				
			||||||
from PIL import Image
 | 
					from PIL import Image
 | 
				
			||||||
 | 
					from PIL import ImageOps
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					try:
 | 
				
			||||||
 | 
					    from PIL import _webp
 | 
				
			||||||
 | 
					    HAVE_WEBP = True
 | 
				
			||||||
 | 
					except ImportError:
 | 
				
			||||||
 | 
					    HAVE_WEBP = False
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class TestImageOps(PillowTestCase):
 | 
					class TestImageOps(PillowTestCase):
 | 
				
			||||||
| 
						 | 
					@ -62,6 +68,9 @@ class TestImageOps(PillowTestCase):
 | 
				
			||||||
        ImageOps.solarize(hopper("L"))
 | 
					        ImageOps.solarize(hopper("L"))
 | 
				
			||||||
        ImageOps.solarize(hopper("RGB"))
 | 
					        ImageOps.solarize(hopper("RGB"))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        ImageOps.exif_transpose(hopper("L"))
 | 
				
			||||||
 | 
					        ImageOps.exif_transpose(hopper("RGB"))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def test_1pxfit(self):
 | 
					    def test_1pxfit(self):
 | 
				
			||||||
        # Division by zero in equalize if image is 1 pixel high
 | 
					        # Division by zero in equalize if image is 1 pixel high
 | 
				
			||||||
        newimg = ImageOps.fit(hopper("RGB").resize((1, 1)), (35, 35))
 | 
					        newimg = ImageOps.fit(hopper("RGB").resize((1, 1)), (35, 35))
 | 
				
			||||||
| 
						 | 
					@ -218,3 +227,36 @@ class TestImageOps(PillowTestCase):
 | 
				
			||||||
                                       (0, 127, 0),
 | 
					                                       (0, 127, 0),
 | 
				
			||||||
                                       threshold=1,
 | 
					                                       threshold=1,
 | 
				
			||||||
                                       msg='white test pixel incorrect')
 | 
					                                       msg='white test pixel incorrect')
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def test_exif_transpose(self):
 | 
				
			||||||
 | 
					        exts = [".jpg"]
 | 
				
			||||||
 | 
					        if HAVE_WEBP and _webp.HAVE_WEBPANIM:
 | 
				
			||||||
 | 
					            exts.append(".webp")
 | 
				
			||||||
 | 
					        for ext in exts:
 | 
				
			||||||
 | 
					            base_im = Image.open("Tests/images/hopper"+ext)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            orientations = [base_im]
 | 
				
			||||||
 | 
					            for i in range(2, 9):
 | 
				
			||||||
 | 
					                im = Image.open("Tests/images/hopper_orientation_"+str(i)+ext)
 | 
				
			||||||
 | 
					                orientations.append(im)
 | 
				
			||||||
 | 
					            for i, orientation_im in enumerate(orientations):
 | 
				
			||||||
 | 
					                for im in [
 | 
				
			||||||
 | 
					                    orientation_im,        # ImageFile
 | 
				
			||||||
 | 
					                    orientation_im.copy()  # Image
 | 
				
			||||||
 | 
					                ]:
 | 
				
			||||||
 | 
					                    if i == 0:
 | 
				
			||||||
 | 
					                        self.assertNotIn("exif", im.info)
 | 
				
			||||||
 | 
					                    else:
 | 
				
			||||||
 | 
					                        original_exif = im.info["exif"]
 | 
				
			||||||
 | 
					                    transposed_im = ImageOps.exif_transpose(im)
 | 
				
			||||||
 | 
					                    self.assert_image_similar(base_im, transposed_im, 17)
 | 
				
			||||||
 | 
					                    if i == 0:
 | 
				
			||||||
 | 
					                        self.assertNotIn("exif", im.info)
 | 
				
			||||||
 | 
					                    else:
 | 
				
			||||||
 | 
					                        self.assertNotEqual(transposed_im.info["exif"], original_exif)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                        self.assertNotIn(0x0112, transposed_im.getexif())
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                    # Repeat the operation, to test that it does not keep transposing
 | 
				
			||||||
 | 
					                    transposed_im2 = ImageOps.exif_transpose(transposed_im)
 | 
				
			||||||
 | 
					                    self.assert_image_equal(transposed_im2, transposed_im)
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -113,12 +113,7 @@ class TestNumpy(PillowTestCase):
 | 
				
			||||||
        img = Image.fromarray(arr * 255).convert('1')
 | 
					        img = Image.fromarray(arr * 255).convert('1')
 | 
				
			||||||
        self.assertEqual(img.mode, '1')
 | 
					        self.assertEqual(img.mode, '1')
 | 
				
			||||||
        arr_back = numpy.array(img)
 | 
					        arr_back = numpy.array(img)
 | 
				
			||||||
        # numpy 1.8 and earlier return this as a boolean. (trusty/precise)
 | 
					        numpy.testing.assert_array_equal(arr, arr_back)
 | 
				
			||||||
        if arr_back.dtype == numpy.bool:
 | 
					 | 
				
			||||||
            arr_bool = numpy.array([[1, 0, 0, 1, 0], [0, 1, 0, 0, 0]], 'bool')
 | 
					 | 
				
			||||||
            numpy.testing.assert_array_equal(arr_bool, arr_back)
 | 
					 | 
				
			||||||
        else:
 | 
					 | 
				
			||||||
            numpy.testing.assert_array_equal(arr, arr_back)
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def test_save_tiff_uint16(self):
 | 
					    def test_save_tiff_uint16(self):
 | 
				
			||||||
        # Tests that we're getting the pixel value in the right byte order.
 | 
					        # Tests that we're getting the pixel value in the right byte order.
 | 
				
			||||||
| 
						 | 
					@ -143,21 +138,21 @@ class TestNumpy(PillowTestCase):
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            np_img = numpy.array(img)
 | 
					            np_img = numpy.array(img)
 | 
				
			||||||
            self._test_img_equals_nparray(img, np_img)
 | 
					            self._test_img_equals_nparray(img, np_img)
 | 
				
			||||||
            self.assertEqual(np_img.dtype, numpy.dtype(dtype))
 | 
					            self.assertEqual(np_img.dtype, dtype)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        modes = [("L", 'uint8'),
 | 
					        modes = [("L", numpy.uint8),
 | 
				
			||||||
                 ("I", 'int32'),
 | 
					                 ("I", numpy.int32),
 | 
				
			||||||
                 ("F", 'float32'),
 | 
					                 ("F", numpy.float32),
 | 
				
			||||||
                 ("LA", 'uint8'),
 | 
					                 ("LA", numpy.uint8),
 | 
				
			||||||
                 ("RGB", 'uint8'),
 | 
					                 ("RGB", numpy.uint8),
 | 
				
			||||||
                 ("RGBA", 'uint8'),
 | 
					                 ("RGBA", numpy.uint8),
 | 
				
			||||||
                 ("RGBX", 'uint8'),
 | 
					                 ("RGBX", numpy.uint8),
 | 
				
			||||||
                 ("CMYK", 'uint8'),
 | 
					                 ("CMYK", numpy.uint8),
 | 
				
			||||||
                 ("YCbCr", 'uint8'),
 | 
					                 ("YCbCr", numpy.uint8),
 | 
				
			||||||
                 ("I;16", '<u2'),
 | 
					                 ("I;16", '<u2'),
 | 
				
			||||||
                 ("I;16B", '>u2'),
 | 
					                 ("I;16B", '>u2'),
 | 
				
			||||||
                 ("I;16L", '<u2'),
 | 
					                 ("I;16L", '<u2'),
 | 
				
			||||||
                 ("HSV", 'uint8'),
 | 
					                 ("HSV", numpy.uint8),
 | 
				
			||||||
                 ]
 | 
					                 ]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        for mode in modes:
 | 
					        for mode in modes:
 | 
				
			||||||
| 
						 | 
					@ -167,7 +162,7 @@ class TestNumpy(PillowTestCase):
 | 
				
			||||||
        # see https://github.com/python-pillow/Pillow/issues/439
 | 
					        # see https://github.com/python-pillow/Pillow/issues/439
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        data = list(range(256))*3
 | 
					        data = list(range(256))*3
 | 
				
			||||||
        lut = numpy.array(data, dtype='uint8')
 | 
					        lut = numpy.array(data, dtype=numpy.uint8)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        im = hopper()
 | 
					        im = hopper()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -10,7 +10,6 @@ except ImportError:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@unittest.skipIf(pyroma is None, "Pyroma is not installed")
 | 
					@unittest.skipIf(pyroma is None, "Pyroma is not installed")
 | 
				
			||||||
class TestPyroma(PillowTestCase):
 | 
					class TestPyroma(PillowTestCase):
 | 
				
			||||||
 | 
					 | 
				
			||||||
    def test_pyroma(self):
 | 
					    def test_pyroma(self):
 | 
				
			||||||
        # Arrange
 | 
					        # Arrange
 | 
				
			||||||
        data = pyroma.projectdata.get_data(".")
 | 
					        data = pyroma.projectdata.get_data(".")
 | 
				
			||||||
| 
						 | 
					@ -19,12 +18,13 @@ class TestPyroma(PillowTestCase):
 | 
				
			||||||
        rating = pyroma.ratings.rate(data)
 | 
					        rating = pyroma.ratings.rate(data)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        # Assert
 | 
					        # Assert
 | 
				
			||||||
        if 'rc' in __version__:
 | 
					        if "rc" in __version__:
 | 
				
			||||||
            # Pyroma needs to chill about RC versions
 | 
					            # Pyroma needs to chill about RC versions and not kill all our tests.
 | 
				
			||||||
            # and not kill all our tests.
 | 
					            self.assertEqual(
 | 
				
			||||||
            self.assertEqual(rating, (9, [
 | 
					                rating,
 | 
				
			||||||
                "The package's version number does not comply with PEP-386."]))
 | 
					                (9, ["The package's version number does not comply with PEP-386."]),
 | 
				
			||||||
 | 
					            )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        else:
 | 
					        else:
 | 
				
			||||||
            # Should have a perfect score
 | 
					            # Should have a near-perfect score
 | 
				
			||||||
            self.assertEqual(rating, (10, []))
 | 
					            self.assertEqual(rating, (9, ["Your package does not have license data."]))
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -11,17 +11,6 @@ class TestFromQPixmap(PillowQPixmapTestCase, PillowTestCase):
 | 
				
			||||||
        # Qt saves all pixmaps as rgb
 | 
					        # Qt saves all pixmaps as rgb
 | 
				
			||||||
        self.assert_image_equal(result, expected.convert('RGB'))
 | 
					        self.assert_image_equal(result, expected.convert('RGB'))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def test_sanity_1(self):
 | 
					    def test_sanity(self):
 | 
				
			||||||
        self.roundtrip(hopper('1'))
 | 
					        for mode in ('1', 'RGB', 'RGBA', 'L', 'P'):
 | 
				
			||||||
 | 
					            self.roundtrip(hopper(mode))
 | 
				
			||||||
    def test_sanity_rgb(self):
 | 
					 | 
				
			||||||
        self.roundtrip(hopper('RGB'))
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    def test_sanity_rgba(self):
 | 
					 | 
				
			||||||
        self.roundtrip(hopper('RGBA'))
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    def test_sanity_l(self):
 | 
					 | 
				
			||||||
        self.roundtrip(hopper('L'))
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    def test_sanity_p(self):
 | 
					 | 
				
			||||||
        self.roundtrip(hopper('P'))
 | 
					 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -23,13 +23,13 @@ jobs:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
- template: .azure-pipelines/jobs/test-docker.yml
 | 
					- template: .azure-pipelines/jobs/test-docker.yml
 | 
				
			||||||
  parameters:
 | 
					  parameters:
 | 
				
			||||||
    docker: 'ubuntu-trusty-x86'
 | 
					    docker: 'ubuntu-16.04-xenial-amd64'
 | 
				
			||||||
    name:   'ubuntu_trusty_x86'
 | 
					    name:   'ubuntu_16_04_xenial_amd64'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
- template: .azure-pipelines/jobs/test-docker.yml
 | 
					- template: .azure-pipelines/jobs/test-docker.yml
 | 
				
			||||||
  parameters:
 | 
					  parameters:
 | 
				
			||||||
    docker: 'ubuntu-xenial-amd64'
 | 
					    docker: 'ubuntu-18.04-bionic-amd64'
 | 
				
			||||||
    name:   'ubuntu_xenial_amd64'
 | 
					    name:   'ubuntu_18_04_bionic_amd64'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
- template: .azure-pipelines/jobs/test-docker.yml
 | 
					- template: .azure-pipelines/jobs/test-docker.yml
 | 
				
			||||||
  parameters:
 | 
					  parameters:
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -79,6 +79,26 @@ PILLOW_VERSION constant
 | 
				
			||||||
``PILLOW_VERSION`` has been deprecated and will be removed in the next
 | 
					``PILLOW_VERSION`` has been deprecated and will be removed in the next
 | 
				
			||||||
major release. Use ``__version__`` instead.
 | 
					major release. Use ``__version__`` instead.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					ImageCms.CmsProfile attributes
 | 
				
			||||||
 | 
					~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.. deprecated:: 3.2.0
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Some attributes in ``ImageCms.CmsProfile`` are deprecated. From 6.0.0, they issue a
 | 
				
			||||||
 | 
					``DeprecationWarning``:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					========================  ===============================
 | 
				
			||||||
 | 
					Deprecated                Use instead
 | 
				
			||||||
 | 
					========================  ===============================
 | 
				
			||||||
 | 
					``color_space``           Padded ``xcolor_space``
 | 
				
			||||||
 | 
					``pcs``                   Padded ``connection_space``
 | 
				
			||||||
 | 
					``product_copyright``     Unicode ``copyright``
 | 
				
			||||||
 | 
					``product_desc``          Unicode ``profile_description``
 | 
				
			||||||
 | 
					``product_description``   Unicode ``profile_description``
 | 
				
			||||||
 | 
					``product_manufacturer``  Unicode ``manufacturer``
 | 
				
			||||||
 | 
					``product_model``         Unicode ``model``
 | 
				
			||||||
 | 
					========================  ===============================
 | 
				
			||||||
 | 
					
 | 
				
			||||||
Removed features
 | 
					Removed features
 | 
				
			||||||
----------------
 | 
					----------------
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -104,11 +104,11 @@ The :py:meth:`~PIL.Image.Image.open` method sets the following
 | 
				
			||||||
Reading sequences
 | 
					Reading sequences
 | 
				
			||||||
~~~~~~~~~~~~~~~~~
 | 
					~~~~~~~~~~~~~~~~~
 | 
				
			||||||
 | 
					
 | 
				
			||||||
The GIF loader supports the :py:meth:`~file.seek` and :py:meth:`~file.tell`
 | 
					The GIF loader supports the :py:meth:`~PIL.Image.Image.seek` and
 | 
				
			||||||
methods. You can seek to the next frame (``im.seek(im.tell() + 1)``), or rewind
 | 
					:py:meth:`~PIL.Image.Image.tell` methods. You can combine these methods
 | 
				
			||||||
the file by seeking to the first frame. Random access is not supported.
 | 
					to seek to the next frame (``im.seek(im.tell() + 1)``).
 | 
				
			||||||
 | 
					
 | 
				
			||||||
``im.seek()`` raises an ``EOFError`` if you try to seek after the last frame.
 | 
					``im.seek()`` raises an :py:exc:`EOFError` if you try to seek after the last frame.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
Saving
 | 
					Saving
 | 
				
			||||||
~~~~~~
 | 
					~~~~~~
 | 
				
			||||||
| 
						 | 
					@ -459,12 +459,14 @@ Pillow reads and writes PCX files containing ``1``, ``L``, ``P``, or ``RGB`` dat
 | 
				
			||||||
PNG
 | 
					PNG
 | 
				
			||||||
^^^
 | 
					^^^
 | 
				
			||||||
 | 
					
 | 
				
			||||||
Pillow identifies, reads, and writes PNG files containing ``1``, ``L``, ``P``,
 | 
					Pillow identifies, reads, and writes PNG files containing ``1``, ``L``, ``LA``,
 | 
				
			||||||
``RGB``, or ``RGBA`` data. Interlaced files are supported as of v1.1.7.
 | 
					``I``, ``P``, ``RGB`` or ``RGBA`` data. Interlaced files are supported as of
 | 
				
			||||||
 | 
					v1.1.7.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
As of Pillow 6.0, EXIF data can be read from PNG images. However, unlike other
 | 
					As of Pillow 6.0, EXIF data can be read from PNG images. However, unlike other
 | 
				
			||||||
image formats, EXIF data is not guaranteed to have been read until
 | 
					image formats, EXIF data is not guaranteed to be present in
 | 
				
			||||||
:py:meth:`~PIL.Image.Image.load` has been called.
 | 
					:py:attr:`~PIL.Image.Image.info` until :py:meth:`~PIL.Image.Image.load` has been
 | 
				
			||||||
 | 
					called.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
The :py:meth:`~PIL.Image.Image.open` method sets the following
 | 
					The :py:meth:`~PIL.Image.Image.open` method sets the following
 | 
				
			||||||
:py:attr:`~PIL.Image.Image.info` properties, when appropriate:
 | 
					:py:attr:`~PIL.Image.Image.info` properties, when appropriate:
 | 
				
			||||||
| 
						 | 
					@ -489,12 +491,12 @@ The :py:meth:`~PIL.Image.Image.open` method sets the following
 | 
				
			||||||
    For ``P`` images: Either the palette index for full transparent pixels,
 | 
					    For ``P`` images: Either the palette index for full transparent pixels,
 | 
				
			||||||
    or a byte string with alpha values for each palette entry.
 | 
					    or a byte string with alpha values for each palette entry.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    For ``L`` and ``RGB`` images, the color that represents full transparent
 | 
					    For ``1``, ``L``, ``I`` and ``RGB`` images, the color that represents
 | 
				
			||||||
    pixels in this image.
 | 
					    full transparent pixels in this image.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    This key is omitted if the image is not a transparent palette image.
 | 
					    This key is omitted if the image is not a transparent palette image.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
``Open`` also sets ``Image.text`` to a dictionary of the values of the
 | 
					``open`` also sets ``Image.text`` to a dictionary of the values of the
 | 
				
			||||||
``tEXt``, ``zTXt``, and ``iTXt`` chunks of the PNG image. Individual
 | 
					``tEXt``, ``zTXt``, and ``iTXt`` chunks of the PNG image. Individual
 | 
				
			||||||
compressed chunks are limited to a decompressed size of
 | 
					compressed chunks are limited to a decompressed size of
 | 
				
			||||||
``PngImagePlugin.MAX_TEXT_CHUNK``, by default 1MB, to prevent
 | 
					``PngImagePlugin.MAX_TEXT_CHUNK``, by default 1MB, to prevent
 | 
				
			||||||
| 
						 | 
					@ -510,8 +512,8 @@ The :py:meth:`~PIL.Image.Image.save` method supports the following options:
 | 
				
			||||||
    encoder settings.
 | 
					    encoder settings.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
**transparency**
 | 
					**transparency**
 | 
				
			||||||
    For ``P``, ``L``, and ``RGB`` images, this option controls what
 | 
					    For ``P``, ``1``, ``L``, ``I``, and ``RGB`` images, this option controls
 | 
				
			||||||
    color image to mark as transparent.
 | 
					    what color from the image to mark as transparent.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    For ``P`` images, this can be a either the palette index,
 | 
					    For ``P`` images, this can be a either the palette index,
 | 
				
			||||||
    or a byte string with alpha values for each palette entry.
 | 
					    or a byte string with alpha values for each palette entry.
 | 
				
			||||||
| 
						 | 
					@ -552,7 +554,7 @@ The :py:meth:`~PIL.Image.Image.save` method supports the following options:
 | 
				
			||||||
PPM
 | 
					PPM
 | 
				
			||||||
^^^
 | 
					^^^
 | 
				
			||||||
 | 
					
 | 
				
			||||||
Pillow reads and writes PBM, PGM and PPM files containing ``1``, ``L`` or
 | 
					Pillow reads and writes PBM, PGM, PPM and PNM files containing ``1``, ``L`` or
 | 
				
			||||||
``RGB`` data.
 | 
					``RGB`` data.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
SGI
 | 
					SGI
 | 
				
			||||||
| 
						 | 
					@ -568,7 +570,7 @@ Pillow reads and writes SPIDER image files of 32-bit floating point data
 | 
				
			||||||
("F;32F").
 | 
					("F;32F").
 | 
				
			||||||
 | 
					
 | 
				
			||||||
Pillow also reads SPIDER stack files containing sequences of SPIDER images. The
 | 
					Pillow also reads SPIDER stack files containing sequences of SPIDER images. The
 | 
				
			||||||
:py:meth:`~file.seek` and :py:meth:`~file.tell` methods are supported, and
 | 
					:py:meth:`~PIL.Image.Image.seek` and :py:meth:`~PIL.Image.Image.tell` methods are supported, and
 | 
				
			||||||
random access is allowed.
 | 
					random access is allowed.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
The :py:meth:`~PIL.Image.Image.open` method sets the following attributes:
 | 
					The :py:meth:`~PIL.Image.Image.open` method sets the following attributes:
 | 
				
			||||||
| 
						 | 
					@ -579,7 +581,7 @@ The :py:meth:`~PIL.Image.Image.open` method sets the following attributes:
 | 
				
			||||||
**istack**
 | 
					**istack**
 | 
				
			||||||
    Set to 1 if the file is an image stack, else 0.
 | 
					    Set to 1 if the file is an image stack, else 0.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
**nimages**
 | 
					**n_frames**
 | 
				
			||||||
    Set to the number of images in the stack.
 | 
					    Set to the number of images in the stack.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
A convenience method, :py:meth:`~PIL.Image.Image.convert2byte`, is provided for
 | 
					A convenience method, :py:meth:`~PIL.Image.Image.convert2byte`, is provided for
 | 
				
			||||||
| 
						 | 
					@ -663,6 +665,17 @@ numbers are returned as a tuple of ``(numerator, denominator)``.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    .. deprecated:: 3.0.0
 | 
					    .. deprecated:: 3.0.0
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Reading Multi-frame TIFF Images
 | 
				
			||||||
 | 
					~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					The TIFF loader supports the :py:meth:`~PIL.Image.Image.seek` and
 | 
				
			||||||
 | 
					:py:meth:`~PIL.Image.Image.tell` methods, taking and returning frame numbers
 | 
				
			||||||
 | 
					within the image file. You can combine these methods to seek to the next frame
 | 
				
			||||||
 | 
					(``im.seek(im.tell() + 1)``). Frames are numbered from 0 to ``im.num_frames - 1``,
 | 
				
			||||||
 | 
					and can be accessed in any order.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					``im.seek()`` raises an :py:exc:`EOFError` if you try to seek after the
 | 
				
			||||||
 | 
					last frame.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
Saving Tiff Images
 | 
					Saving Tiff Images
 | 
				
			||||||
~~~~~~~~~~~~~~~~~~
 | 
					~~~~~~~~~~~~~~~~~~
 | 
				
			||||||
| 
						 | 
					@ -850,7 +863,7 @@ is commonly used in fax applications. The DCX decoder can read files containing
 | 
				
			||||||
``1``, ``L``, ``P``, or ``RGB`` data.
 | 
					``1``, ``L``, ``P``, or ``RGB`` data.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
When the file is opened, only the first image is read. You can use
 | 
					When the file is opened, only the first image is read. You can use
 | 
				
			||||||
:py:meth:`~file.seek` or :py:mod:`~PIL.ImageSequence` to read other images.
 | 
					:py:meth:`~PIL.Image.Image.seek` or :py:mod:`~PIL.ImageSequence` to read other images.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
DDS
 | 
					DDS
 | 
				
			||||||
| 
						 | 
					@ -942,8 +955,8 @@ MIC
 | 
				
			||||||
^^^
 | 
					^^^
 | 
				
			||||||
 | 
					
 | 
				
			||||||
Pillow identifies and reads Microsoft Image Composer (MIC) files. When opened,
 | 
					Pillow identifies and reads Microsoft Image Composer (MIC) files. When opened,
 | 
				
			||||||
the first sprite in the file is loaded. You can use :py:meth:`~file.seek` and
 | 
					the first sprite in the file is loaded. You can use :py:meth:`~PIL.Image.Image.seek` and
 | 
				
			||||||
:py:meth:`~file.tell` to read other sprites from the file.
 | 
					:py:meth:`~PIL.Image.Image.tell` to read other sprites from the file.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
Note that there may be an embedded gamma of 2.2 in MIC files.
 | 
					Note that there may be an embedded gamma of 2.2 in MIC files.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -951,7 +964,7 @@ MPO
 | 
				
			||||||
^^^
 | 
					^^^
 | 
				
			||||||
 | 
					
 | 
				
			||||||
Pillow identifies and reads Multi Picture Object (MPO) files, loading the primary
 | 
					Pillow identifies and reads Multi Picture Object (MPO) files, loading the primary
 | 
				
			||||||
image when first opened. The :py:meth:`~file.seek` and :py:meth:`~file.tell`
 | 
					image when first opened. The :py:meth:`~PIL.Image.Image.seek` and :py:meth:`~PIL.Image.Image.tell`
 | 
				
			||||||
methods may be used to read other pictures from the file. The pictures are
 | 
					methods may be used to read other pictures from the file. The pictures are
 | 
				
			||||||
zero-indexed and random access is supported.
 | 
					zero-indexed and random access is supported.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -396,10 +396,6 @@ Reading sequences
 | 
				
			||||||
As seen in this example, you’ll get an :py:exc:`EOFError` exception when the
 | 
					As seen in this example, you’ll get an :py:exc:`EOFError` exception when the
 | 
				
			||||||
sequence ends.
 | 
					sequence ends.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
Note that most drivers in the current version of the library only allow you to
 | 
					 | 
				
			||||||
seek to the next frame (as in the above example). To rewind the file, you may
 | 
					 | 
				
			||||||
have to reopen it.
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
The following class lets you use the for-statement to loop over the sequence:
 | 
					The following class lets you use the for-statement to loop over the sequence:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
Using the ImageSequence Iterator class
 | 
					Using the ImageSequence Iterator class
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -30,8 +30,8 @@ Pillow is the friendly PIL fork by `Alex Clark and Contributors <https://github.
 | 
				
			||||||
   :target: https://pypi.org/project/Pillow/
 | 
					   :target: https://pypi.org/project/Pillow/
 | 
				
			||||||
   :alt: Number of PyPI downloads
 | 
					   :alt: Number of PyPI downloads
 | 
				
			||||||
 | 
					
 | 
				
			||||||
.. image:: https://coveralls.io/repos/python-pillow/Pillow/badge.svg?branch=master&service=github
 | 
					.. image:: https://codecov.io/gh/python-pillow/Pillow/branch/master/graph/badge.svg
 | 
				
			||||||
   :target: https://coveralls.io/github/python-pillow/Pillow?branch=master
 | 
					   :target: https://codecov.io/gh/python-pillow/Pillow
 | 
				
			||||||
   :alt: Code coverage
 | 
					   :alt: Code coverage
 | 
				
			||||||
 | 
					
 | 
				
			||||||
.. toctree::
 | 
					.. toctree::
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -151,7 +151,7 @@ Many of Pillow's features require external libraries:
 | 
				
			||||||
* **littlecms** provides color management
 | 
					* **littlecms** provides color management
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  * Pillow version 2.2.1 and below uses liblcms1, Pillow 2.3.0 and
 | 
					  * Pillow version 2.2.1 and below uses liblcms1, Pillow 2.3.0 and
 | 
				
			||||||
    above uses liblcms2. Tested with **1.19** and **2.7**.
 | 
					    above uses liblcms2. Tested with **1.19** and **2.7-2.9**.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
* **libwebp** provides the WebP format.
 | 
					* **libwebp** provides the WebP format.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -165,7 +165,7 @@ Many of Pillow's features require external libraries:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  * Pillow has been tested with openjpeg **2.0.0** and **2.1.0**.
 | 
					  * Pillow has been tested with openjpeg **2.0.0** and **2.1.0**.
 | 
				
			||||||
  * Pillow does **not** support the earlier **1.5** series which ships
 | 
					  * Pillow does **not** support the earlier **1.5** series which ships
 | 
				
			||||||
    with Ubuntu <= 14.04 and Debian Jessie.
 | 
					    with Debian Jessie.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
* **libimagequant** provides improved color quantization
 | 
					* **libimagequant** provides improved color quantization
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -403,10 +403,6 @@ These platforms are built and tested for every change.
 | 
				
			||||||
| Ubuntu Linux 16.04 LTS           | 2.7, 3.5, 3.6, 3.7,           |x86-64                 |
 | 
					| Ubuntu Linux 16.04 LTS           | 2.7, 3.5, 3.6, 3.7,           |x86-64                 |
 | 
				
			||||||
|                                  | PyPy, PyPy3                   |                       |
 | 
					|                                  | PyPy, PyPy3                   |                       |
 | 
				
			||||||
+----------------------------------+-------------------------------+-----------------------+
 | 
					+----------------------------------+-------------------------------+-----------------------+
 | 
				
			||||||
| Ubuntu Linux 14.04 LTS           | 2.7, 3.5, 3.6                 |x86-64                 |
 | 
					 | 
				
			||||||
|                                  +-------------------------------+-----------------------+
 | 
					 | 
				
			||||||
|                                  | 2.7                           |x86                    |
 | 
					 | 
				
			||||||
+----------------------------------+-------------------------------+-----------------------+
 | 
					 | 
				
			||||||
| Windows Server 2012 R2           | 2.7, 3.5, 3.6, 3.7            |x86, x86-64            |
 | 
					| Windows Server 2012 R2           | 2.7, 3.5, 3.6, 3.7            |x86, x86-64            |
 | 
				
			||||||
|                                  +-------------------------------+-----------------------+
 | 
					|                                  +-------------------------------+-----------------------+
 | 
				
			||||||
|                                  | PyPy, 3.7/MinGW               |x86                    |
 | 
					|                                  | PyPy, 3.7/MinGW               |x86                    |
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -132,21 +132,21 @@ can be easily displayed in a chromaticity diagram, for example).
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    .. py:attribute:: manufacturer
 | 
					    .. py:attribute:: manufacturer
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        The (english) display string for the device manufacturer (see
 | 
					        The (English) display string for the device manufacturer (see
 | 
				
			||||||
        9.2.22 of ICC.1:2010).
 | 
					        9.2.22 of ICC.1:2010).
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        :type: :py:class:`unicode` or ``None``
 | 
					        :type: :py:class:`unicode` or ``None``
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    .. py:attribute:: model
 | 
					    .. py:attribute:: model
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        The (english) display string for the device model of the device
 | 
					        The (English) display string for the device model of the device
 | 
				
			||||||
        for which this profile is created (see 9.2.23 of ICC.1:2010).
 | 
					        for which this profile is created (see 9.2.23 of ICC.1:2010).
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        :type: :py:class:`unicode` or ``None``
 | 
					        :type: :py:class:`unicode` or ``None``
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    .. py:attribute:: profile_description
 | 
					    .. py:attribute:: profile_description
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        The (english) display string for the profile description (see
 | 
					        The (English) display string for the profile description (see
 | 
				
			||||||
        9.2.41 of ICC.1:2010).
 | 
					        9.2.41 of ICC.1:2010).
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        :type: :py:class:`unicode` or ``None``
 | 
					        :type: :py:class:`unicode` or ``None``
 | 
				
			||||||
| 
						 | 
					@ -269,14 +269,14 @@ can be easily displayed in a chromaticity diagram, for example).
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    .. py:attribute:: viewing_condition
 | 
					    .. py:attribute:: viewing_condition
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        The (english) display string for the viewing conditions (see
 | 
					        The (English) display string for the viewing conditions (see
 | 
				
			||||||
        9.2.48 of ICC.1:2010).
 | 
					        9.2.48 of ICC.1:2010).
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        :type: :py:class:`unicode` or ``None``
 | 
					        :type: :py:class:`unicode` or ``None``
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    .. py:attribute:: screening_description
 | 
					    .. py:attribute:: screening_description
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        The (english) display string for the screening conditions.
 | 
					        The (English) display string for the screening conditions.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        This tag was available in ICC 3.2, but it is removed from
 | 
					        This tag was available in ICC 3.2, but it is removed from
 | 
				
			||||||
        version 4.
 | 
					        version 4.
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -255,7 +255,7 @@ Methods
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    Draw a shape.
 | 
					    Draw a shape.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
.. py:method:: PIL.ImageDraw.ImageDraw.text(xy, text, fill=None, font=None, anchor=None, spacing=0, align="left", direction=None, features=None)
 | 
					.. py:method:: PIL.ImageDraw.ImageDraw.text(xy, text, fill=None, font=None, anchor=None, spacing=0, align="left", direction=None, features=None, language=None)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    Draws the string at the given position.
 | 
					    Draws the string at the given position.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -287,7 +287,17 @@ Methods
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                     .. versionadded:: 4.2.0
 | 
					                     .. versionadded:: 4.2.0
 | 
				
			||||||
 | 
					
 | 
				
			||||||
.. py:method:: PIL.ImageDraw.ImageDraw.multiline_text(xy, text, fill=None, font=None, anchor=None, spacing=0, align="left", direction=None, features=None)
 | 
					    :param language: Language of the text. Different languages may use 
 | 
				
			||||||
 | 
					                     different glyph shapes or ligatures. This parameter tells
 | 
				
			||||||
 | 
					                     the font which language the text is in, and to apply the
 | 
				
			||||||
 | 
					                     correct substitutions as appropriate, if available.
 | 
				
			||||||
 | 
					                     It should be a `BCP47 language code
 | 
				
			||||||
 | 
					                     <https://www.w3.org/International/articles/language-tags/>`
 | 
				
			||||||
 | 
					                     Requires libraqm.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                     .. versionadded:: 6.0.0
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.. py:method:: PIL.ImageDraw.ImageDraw.multiline_text(xy, text, fill=None, font=None, anchor=None, spacing=0, align="left", direction=None, features=None, language=None)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    Draws the string at the given position.
 | 
					    Draws the string at the given position.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -316,7 +326,17 @@ Methods
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                     .. versionadded:: 4.2.0
 | 
					                     .. versionadded:: 4.2.0
 | 
				
			||||||
 | 
					
 | 
				
			||||||
.. py:method:: PIL.ImageDraw.ImageDraw.textsize(text, font=None, spacing=4, direction=None, features=None)
 | 
					    :param language: Language of the text. Different languages may use 
 | 
				
			||||||
 | 
					                     different glyph shapes or ligatures. This parameter tells
 | 
				
			||||||
 | 
					                     the font which language the text is in, and to apply the
 | 
				
			||||||
 | 
					                     correct substitutions as appropriate, if available.
 | 
				
			||||||
 | 
					                     It should be a `BCP47 language code
 | 
				
			||||||
 | 
					                     <https://www.w3.org/International/articles/language-tags/>`
 | 
				
			||||||
 | 
					                     Requires libraqm.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                     .. versionadded:: 6.0.0
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.. py:method:: PIL.ImageDraw.ImageDraw.textsize(text, font=None, spacing=4, direction=None, features=None, language=None)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    Return the size of the given string, in pixels.
 | 
					    Return the size of the given string, in pixels.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -330,7 +350,6 @@ Methods
 | 
				
			||||||
                      Requires libraqm.
 | 
					                      Requires libraqm.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                      .. versionadded:: 4.2.0
 | 
					                      .. versionadded:: 4.2.0
 | 
				
			||||||
 | 
					 | 
				
			||||||
    :param features: A list of OpenType font features to be used during text
 | 
					    :param features: A list of OpenType font features to be used during text
 | 
				
			||||||
                     layout. This is usually used to turn on optional
 | 
					                     layout. This is usually used to turn on optional
 | 
				
			||||||
                     font features that are not enabled by default,
 | 
					                     font features that are not enabled by default,
 | 
				
			||||||
| 
						 | 
					@ -343,8 +362,17 @@ Methods
 | 
				
			||||||
                     Requires libraqm.
 | 
					                     Requires libraqm.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                     .. versionadded:: 4.2.0
 | 
					                     .. versionadded:: 4.2.0
 | 
				
			||||||
 | 
					    :param language: Language of the text. Different languages may use 
 | 
				
			||||||
 | 
					                     different glyph shapes or ligatures. This parameter tells
 | 
				
			||||||
 | 
					                     the font which language the text is in, and to apply the
 | 
				
			||||||
 | 
					                     correct substitutions as appropriate, if available.
 | 
				
			||||||
 | 
					                     It should be a `BCP47 language code
 | 
				
			||||||
 | 
					                     <https://www.w3.org/International/articles/language-tags/>`
 | 
				
			||||||
 | 
					                     Requires libraqm.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
.. py:method:: PIL.ImageDraw.ImageDraw.multiline_textsize(text, font=None, spacing=4, direction=None, features=None)
 | 
					                     .. versionadded:: 6.0.0
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.. py:method:: PIL.ImageDraw.ImageDraw.multiline_textsize(text, font=None, spacing=4, direction=None, features=None, language=None)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    Return the size of the given string, in pixels.
 | 
					    Return the size of the given string, in pixels.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -370,6 +398,16 @@ Methods
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                     .. versionadded:: 4.2.0
 | 
					                     .. versionadded:: 4.2.0
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    :param language: Language of the text. Different languages may use 
 | 
				
			||||||
 | 
					                     different glyph shapes or ligatures. This parameter tells
 | 
				
			||||||
 | 
					                     the font which language the text is in, and to apply the
 | 
				
			||||||
 | 
					                     correct substitutions as appropriate, if available.
 | 
				
			||||||
 | 
					                     It should be a `BCP47 language code
 | 
				
			||||||
 | 
					                     <https://www.w3.org/International/articles/language-tags/>`
 | 
				
			||||||
 | 
					                     Requires libraqm.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                     .. versionadded:: 6.0.0
 | 
				
			||||||
 | 
					
 | 
				
			||||||
.. py:method:: PIL.ImageDraw.getdraw(im=None, hints=None)
 | 
					.. py:method:: PIL.ImageDraw.getdraw(im=None, hints=None)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    .. warning:: This method is experimental.
 | 
					    .. warning:: This method is experimental.
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -47,11 +47,45 @@ Functions
 | 
				
			||||||
Methods
 | 
					Methods
 | 
				
			||||||
-------
 | 
					-------
 | 
				
			||||||
 | 
					
 | 
				
			||||||
.. py:method:: PIL.ImageFont.ImageFont.getsize(text)
 | 
					.. py:method:: PIL.ImageFont.ImageFont.getsize(text, direction=None, features=[], language=None)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    Returns width and height (in pixels) of given text if rendered in font with
 | 
				
			||||||
 | 
					    provided direction, features, and language.
 | 
				
			||||||
 | 
					    
 | 
				
			||||||
 | 
					    :param text: Text to measure.
 | 
				
			||||||
 | 
					    
 | 
				
			||||||
 | 
					    :param direction: Direction of the text. It can be 'rtl' (right to
 | 
				
			||||||
 | 
					                      left), 'ltr' (left to right) or 'ttb' (top to bottom).
 | 
				
			||||||
 | 
					                      Requires libraqm.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                      .. versionadded:: 4.2.0
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    :param features: A list of OpenType font features to be used during text
 | 
				
			||||||
 | 
					                     layout. This is usually used to turn on optional
 | 
				
			||||||
 | 
					                     font features that are not enabled by default,
 | 
				
			||||||
 | 
					                     for example 'dlig' or 'ss01', but can be also
 | 
				
			||||||
 | 
					                     used to turn off default font features for
 | 
				
			||||||
 | 
					                     example '-liga' to disable ligatures or '-kern'
 | 
				
			||||||
 | 
					                     to disable kerning.  To get all supported
 | 
				
			||||||
 | 
					                     features, see
 | 
				
			||||||
 | 
					                     https://docs.microsoft.com/en-us/typography/opentype/spec/featurelist
 | 
				
			||||||
 | 
					                     Requires libraqm.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                     .. versionadded:: 4.2.0
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    :param language: Language of the text. Different languages may use 
 | 
				
			||||||
 | 
					                     different glyph shapes or ligatures. This parameter tells
 | 
				
			||||||
 | 
					                     the font which language the text is in, and to apply the
 | 
				
			||||||
 | 
					                     correct substitutions as appropriate, if available.
 | 
				
			||||||
 | 
					                     It should be a `BCP47 language code
 | 
				
			||||||
 | 
					                     <https://www.w3.org/International/articles/language-tags/>`
 | 
				
			||||||
 | 
					                     Requires libraqm.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                     .. versionadded:: 6.0.0
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    :return: (width, height)
 | 
					    :return: (width, height)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
.. py:method:: PIL.ImageFont.ImageFont.getmask(text, mode='', direction=None, features=[])
 | 
					.. py:method:: PIL.ImageFont.ImageFont.getmask(text, mode='', direction=None, features=[], language=None)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    Create a bitmap for the text.
 | 
					    Create a bitmap for the text.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -85,5 +119,15 @@ Methods
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                     .. versionadded:: 4.2.0
 | 
					                     .. versionadded:: 4.2.0
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    :param language: Language of the text. Different languages may use 
 | 
				
			||||||
 | 
					                     different glyph shapes or ligatures. This parameter tells
 | 
				
			||||||
 | 
					                     the font which language the text is in, and to apply the
 | 
				
			||||||
 | 
					                     correct substitutions as appropriate, if available.
 | 
				
			||||||
 | 
					                     It should be a `BCP47 language code
 | 
				
			||||||
 | 
					                     <https://www.w3.org/International/articles/language-tags/>`
 | 
				
			||||||
 | 
					                     Requires libraqm.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                     .. versionadded:: 6.0.0
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    :return: An internal PIL storage memory instance as defined by the
 | 
					    :return: An internal PIL storage memory instance as defined by the
 | 
				
			||||||
             :py:mod:`PIL.Image.core` interface module.
 | 
					             :py:mod:`PIL.Image.core` interface module.
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -3,10 +3,10 @@
 | 
				
			||||||
File Handling in Pillow
 | 
					File Handling in Pillow
 | 
				
			||||||
=======================
 | 
					=======================
 | 
				
			||||||
 | 
					
 | 
				
			||||||
When opening a file as an image, Pillow requires a filename,
 | 
					When opening a file as an image, Pillow requires a filename, ``pathlib.Path``
 | 
				
			||||||
pathlib.Path object, or a file-like object. Pillow uses the filename
 | 
					object, or a file-like object. Pillow uses the filename or ``Path`` to open a
 | 
				
			||||||
or Path to open a file, so for the rest of this article, they will all
 | 
					file, so for the rest of this article, they will all be treated as a file-like
 | 
				
			||||||
be treated as a file-like object.
 | 
					object.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
The first four of these items are equivalent, the last is dangerous
 | 
					The first four of these items are equivalent, the last is dangerous
 | 
				
			||||||
and may fail::
 | 
					and may fail::
 | 
				
			||||||
| 
						 | 
					@ -48,24 +48,23 @@ Issues
 | 
				
			||||||
Image Lifecycle
 | 
					Image Lifecycle
 | 
				
			||||||
---------------
 | 
					---------------
 | 
				
			||||||
 | 
					
 | 
				
			||||||
* ``Image.open()`` Path-like objects are opened as a file. Metadata is read
 | 
					* ``Image.open()`` Filenames and ``Path`` objects are opened as a file.
 | 
				
			||||||
  from the open file. The file is left open for further usage.
 | 
					  Metadata is read from the open file. The file is left open for further usage.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
* ``Image.Image.load()`` When the pixel data from the image is
 | 
					* ``Image.Image.load()`` When the pixel data from the image is
 | 
				
			||||||
  required, ``load()`` is called. The current frame is read into
 | 
					  required, ``load()`` is called. The current frame is read into
 | 
				
			||||||
  memory. The image can now be used independently of the underlying
 | 
					  memory. The image can now be used independently of the underlying
 | 
				
			||||||
  image file.
 | 
					  image file.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  If a filename or a path-like object was passed to ``Image.open()``, then
 | 
					  If a filename or a ``Path`` object was passed to ``Image.open()``, then the
 | 
				
			||||||
  the file object was opened by Pillow and is considered to be used exclusively
 | 
					  file object was opened by Pillow and is considered to be used exclusively by
 | 
				
			||||||
  by Pillow. So if the image is a single-frame image, the file will
 | 
					  Pillow. So if the image is a single-frame image, the file will be closed in
 | 
				
			||||||
  be closed in this method after the frame is read. If the image is a
 | 
					  this method after the frame is read. If the image is a multi-frame image,
 | 
				
			||||||
  multi-frame image, (e.g. multipage TIFF and animated GIF) the image file is
 | 
					  (e.g. multipage TIFF and animated GIF) the image file is left open so that
 | 
				
			||||||
  left open so that ``Image.Image.seek()`` can load the appropriate frame.
 | 
					  ``Image.Image.seek()`` can load the appropriate frame.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
* ``Image.Image.close()`` Closes the file pointer and destroys the
 | 
					* ``Image.Image.close()`` Closes the file and destroys the core image object.
 | 
				
			||||||
  core image object. This is used in the Pillow context manager
 | 
					  This is used in the Pillow context manager support. e.g.::
 | 
				
			||||||
  support. e.g.::
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
      with Image.open('test.jpg') as img:
 | 
					      with Image.open('test.jpg') as img:
 | 
				
			||||||
         ...  # image operations here.
 | 
					         ...  # image operations here.
 | 
				
			||||||
| 
						 | 
					@ -84,17 +83,9 @@ data until the caller has explicitly closed the image.
 | 
				
			||||||
Complications
 | 
					Complications
 | 
				
			||||||
-------------
 | 
					-------------
 | 
				
			||||||
 | 
					
 | 
				
			||||||
* TiffImagePlugin has some code to pass the underlying file descriptor
 | 
					* ``TiffImagePlugin`` has some code to pass the underlying file descriptor into
 | 
				
			||||||
  into libtiff (if working on an actual file). Since libtiff closes
 | 
					  libtiff (if working on an actual file). Since libtiff closes the file
 | 
				
			||||||
  the file descriptor internally, it is duplicated prior to passing it
 | 
					  descriptor internally, it is duplicated prior to passing it into libtiff.
 | 
				
			||||||
  into libtiff.
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
* ``decoder.handles_eof`` This slightly misnamed flag indicates that
 | 
					 | 
				
			||||||
  the decoder wants to be called with a 0 length buffer when reads are
 | 
					 | 
				
			||||||
  done. Despite the comments in ``ImageFile.load()``, the only decoder
 | 
					 | 
				
			||||||
  that actually uses this flag is the Jpeg2K decoder. The use of this
 | 
					 | 
				
			||||||
  flag in Jpeg2K predated the change to the decoder that added the
 | 
					 | 
				
			||||||
  pulls_fd flag, and is therefore not used.
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
* I don't think that there's any way to make this safe without
 | 
					* I don't think that there's any way to make this safe without
 | 
				
			||||||
  changing the lazy loading::
 | 
					  changing the lazy loading::
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -99,30 +99,114 @@ version.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
Use ``PIL.__version__`` instead.
 | 
					Use ``PIL.__version__`` instead.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					ImageCms.CmsProfile attributes
 | 
				
			||||||
 | 
					~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Some attributes in ``ImageCms.CmsProfile`` have been deprecated since Pillow 3.2.0. From
 | 
				
			||||||
 | 
					6.0.0, they issue a ``DeprecationWarning``:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					========================  ===============================
 | 
				
			||||||
 | 
					Deprecated                Use instead
 | 
				
			||||||
 | 
					========================  ===============================
 | 
				
			||||||
 | 
					``color_space``           Padded ``xcolor_space``
 | 
				
			||||||
 | 
					``pcs``                   Padded ``connection_space``
 | 
				
			||||||
 | 
					``product_copyright``     Unicode ``copyright``
 | 
				
			||||||
 | 
					``product_desc``          Unicode ``profile_description``
 | 
				
			||||||
 | 
					``product_description``   Unicode ``profile_description``
 | 
				
			||||||
 | 
					``product_manufacturer``  Unicode ``manufacturer``
 | 
				
			||||||
 | 
					``product_model``         Unicode ``model``
 | 
				
			||||||
 | 
					========================  ===============================
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					MIME type improvements
 | 
				
			||||||
 | 
					^^^^^^^^^^^^^^^^^^^^^^
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Previously, all JPEG2000 images had the MIME type "image/jpx". This has now been
 | 
				
			||||||
 | 
					corrected. After the file format drivers have been loaded, ``Image.MIME["JPEG2000"]``
 | 
				
			||||||
 | 
					will return "image/jp2". ``ImageFile.get_format_mimetype`` will return "image/jpx" if
 | 
				
			||||||
 | 
					a JPX profile is present, or "image/jp2" otherwise.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Previously, all SGI images had the MIME type "image/rgb". This has now been
 | 
				
			||||||
 | 
					corrected. After the file format drivers have been loaded, ``Image.MIME["SGI"]``
 | 
				
			||||||
 | 
					will return "image/sgi". ``ImageFile.get_format_mimetype`` will return "image/rgb" if
 | 
				
			||||||
 | 
					RGB image data is present, or "image/sgi" otherwise.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					MIME types have been added to the PPM format. After the file format drivers have been
 | 
				
			||||||
 | 
					loaded, ``Image.MIME["PPM"]`` will now return the generic "image/x-portable-anymap".
 | 
				
			||||||
 | 
					``ImageFile.get_format_mimetype`` will return a MIME type specific to the color type.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					The TGA, PCX and ICO formats also now have MIME types: "image/x-tga", "image/x-pcx" and
 | 
				
			||||||
 | 
					"image/x-icon" respectively.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
API Additions
 | 
					API Additions
 | 
				
			||||||
=============
 | 
					=============
 | 
				
			||||||
 | 
					
 | 
				
			||||||
DIB File Format
 | 
					DIB file format
 | 
				
			||||||
^^^^^^^^^^^^^^^
 | 
					^^^^^^^^^^^^^^^
 | 
				
			||||||
 | 
					
 | 
				
			||||||
Pillow now supports reading and writing the DIB "Device Independent Bitmap" file format.
 | 
					Pillow now supports reading and writing the Device Independent Bitmap file format.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
Image.quantize
 | 
					Image.quantize
 | 
				
			||||||
^^^^^^^^^^^^^^
 | 
					^^^^^^^^^^^^^^
 | 
				
			||||||
 | 
					
 | 
				
			||||||
The `dither` option is now a customisable parameter (was previously hardcoded to `1`). This parameter takes the same values used in `Image.convert`
 | 
					The ``dither`` option is now a customisable parameter (was previously hardcoded to ``1``).
 | 
				
			||||||
 | 
					This parameter takes the same values used in :py:meth:`~PIL.Image.Image.convert`.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
PNG EXIF Data
 | 
					New language parameter
 | 
				
			||||||
 | 
					^^^^^^^^^^^^^^^^^^^^^^
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					These text-rendering functions now accept a ``language`` parameter to request
 | 
				
			||||||
 | 
					language-specific glyphs and ligatures from the font:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					* ``ImageDraw.ImageDraw.multiline_text()``
 | 
				
			||||||
 | 
					* ``ImageDraw.ImageDraw.multiline_textsize()``
 | 
				
			||||||
 | 
					* ``ImageDraw.ImageDraw.text()``
 | 
				
			||||||
 | 
					* ``ImageDraw.ImageDraw.textsize()``
 | 
				
			||||||
 | 
					* ``ImageFont.ImageFont.getmask()``
 | 
				
			||||||
 | 
					* ``ImageFont.ImageFont.getsize_multiline()``
 | 
				
			||||||
 | 
					* ``ImageFont.ImageFont.getsize()``
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Added EXIF class
 | 
				
			||||||
 | 
					^^^^^^^^^^^^^^^^
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					:py:meth:`~PIL.Image.Image.getexif` has been added, which returns an
 | 
				
			||||||
 | 
					:py:class:`~PIL.Image.Exif` instance. Values can be retrieved and set like a
 | 
				
			||||||
 | 
					dictionary. When saving JPEG, PNG or WEBP, the instance can be passed as an
 | 
				
			||||||
 | 
					``exif`` argument to include any changes in the output image.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Added ImageOps.exif_transpose
 | 
				
			||||||
 | 
					^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					:py:meth:`~PIL.ImageOps.exif_transpose` returns a copy of an image, transposed
 | 
				
			||||||
 | 
					according to its EXIF Orientation tag.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					PNG EXIF data
 | 
				
			||||||
^^^^^^^^^^^^^
 | 
					^^^^^^^^^^^^^
 | 
				
			||||||
 | 
					
 | 
				
			||||||
EXIF data can now be read from and saved to PNG images. However, unlike other image
 | 
					EXIF data can now be read from and saved to PNG images. However, unlike other image
 | 
				
			||||||
formats, EXIF data is not guaranteed to have been read until
 | 
					formats, EXIF data is not guaranteed to be present in :py:attr:`~PIL.Image.Image.info`
 | 
				
			||||||
:py:meth:`~PIL.Image.Image.load` has been called.
 | 
					until :py:meth:`~PIL.Image.Image.load` has been called.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
Other Changes
 | 
					Other Changes
 | 
				
			||||||
=============
 | 
					=============
 | 
				
			||||||
 | 
					
 | 
				
			||||||
Reading new DDS image format
 | 
					Reading new DDS image format
 | 
				
			||||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~
 | 
					^^^^^^^^^^^^^^^^^^^^^^^^^^^^
 | 
				
			||||||
 | 
					
 | 
				
			||||||
Pillow can now read uncompressed RGB data from DDS images.
 | 
					Pillow can now read uncompressed RGB data from DDS images.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Reading TIFF with old-style JPEG compression
 | 
				
			||||||
 | 
					^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Added support reading TIFF files with old-style JPEG compression through LibTIFF. All
 | 
				
			||||||
 | 
					YCbCr TIFF images are now always read as RGB.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					TIFF compression codecs
 | 
				
			||||||
 | 
					^^^^^^^^^^^^^^^^^^^^^^^
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Support has been added for the LZMA, Zstd and WebP TIFF compression codecs.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Improved support for transposing I;16 images
 | 
				
			||||||
 | 
					^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					I;16, I;16L and I;16B are now supported image modes for all
 | 
				
			||||||
 | 
					:py:meth:`~PIL.Image.Image.transpose` operations.
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
							
								
								
									
										13
									
								
								setup.py
									
									
									
									
									
								
							
							
						
						| 
						 | 
					@ -765,12 +765,7 @@ try:
 | 
				
			||||||
          url='http://python-pillow.org',
 | 
					          url='http://python-pillow.org',
 | 
				
			||||||
          classifiers=[
 | 
					          classifiers=[
 | 
				
			||||||
              "Development Status :: 6 - Mature",
 | 
					              "Development Status :: 6 - Mature",
 | 
				
			||||||
              "Topic :: Multimedia :: Graphics",
 | 
					              "License :: OSI Approved :: Historical Permission Notice and Disclaimer (HPND)",  # noqa: E501
 | 
				
			||||||
              "Topic :: Multimedia :: Graphics :: Capture :: Digital Camera",
 | 
					 | 
				
			||||||
              "Topic :: Multimedia :: Graphics :: Capture :: Screen Capture",
 | 
					 | 
				
			||||||
              "Topic :: Multimedia :: Graphics :: Graphics Conversion",
 | 
					 | 
				
			||||||
              "Topic :: Multimedia :: Graphics :: Viewers",
 | 
					 | 
				
			||||||
              "License :: Other/Proprietary License",
 | 
					 | 
				
			||||||
              "Programming Language :: Python :: 2",
 | 
					              "Programming Language :: Python :: 2",
 | 
				
			||||||
              "Programming Language :: Python :: 2.7",
 | 
					              "Programming Language :: Python :: 2.7",
 | 
				
			||||||
              "Programming Language :: Python :: 3",
 | 
					              "Programming Language :: Python :: 3",
 | 
				
			||||||
| 
						 | 
					@ -779,6 +774,11 @@ try:
 | 
				
			||||||
              "Programming Language :: Python :: 3.7",
 | 
					              "Programming Language :: Python :: 3.7",
 | 
				
			||||||
              "Programming Language :: Python :: Implementation :: CPython",
 | 
					              "Programming Language :: Python :: Implementation :: CPython",
 | 
				
			||||||
              "Programming Language :: Python :: Implementation :: PyPy",
 | 
					              "Programming Language :: Python :: Implementation :: PyPy",
 | 
				
			||||||
 | 
					              "Topic :: Multimedia :: Graphics",
 | 
				
			||||||
 | 
					              "Topic :: Multimedia :: Graphics :: Capture :: Digital Camera",
 | 
				
			||||||
 | 
					              "Topic :: Multimedia :: Graphics :: Capture :: Screen Capture",
 | 
				
			||||||
 | 
					              "Topic :: Multimedia :: Graphics :: Graphics Conversion",
 | 
				
			||||||
 | 
					              "Topic :: Multimedia :: Graphics :: Viewers",
 | 
				
			||||||
          ],
 | 
					          ],
 | 
				
			||||||
          python_requires=">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*",
 | 
					          python_requires=">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*",
 | 
				
			||||||
          cmdclass={"build_ext": pil_build_ext},
 | 
					          cmdclass={"build_ext": pil_build_ext},
 | 
				
			||||||
| 
						 | 
					@ -789,7 +789,6 @@ try:
 | 
				
			||||||
          packages=["PIL"],
 | 
					          packages=["PIL"],
 | 
				
			||||||
          package_dir={'': 'src'},
 | 
					          package_dir={'': 'src'},
 | 
				
			||||||
          keywords=["Imaging", ],
 | 
					          keywords=["Imaging", ],
 | 
				
			||||||
          license='Standard PIL License',
 | 
					 | 
				
			||||||
          zip_safe=not (debug_build() or PLATFORM_MINGW), )
 | 
					          zip_safe=not (debug_build() or PLATFORM_MINGW), )
 | 
				
			||||||
except RequiredDependencyException as err:
 | 
					except RequiredDependencyException as err:
 | 
				
			||||||
    msg = """
 | 
					    msg = """
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -27,7 +27,6 @@
 | 
				
			||||||
from . import Image, ImageFile, ImagePalette
 | 
					from . import Image, ImageFile, ImagePalette
 | 
				
			||||||
from ._binary import i8, i16le as i16, i32le as i32, \
 | 
					from ._binary import i8, i16le as i16, i32le as i32, \
 | 
				
			||||||
                     o8, o16le as o16, o32le as o32
 | 
					                     o8, o16le as o16, o32le as o32
 | 
				
			||||||
import math
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
# __version__ is deprecated and will be removed in a future version. Use
 | 
					# __version__ is deprecated and will be removed in a future version. Use
 | 
				
			||||||
# PIL.__version__ instead.
 | 
					# PIL.__version__ instead.
 | 
				
			||||||
| 
						 | 
					@ -105,53 +104,51 @@ class BmpImageFile(ImageFile.ImageFile):
 | 
				
			||||||
        # --------------------------------------------- Windows Bitmap v2 to v5
 | 
					        # --------------------------------------------- Windows Bitmap v2 to v5
 | 
				
			||||||
        # v3, OS/2 v2, v4, v5
 | 
					        # v3, OS/2 v2, v4, v5
 | 
				
			||||||
        elif file_info['header_size'] in (40, 64, 108, 124):
 | 
					        elif file_info['header_size'] in (40, 64, 108, 124):
 | 
				
			||||||
            if file_info['header_size'] >= 40:  # v3 and OS/2
 | 
					            file_info['y_flip'] = i8(header_data[7]) == 0xff
 | 
				
			||||||
                file_info['y_flip'] = i8(header_data[7]) == 0xff
 | 
					            file_info['direction'] = 1 if file_info['y_flip'] else -1
 | 
				
			||||||
                file_info['direction'] = 1 if file_info['y_flip'] else -1
 | 
					            file_info['width'] = i32(header_data[0:4])
 | 
				
			||||||
                file_info['width'] = i32(header_data[0:4])
 | 
					            file_info['height'] = (i32(header_data[4:8])
 | 
				
			||||||
                file_info['height'] = (i32(header_data[4:8])
 | 
					                                   if not file_info['y_flip']
 | 
				
			||||||
                                       if not file_info['y_flip']
 | 
					                                   else 2**32 - i32(header_data[4:8]))
 | 
				
			||||||
                                       else 2**32 - i32(header_data[4:8]))
 | 
					            file_info['planes'] = i16(header_data[8:10])
 | 
				
			||||||
                file_info['planes'] = i16(header_data[8:10])
 | 
					            file_info['bits'] = i16(header_data[10:12])
 | 
				
			||||||
                file_info['bits'] = i16(header_data[10:12])
 | 
					            file_info['compression'] = i32(header_data[12:16])
 | 
				
			||||||
                file_info['compression'] = i32(header_data[12:16])
 | 
					            # byte size of pixel data
 | 
				
			||||||
                # byte size of pixel data
 | 
					            file_info['data_size'] = i32(header_data[16:20])
 | 
				
			||||||
                file_info['data_size'] = i32(header_data[16:20])
 | 
					            file_info['pixels_per_meter'] = (i32(header_data[20:24]),
 | 
				
			||||||
                file_info['pixels_per_meter'] = (i32(header_data[20:24]),
 | 
					                                             i32(header_data[24:28]))
 | 
				
			||||||
                                                 i32(header_data[24:28]))
 | 
					            file_info['colors'] = i32(header_data[28:32])
 | 
				
			||||||
                file_info['colors'] = i32(header_data[28:32])
 | 
					            file_info['palette_padding'] = 4
 | 
				
			||||||
                file_info['palette_padding'] = 4
 | 
					            self.info["dpi"] = tuple(
 | 
				
			||||||
                self.info["dpi"] = tuple(
 | 
					                int(x / 39.3701 + 0.5) for x in file_info['pixels_per_meter'])
 | 
				
			||||||
                    map(lambda x: int(math.ceil(x / 39.3701)),
 | 
					            if file_info['compression'] == self.BITFIELDS:
 | 
				
			||||||
                        file_info['pixels_per_meter']))
 | 
					                if len(header_data) >= 52:
 | 
				
			||||||
                if file_info['compression'] == self.BITFIELDS:
 | 
					                    for idx, mask in enumerate(['r_mask',
 | 
				
			||||||
                    if len(header_data) >= 52:
 | 
					                                                'g_mask',
 | 
				
			||||||
                        for idx, mask in enumerate(['r_mask',
 | 
					                                                'b_mask',
 | 
				
			||||||
                                                    'g_mask',
 | 
					                                                'a_mask']):
 | 
				
			||||||
                                                    'b_mask',
 | 
					                        file_info[mask] = i32(
 | 
				
			||||||
                                                    'a_mask']):
 | 
					                            header_data[36 + idx * 4:40 + idx * 4]
 | 
				
			||||||
                            file_info[mask] = i32(
 | 
					                        )
 | 
				
			||||||
                                header_data[36 + idx * 4:40 + idx * 4]
 | 
					                else:
 | 
				
			||||||
                            )
 | 
					                    # 40 byte headers only have the three components in the
 | 
				
			||||||
                    else:
 | 
					                    # bitfields masks, ref:
 | 
				
			||||||
                        # 40 byte headers only have the three components in the
 | 
					                    # https://msdn.microsoft.com/en-us/library/windows/desktop/dd183376(v=vs.85).aspx
 | 
				
			||||||
                        # bitfields masks, ref:
 | 
					                    # See also
 | 
				
			||||||
                        # https://msdn.microsoft.com/en-us/library/windows/desktop/dd183376(v=vs.85).aspx
 | 
					                    # https://github.com/python-pillow/Pillow/issues/1293
 | 
				
			||||||
                        # See also
 | 
					                    # There is a 4th component in the RGBQuad, in the alpha
 | 
				
			||||||
                        # https://github.com/python-pillow/Pillow/issues/1293
 | 
					                    # location, but it is listed as a reserved component,
 | 
				
			||||||
                        # There is a 4th component in the RGBQuad, in the alpha
 | 
					                    # and it is not generally an alpha channel
 | 
				
			||||||
                        # location, but it is listed as a reserved component,
 | 
					                    file_info['a_mask'] = 0x0
 | 
				
			||||||
                        # and it is not generally an alpha channel
 | 
					                    for mask in ['r_mask', 'g_mask', 'b_mask']:
 | 
				
			||||||
                        file_info['a_mask'] = 0x0
 | 
					                        file_info[mask] = i32(read(4))
 | 
				
			||||||
                        for mask in ['r_mask', 'g_mask', 'b_mask']:
 | 
					                file_info['rgb_mask'] = (file_info['r_mask'],
 | 
				
			||||||
                            file_info[mask] = i32(read(4))
 | 
					                                         file_info['g_mask'],
 | 
				
			||||||
                    file_info['rgb_mask'] = (file_info['r_mask'],
 | 
					                                         file_info['b_mask'])
 | 
				
			||||||
                                             file_info['g_mask'],
 | 
					                file_info['rgba_mask'] = (file_info['r_mask'],
 | 
				
			||||||
                                             file_info['b_mask'])
 | 
					                                          file_info['g_mask'],
 | 
				
			||||||
                    file_info['rgba_mask'] = (file_info['r_mask'],
 | 
					                                          file_info['b_mask'],
 | 
				
			||||||
                                              file_info['g_mask'],
 | 
					                                          file_info['a_mask'])
 | 
				
			||||||
                                              file_info['b_mask'],
 | 
					 | 
				
			||||||
                                              file_info['a_mask'])
 | 
					 | 
				
			||||||
        else:
 | 
					        else:
 | 
				
			||||||
            raise IOError("Unsupported BMP header type (%d)" %
 | 
					            raise IOError("Unsupported BMP header type (%d)" %
 | 
				
			||||||
                          file_info['header_size'])
 | 
					                          file_info['header_size'])
 | 
				
			||||||
| 
						 | 
					@ -180,6 +177,7 @@ class BmpImageFile(ImageFile.ImageFile):
 | 
				
			||||||
            SUPPORTED = {
 | 
					            SUPPORTED = {
 | 
				
			||||||
                32: [(0xff0000, 0xff00, 0xff, 0x0),
 | 
					                32: [(0xff0000, 0xff00, 0xff, 0x0),
 | 
				
			||||||
                     (0xff0000, 0xff00, 0xff, 0xff000000),
 | 
					                     (0xff0000, 0xff00, 0xff, 0xff000000),
 | 
				
			||||||
 | 
					                     (0xff, 0xff00, 0xff0000, 0xff000000),
 | 
				
			||||||
                     (0x0, 0x0, 0x0, 0x0),
 | 
					                     (0x0, 0x0, 0x0, 0x0),
 | 
				
			||||||
                     (0xff000000, 0xff0000, 0xff00, 0x0)],
 | 
					                     (0xff000000, 0xff0000, 0xff00, 0x0)],
 | 
				
			||||||
                24: [(0xff0000, 0xff00, 0xff)],
 | 
					                24: [(0xff0000, 0xff00, 0xff)],
 | 
				
			||||||
| 
						 | 
					@ -188,6 +186,7 @@ class BmpImageFile(ImageFile.ImageFile):
 | 
				
			||||||
            MASK_MODES = {
 | 
					            MASK_MODES = {
 | 
				
			||||||
                (32, (0xff0000, 0xff00, 0xff, 0x0)): "BGRX",
 | 
					                (32, (0xff0000, 0xff00, 0xff, 0x0)): "BGRX",
 | 
				
			||||||
                (32, (0xff000000, 0xff0000, 0xff00, 0x0)): "XBGR",
 | 
					                (32, (0xff000000, 0xff0000, 0xff00, 0x0)): "XBGR",
 | 
				
			||||||
 | 
					                (32, (0xff, 0xff00, 0xff0000, 0xff000000)): "RGBA",
 | 
				
			||||||
                (32, (0xff0000, 0xff00, 0xff, 0xff000000)): "BGRA",
 | 
					                (32, (0xff0000, 0xff00, 0xff, 0xff000000)): "BGRA",
 | 
				
			||||||
                (32, (0x0, 0x0, 0x0, 0x0)): "BGRA",
 | 
					                (32, (0x0, 0x0, 0x0, 0x0)): "BGRA",
 | 
				
			||||||
                (24, (0xff0000, 0xff00, 0xff)): "BGR",
 | 
					                (24, (0xff0000, 0xff00, 0xff)): "BGR",
 | 
				
			||||||
| 
						 | 
					@ -200,7 +199,7 @@ class BmpImageFile(ImageFile.ImageFile):
 | 
				
			||||||
                    raw_mode = MASK_MODES[
 | 
					                    raw_mode = MASK_MODES[
 | 
				
			||||||
                        (file_info["bits"], file_info["rgba_mask"])
 | 
					                        (file_info["bits"], file_info["rgba_mask"])
 | 
				
			||||||
                    ]
 | 
					                    ]
 | 
				
			||||||
                    self.mode = "RGBA" if raw_mode in ("BGRA",) else self.mode
 | 
					                    self.mode = "RGBA" if "A" in raw_mode else self.mode
 | 
				
			||||||
                elif (file_info['bits'] in (24, 16) and
 | 
					                elif (file_info['bits'] in (24, 16) and
 | 
				
			||||||
                      file_info['rgb_mask'] in SUPPORTED[file_info['bits']]):
 | 
					                      file_info['rgb_mask'] in SUPPORTED[file_info['bits']]):
 | 
				
			||||||
                    raw_mode = MASK_MODES[
 | 
					                    raw_mode = MASK_MODES[
 | 
				
			||||||
| 
						 | 
					@ -310,7 +309,7 @@ def _save(im, fp, filename, bitmap_header=True):
 | 
				
			||||||
    dpi = info.get("dpi", (96, 96))
 | 
					    dpi = info.get("dpi", (96, 96))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    # 1 meter == 39.3701 inches
 | 
					    # 1 meter == 39.3701 inches
 | 
				
			||||||
    ppm = tuple(map(lambda x: int(x * 39.3701), dpi))
 | 
					    ppm = tuple(map(lambda x: int(x * 39.3701 + 0.5), dpi))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    stride = ((im.size[0]*bits+7)//8+3) & (~3)
 | 
					    stride = ((im.size[0]*bits+7)//8+3) & (~3)
 | 
				
			||||||
    header = 40  # or 64 for OS/2 version 2
 | 
					    header = 40  # or 64 for OS/2 version 2
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -141,13 +141,11 @@ def Ghostscript(tile, size, fp, scale=1):
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    # push data through Ghostscript
 | 
					    # push data through Ghostscript
 | 
				
			||||||
    try:
 | 
					    try:
 | 
				
			||||||
        with open(os.devnull, 'w+b') as devnull:
 | 
					        startupinfo = None
 | 
				
			||||||
            startupinfo = None
 | 
					        if sys.platform.startswith('win'):
 | 
				
			||||||
            if sys.platform.startswith('win'):
 | 
					            startupinfo = subprocess.STARTUPINFO()
 | 
				
			||||||
                startupinfo = subprocess.STARTUPINFO()
 | 
					            startupinfo.dwFlags |= subprocess.STARTF_USESHOWWINDOW
 | 
				
			||||||
                startupinfo.dwFlags |= subprocess.STARTF_USESHOWWINDOW
 | 
					        subprocess.check_call(command, startupinfo=startupinfo)
 | 
				
			||||||
            subprocess.check_call(command, stdout=devnull,
 | 
					 | 
				
			||||||
                                  startupinfo=startupinfo)
 | 
					 | 
				
			||||||
        im = Image.open(outfile)
 | 
					        im = Image.open(outfile)
 | 
				
			||||||
        im.load()
 | 
					        im.load()
 | 
				
			||||||
    finally:
 | 
					    finally:
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -122,6 +122,8 @@ class GifImageFile(ImageFile.ImageFile):
 | 
				
			||||||
        if not self._seek_check(frame):
 | 
					        if not self._seek_check(frame):
 | 
				
			||||||
            return
 | 
					            return
 | 
				
			||||||
        if frame < self.__frame:
 | 
					        if frame < self.__frame:
 | 
				
			||||||
 | 
					            if frame != 0:
 | 
				
			||||||
 | 
					                self.im = None
 | 
				
			||||||
            self._seek(0)
 | 
					            self._seek(0)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        last_frame = self.__frame
 | 
					        last_frame = self.__frame
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -32,14 +32,12 @@ class GimpPaletteFile(object):
 | 
				
			||||||
        if fp.readline()[:12] != b"GIMP Palette":
 | 
					        if fp.readline()[:12] != b"GIMP Palette":
 | 
				
			||||||
            raise SyntaxError("not a GIMP palette file")
 | 
					            raise SyntaxError("not a GIMP palette file")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        i = 0
 | 
					        for i in range(256):
 | 
				
			||||||
 | 
					 | 
				
			||||||
        while i <= 255:
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
            s = fp.readline()
 | 
					            s = fp.readline()
 | 
				
			||||||
 | 
					 | 
				
			||||||
            if not s:
 | 
					            if not s:
 | 
				
			||||||
                break
 | 
					                break
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            # skip fields and comment lines
 | 
					            # skip fields and comment lines
 | 
				
			||||||
            if re.match(br"\w+:|#", s):
 | 
					            if re.match(br"\w+:|#", s):
 | 
				
			||||||
                continue
 | 
					                continue
 | 
				
			||||||
| 
						 | 
					@ -50,10 +48,7 @@ class GimpPaletteFile(object):
 | 
				
			||||||
            if len(v) != 3:
 | 
					            if len(v) != 3:
 | 
				
			||||||
                raise ValueError("bad palette entry")
 | 
					                raise ValueError("bad palette entry")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            if 0 <= i <= 255:
 | 
					            self.palette[i] = o8(v[0]) + o8(v[1]) + o8(v[2])
 | 
				
			||||||
                self.palette[i] = o8(v[0]) + o8(v[1]) + o8(v[2])
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            i += 1
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
        self.palette = b"".join(self.palette)
 | 
					        self.palette = b"".join(self.palette)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -295,3 +295,5 @@ class IcoImageFile(ImageFile.ImageFile):
 | 
				
			||||||
Image.register_open(IcoImageFile.format, IcoImageFile, _accept)
 | 
					Image.register_open(IcoImageFile.format, IcoImageFile, _accept)
 | 
				
			||||||
Image.register_save(IcoImageFile.format, _save)
 | 
					Image.register_save(IcoImageFile.format, _save)
 | 
				
			||||||
Image.register_extension(IcoImageFile.format, ".ico")
 | 
					Image.register_extension(IcoImageFile.format, ".ico")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Image.register_mime(IcoImageFile.format, "image/x-icon")
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
							
								
								
									
										253
									
								
								src/PIL/Image.py
									
									
									
									
									
								
							
							
						
						| 
						 | 
					@ -40,8 +40,8 @@ except ImportError:
 | 
				
			||||||
    import __builtin__
 | 
					    import __builtin__
 | 
				
			||||||
    builtins = __builtin__
 | 
					    builtins = __builtin__
 | 
				
			||||||
 | 
					
 | 
				
			||||||
from . import ImageMode
 | 
					from . import ImageMode, TiffTags
 | 
				
			||||||
from ._binary import i8
 | 
					from ._binary import i8, i32le
 | 
				
			||||||
from ._util import isPath, isStringType, deferred_error
 | 
					from ._util import isPath, isStringType, deferred_error
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import os
 | 
					import os
 | 
				
			||||||
| 
						 | 
					@ -54,10 +54,10 @@ import atexit
 | 
				
			||||||
import numbers
 | 
					import numbers
 | 
				
			||||||
try:
 | 
					try:
 | 
				
			||||||
    # Python 3
 | 
					    # Python 3
 | 
				
			||||||
    from collections.abc import Callable
 | 
					    from collections.abc import Callable, MutableMapping
 | 
				
			||||||
except ImportError:
 | 
					except ImportError:
 | 
				
			||||||
    # Python 2.7
 | 
					    # Python 2.7
 | 
				
			||||||
    from collections import Callable
 | 
					    from collections import Callable, MutableMapping
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
# Silence warning
 | 
					# Silence warning
 | 
				
			||||||
| 
						 | 
					@ -247,7 +247,7 @@ _MODEINFO = {
 | 
				
			||||||
    "L": ("L", "L", ("L",)),
 | 
					    "L": ("L", "L", ("L",)),
 | 
				
			||||||
    "I": ("L", "I", ("I",)),
 | 
					    "I": ("L", "I", ("I",)),
 | 
				
			||||||
    "F": ("L", "F", ("F",)),
 | 
					    "F": ("L", "F", ("F",)),
 | 
				
			||||||
    "P": ("RGB", "L", ("P",)),
 | 
					    "P": ("P", "L", ("P",)),
 | 
				
			||||||
    "RGB": ("RGB", "L", ("R", "G", "B")),
 | 
					    "RGB": ("RGB", "L", ("R", "G", "B")),
 | 
				
			||||||
    "RGBX": ("RGB", "L", ("R", "G", "B", "X")),
 | 
					    "RGBX": ("RGB", "L", ("R", "G", "B", "X")),
 | 
				
			||||||
    "RGBA": ("RGB", "L", ("R", "G", "B", "A")),
 | 
					    "RGBA": ("RGB", "L", ("R", "G", "B", "A")),
 | 
				
			||||||
| 
						 | 
					@ -578,7 +578,12 @@ class Image(object):
 | 
				
			||||||
        return self
 | 
					        return self
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def __exit__(self, *args):
 | 
					    def __exit__(self, *args):
 | 
				
			||||||
        self.close()
 | 
					        if hasattr(self, 'fp') and getattr(self, '_exclusive_fp', False):
 | 
				
			||||||
 | 
					            if hasattr(self, "_close__fp"):
 | 
				
			||||||
 | 
					                self._close__fp()
 | 
				
			||||||
 | 
					            if self.fp:
 | 
				
			||||||
 | 
					                self.fp.close()
 | 
				
			||||||
 | 
					        self.fp = None
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def close(self):
 | 
					    def close(self):
 | 
				
			||||||
        """
 | 
					        """
 | 
				
			||||||
| 
						 | 
					@ -610,12 +615,7 @@ class Image(object):
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    if sys.version_info.major >= 3:
 | 
					    if sys.version_info.major >= 3:
 | 
				
			||||||
        def __del__(self):
 | 
					        def __del__(self):
 | 
				
			||||||
            if hasattr(self, "_close__fp"):
 | 
					            self.__exit__()
 | 
				
			||||||
                self._close__fp()
 | 
					 | 
				
			||||||
            if (hasattr(self, 'fp') and hasattr(self, '_exclusive_fp')
 | 
					 | 
				
			||||||
               and self.fp and self._exclusive_fp):
 | 
					 | 
				
			||||||
                self.fp.close()
 | 
					 | 
				
			||||||
            self.fp = None
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def _copy(self):
 | 
					    def _copy(self):
 | 
				
			||||||
        self.load()
 | 
					        self.load()
 | 
				
			||||||
| 
						 | 
					@ -950,7 +950,7 @@ class Image(object):
 | 
				
			||||||
        delete_trns = False
 | 
					        delete_trns = False
 | 
				
			||||||
        # transparency handling
 | 
					        # transparency handling
 | 
				
			||||||
        if has_transparency:
 | 
					        if has_transparency:
 | 
				
			||||||
            if self.mode in ('L', 'RGB') and mode == 'RGBA':
 | 
					            if self.mode in ('1', 'L', 'I', 'RGB') and mode == 'RGBA':
 | 
				
			||||||
                # Use transparent conversion to promote from transparent
 | 
					                # Use transparent conversion to promote from transparent
 | 
				
			||||||
                # color to an alpha channel.
 | 
					                # color to an alpha channel.
 | 
				
			||||||
                new_im = self._new(self.im.convert_transparent(
 | 
					                new_im = self._new(self.im.convert_transparent(
 | 
				
			||||||
| 
						 | 
					@ -1096,7 +1096,13 @@ class Image(object):
 | 
				
			||||||
            im = self.im.convert("P", dither, palette.im)
 | 
					            im = self.im.convert("P", dither, palette.im)
 | 
				
			||||||
            return self._new(im)
 | 
					            return self._new(im)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        return self._new(self.im.quantize(colors, method, kmeans))
 | 
					        im = self._new(self.im.quantize(colors, method, kmeans))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        from . import ImagePalette
 | 
				
			||||||
 | 
					        mode = im.im.getpalettemode()
 | 
				
			||||||
 | 
					        im.palette = ImagePalette.ImagePalette(mode, im.im.getpalette(mode, mode))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        return im
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def copy(self):
 | 
					    def copy(self):
 | 
				
			||||||
        """
 | 
					        """
 | 
				
			||||||
| 
						 | 
					@ -1291,6 +1297,12 @@ class Image(object):
 | 
				
			||||||
            return tuple(extrema)
 | 
					            return tuple(extrema)
 | 
				
			||||||
        return self.im.getextrema()
 | 
					        return self.im.getextrema()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def getexif(self):
 | 
				
			||||||
 | 
					        exif = Exif()
 | 
				
			||||||
 | 
					        if "exif" in self.info:
 | 
				
			||||||
 | 
					            exif.load(self.info["exif"])
 | 
				
			||||||
 | 
					        return exif
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    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.
 | 
				
			||||||
| 
						 | 
					@ -1559,7 +1571,7 @@ class Image(object):
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        self._ensure_mutable()
 | 
					        self._ensure_mutable()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        if self.mode not in ("LA", "RGBA"):
 | 
					        if self.mode not in ("LA", "PA", "RGBA"):
 | 
				
			||||||
            # attempt to promote self to a matching alpha mode
 | 
					            # attempt to promote self to a matching alpha mode
 | 
				
			||||||
            try:
 | 
					            try:
 | 
				
			||||||
                mode = getmodebase(self.mode) + "A"
 | 
					                mode = getmodebase(self.mode) + "A"
 | 
				
			||||||
| 
						 | 
					@ -1568,7 +1580,7 @@ class Image(object):
 | 
				
			||||||
                except (AttributeError, ValueError):
 | 
					                except (AttributeError, ValueError):
 | 
				
			||||||
                    # do things the hard way
 | 
					                    # do things the hard way
 | 
				
			||||||
                    im = self.im.convert(mode)
 | 
					                    im = self.im.convert(mode)
 | 
				
			||||||
                    if im.mode not in ("LA", "RGBA"):
 | 
					                    if im.mode not in ("LA", "PA", "RGBA"):
 | 
				
			||||||
                        raise ValueError  # sanity check
 | 
					                        raise ValueError  # sanity check
 | 
				
			||||||
                    self.im = im
 | 
					                    self.im = im
 | 
				
			||||||
                self.pyaccess = None
 | 
					                self.pyaccess = None
 | 
				
			||||||
| 
						 | 
					@ -1576,7 +1588,7 @@ class Image(object):
 | 
				
			||||||
            except (KeyError, ValueError):
 | 
					            except (KeyError, ValueError):
 | 
				
			||||||
                raise ValueError("illegal image mode")
 | 
					                raise ValueError("illegal image mode")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        if self.mode == "LA":
 | 
					        if self.mode in ("LA", "PA"):
 | 
				
			||||||
            band = 1
 | 
					            band = 1
 | 
				
			||||||
        else:
 | 
					        else:
 | 
				
			||||||
            band = 3
 | 
					            band = 3
 | 
				
			||||||
| 
						 | 
					@ -1619,10 +1631,10 @@ class Image(object):
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def putpalette(self, data, rawmode="RGB"):
 | 
					    def putpalette(self, data, rawmode="RGB"):
 | 
				
			||||||
        """
 | 
					        """
 | 
				
			||||||
        Attaches a palette to this image.  The image must be a "P" or
 | 
					        Attaches a palette to this image.  The image must be a "P",
 | 
				
			||||||
        "L" image, and the palette sequence must contain 768 integer
 | 
					        "PA", "L" or "LA" image, and the palette sequence must contain
 | 
				
			||||||
        values, where each group of three values represent the red,
 | 
					        768 integer values, where each group of three values represent
 | 
				
			||||||
        green, and blue values for the corresponding pixel
 | 
					        the red, green, and blue values for the corresponding pixel
 | 
				
			||||||
        index. Instead of an integer sequence, you can use an 8-bit
 | 
					        index. Instead of an integer sequence, you can use an 8-bit
 | 
				
			||||||
        string.
 | 
					        string.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1631,7 +1643,7 @@ class Image(object):
 | 
				
			||||||
        """
 | 
					        """
 | 
				
			||||||
        from . import ImagePalette
 | 
					        from . import ImagePalette
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        if self.mode not in ("L", "P"):
 | 
					        if self.mode not in ("L", "LA", "P", "PA"):
 | 
				
			||||||
            raise ValueError("illegal image mode")
 | 
					            raise ValueError("illegal image mode")
 | 
				
			||||||
        self.load()
 | 
					        self.load()
 | 
				
			||||||
        if isinstance(data, ImagePalette.ImagePalette):
 | 
					        if isinstance(data, ImagePalette.ImagePalette):
 | 
				
			||||||
| 
						 | 
					@ -1643,7 +1655,7 @@ class Image(object):
 | 
				
			||||||
                else:
 | 
					                else:
 | 
				
			||||||
                    data = "".join(chr(x) for x in data)
 | 
					                    data = "".join(chr(x) for x in data)
 | 
				
			||||||
            palette = ImagePalette.raw(rawmode, data)
 | 
					            palette = ImagePalette.raw(rawmode, data)
 | 
				
			||||||
        self.mode = "P"
 | 
					        self.mode = "PA" if "A" in self.mode else "P"
 | 
				
			||||||
        self.palette = palette
 | 
					        self.palette = palette
 | 
				
			||||||
        self.palette.mode = "RGB"
 | 
					        self.palette.mode = "RGB"
 | 
				
			||||||
        self.load()  # install new palette
 | 
					        self.load()  # install new palette
 | 
				
			||||||
| 
						 | 
					@ -1688,7 +1700,7 @@ class Image(object):
 | 
				
			||||||
        Rewrites the image to reorder the palette.
 | 
					        Rewrites the image to reorder the palette.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        :param dest_map: A list of indexes into the original palette.
 | 
					        :param dest_map: A list of indexes into the original palette.
 | 
				
			||||||
           e.g. [1,0] would swap a two item palette, and list(range(255))
 | 
					           e.g. [1,0] would swap a two item palette, and list(range(256))
 | 
				
			||||||
           is the identity transform.
 | 
					           is the identity transform.
 | 
				
			||||||
        :param source_palette: Bytes or None.
 | 
					        :param source_palette: Bytes or None.
 | 
				
			||||||
        :returns:  An :py:class:`~PIL.Image.Image` object.
 | 
					        :returns:  An :py:class:`~PIL.Image.Image` object.
 | 
				
			||||||
| 
						 | 
					@ -1958,7 +1970,7 @@ class Image(object):
 | 
				
			||||||
            filename = fp.name
 | 
					            filename = fp.name
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        # may mutate self!
 | 
					        # may mutate self!
 | 
				
			||||||
        self.load()
 | 
					        self._ensure_mutable()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        save_all = params.pop('save_all', False)
 | 
					        save_all = params.pop('save_all', False)
 | 
				
			||||||
        self.encoderinfo = params
 | 
					        self.encoderinfo = params
 | 
				
			||||||
| 
						 | 
					@ -2005,9 +2017,6 @@ class Image(object):
 | 
				
			||||||
        **EOFError** exception. When a sequence file is opened, the
 | 
					        **EOFError** exception. When a sequence file is opened, the
 | 
				
			||||||
        library automatically seeks to frame 0.
 | 
					        library automatically seeks to frame 0.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        Note that in the current version of the library, most sequence
 | 
					 | 
				
			||||||
        formats only allow you to seek to the next frame.
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        See :py:meth:`~PIL.Image.Image.tell`.
 | 
					        See :py:meth:`~PIL.Image.Image.tell`.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        :param frame: Frame number, starting at 0.
 | 
					        :param frame: Frame number, starting at 0.
 | 
				
			||||||
| 
						 | 
					@ -2374,7 +2383,14 @@ def new(mode, size, color=0):
 | 
				
			||||||
        from . import ImageColor
 | 
					        from . import ImageColor
 | 
				
			||||||
        color = ImageColor.getcolor(color, mode)
 | 
					        color = ImageColor.getcolor(color, mode)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    return Image()._new(core.fill(mode, size, color))
 | 
					    im = Image()
 | 
				
			||||||
 | 
					    if mode == "P" and \
 | 
				
			||||||
 | 
					       isinstance(color, (list, tuple)) and len(color) in [3, 4]:
 | 
				
			||||||
 | 
					        # RGB or RGBA value for a P image
 | 
				
			||||||
 | 
					        from . import ImagePalette
 | 
				
			||||||
 | 
					        im.palette = ImagePalette.ImagePalette()
 | 
				
			||||||
 | 
					        color = im.palette.getcolor(color)
 | 
				
			||||||
 | 
					    return im._new(core.fill(mode, size, color))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def frombytes(mode, size, data, decoder_name="raw", *args):
 | 
					def frombytes(mode, size, data, decoder_name="raw", *args):
 | 
				
			||||||
| 
						 | 
					@ -2995,3 +3011,182 @@ def _apply_env_variables(env=None):
 | 
				
			||||||
 | 
					
 | 
				
			||||||
_apply_env_variables()
 | 
					_apply_env_variables()
 | 
				
			||||||
atexit.register(core.clear_cache)
 | 
					atexit.register(core.clear_cache)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class Exif(MutableMapping):
 | 
				
			||||||
 | 
					    endian = "<"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def __init__(self):
 | 
				
			||||||
 | 
					        self._data = {}
 | 
				
			||||||
 | 
					        self._ifds = {}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def _fixup_dict(self, src_dict):
 | 
				
			||||||
 | 
					        # Helper function for _getexif()
 | 
				
			||||||
 | 
					        # returns a dict with any single item tuples/lists as individual values
 | 
				
			||||||
 | 
					        def _fixup(value):
 | 
				
			||||||
 | 
					            try:
 | 
				
			||||||
 | 
					                if len(value) == 1 and not isinstance(value, dict):
 | 
				
			||||||
 | 
					                    return value[0]
 | 
				
			||||||
 | 
					            except Exception:
 | 
				
			||||||
 | 
					                pass
 | 
				
			||||||
 | 
					            return value
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        return {k: _fixup(v) for k, v in src_dict.items()}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def _get_ifd_dict(self, tag):
 | 
				
			||||||
 | 
					        try:
 | 
				
			||||||
 | 
					            # an offset pointer to the location of the nested embedded IFD.
 | 
				
			||||||
 | 
					            # It should be a long, but may be corrupted.
 | 
				
			||||||
 | 
					            self.fp.seek(self._data[tag])
 | 
				
			||||||
 | 
					        except (KeyError, TypeError):
 | 
				
			||||||
 | 
					            pass
 | 
				
			||||||
 | 
					        else:
 | 
				
			||||||
 | 
					            from . import TiffImagePlugin
 | 
				
			||||||
 | 
					            info = TiffImagePlugin.ImageFileDirectory_v1(self.head)
 | 
				
			||||||
 | 
					            info.load(self.fp)
 | 
				
			||||||
 | 
					            return self._fixup_dict(info)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def load(self, data):
 | 
				
			||||||
 | 
					        # Extract EXIF information.  This is highly experimental,
 | 
				
			||||||
 | 
					        # and is likely to be replaced with something better in a future
 | 
				
			||||||
 | 
					        # version.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        # The EXIF record consists of a TIFF file embedded in a JPEG
 | 
				
			||||||
 | 
					        # application marker (!).
 | 
				
			||||||
 | 
					        self.fp = io.BytesIO(data[6:])
 | 
				
			||||||
 | 
					        self.head = self.fp.read(8)
 | 
				
			||||||
 | 
					        # process dictionary
 | 
				
			||||||
 | 
					        from . import TiffImagePlugin
 | 
				
			||||||
 | 
					        info = TiffImagePlugin.ImageFileDirectory_v1(self.head)
 | 
				
			||||||
 | 
					        self.endian = info._endian
 | 
				
			||||||
 | 
					        self.fp.seek(info.next)
 | 
				
			||||||
 | 
					        info.load(self.fp)
 | 
				
			||||||
 | 
					        self._data = dict(self._fixup_dict(info))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        # get EXIF extension
 | 
				
			||||||
 | 
					        ifd = self._get_ifd_dict(0x8769)
 | 
				
			||||||
 | 
					        if ifd:
 | 
				
			||||||
 | 
					            self._data.update(ifd)
 | 
				
			||||||
 | 
					            self._ifds[0x8769] = ifd
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        # get gpsinfo extension
 | 
				
			||||||
 | 
					        ifd = self._get_ifd_dict(0x8825)
 | 
				
			||||||
 | 
					        if ifd:
 | 
				
			||||||
 | 
					            self._data[0x8825] = ifd
 | 
				
			||||||
 | 
					            self._ifds[0x8825] = ifd
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def tobytes(self, offset=0):
 | 
				
			||||||
 | 
					        from . import TiffImagePlugin
 | 
				
			||||||
 | 
					        if self.endian == "<":
 | 
				
			||||||
 | 
					            head = b"II\x2A\x00\x08\x00\x00\x00"
 | 
				
			||||||
 | 
					        else:
 | 
				
			||||||
 | 
					            head = b"MM\x00\x2A\x00\x00\x00\x08"
 | 
				
			||||||
 | 
					        ifd = TiffImagePlugin.ImageFileDirectory_v2(ifh=head)
 | 
				
			||||||
 | 
					        for tag, value in self._data.items():
 | 
				
			||||||
 | 
					            ifd[tag] = value
 | 
				
			||||||
 | 
					        return b"Exif\x00\x00"+head+ifd.tobytes(offset)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def get_ifd(self, tag):
 | 
				
			||||||
 | 
					        if tag not in self._ifds and tag in self._data:
 | 
				
			||||||
 | 
					            if tag == 0xa005:  # interop
 | 
				
			||||||
 | 
					                self._ifds[tag] = self._get_ifd_dict(tag)
 | 
				
			||||||
 | 
					            elif tag == 0x927c:  # makernote
 | 
				
			||||||
 | 
					                from . import TiffImagePlugin
 | 
				
			||||||
 | 
					                if self._data[0x927c][:8] == b"FUJIFILM":
 | 
				
			||||||
 | 
					                    exif_data = self._data[0x927c]
 | 
				
			||||||
 | 
					                    ifd_offset = i32le(exif_data[8:12])
 | 
				
			||||||
 | 
					                    ifd_data = exif_data[ifd_offset:]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                    makernote = {}
 | 
				
			||||||
 | 
					                    for i in range(0, struct.unpack("<H", ifd_data[:2])[0]):
 | 
				
			||||||
 | 
					                        ifd_tag, typ, count, data = struct.unpack(
 | 
				
			||||||
 | 
					                            "<HHL4s", ifd_data[i*12 + 2:(i+1)*12 + 2])
 | 
				
			||||||
 | 
					                        try:
 | 
				
			||||||
 | 
					                            unit_size, handler =\
 | 
				
			||||||
 | 
					                                TiffImagePlugin.ImageFileDirectory_v2._load_dispatch[
 | 
				
			||||||
 | 
					                                    typ
 | 
				
			||||||
 | 
					                                ]
 | 
				
			||||||
 | 
					                        except KeyError:
 | 
				
			||||||
 | 
					                            continue
 | 
				
			||||||
 | 
					                        size = count * unit_size
 | 
				
			||||||
 | 
					                        if size > 4:
 | 
				
			||||||
 | 
					                            offset, = struct.unpack("<L", data)
 | 
				
			||||||
 | 
					                            data = ifd_data[offset-12:offset+size-12]
 | 
				
			||||||
 | 
					                        else:
 | 
				
			||||||
 | 
					                            data = data[:size]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                        if len(data) != size:
 | 
				
			||||||
 | 
					                            warnings.warn("Possibly corrupt EXIF MakerNote data.  "
 | 
				
			||||||
 | 
					                                          "Expecting to read %d bytes but only got %d."
 | 
				
			||||||
 | 
					                                          " Skipping tag %s"
 | 
				
			||||||
 | 
					                                          % (size, len(data), ifd_tag))
 | 
				
			||||||
 | 
					                            continue
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                        if not data:
 | 
				
			||||||
 | 
					                            continue
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                        makernote[ifd_tag] = handler(
 | 
				
			||||||
 | 
					                            TiffImagePlugin.ImageFileDirectory_v2(), data, False)
 | 
				
			||||||
 | 
					                    self._ifds[0x927c] = dict(self._fixup_dict(makernote))
 | 
				
			||||||
 | 
					                elif self._data.get(0x010f) == "Nintendo":
 | 
				
			||||||
 | 
					                    ifd_data = self._data[0x927c]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                    makernote = {}
 | 
				
			||||||
 | 
					                    for i in range(0, struct.unpack(">H", ifd_data[:2])[0]):
 | 
				
			||||||
 | 
					                        ifd_tag, typ, count, data = struct.unpack(
 | 
				
			||||||
 | 
					                            ">HHL4s", ifd_data[i*12 + 2:(i+1)*12 + 2])
 | 
				
			||||||
 | 
					                        if ifd_tag == 0x1101:
 | 
				
			||||||
 | 
					                            # CameraInfo
 | 
				
			||||||
 | 
					                            offset, = struct.unpack(">L", data)
 | 
				
			||||||
 | 
					                            self.fp.seek(offset)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                            camerainfo = {'ModelID': self.fp.read(4)}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                            self.fp.read(4)
 | 
				
			||||||
 | 
					                            # Seconds since 2000
 | 
				
			||||||
 | 
					                            camerainfo['TimeStamp'] = i32le(self.fp.read(12))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                            self.fp.read(4)
 | 
				
			||||||
 | 
					                            camerainfo['InternalSerialNumber'] = self.fp.read(4)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                            self.fp.read(12)
 | 
				
			||||||
 | 
					                            parallax = self.fp.read(4)
 | 
				
			||||||
 | 
					                            handler =\
 | 
				
			||||||
 | 
					                                TiffImagePlugin.ImageFileDirectory_v2._load_dispatch[
 | 
				
			||||||
 | 
					                                    TiffTags.FLOAT
 | 
				
			||||||
 | 
					                                ][1]
 | 
				
			||||||
 | 
					                            camerainfo['Parallax'] = handler(
 | 
				
			||||||
 | 
					                                TiffImagePlugin.ImageFileDirectory_v2(),
 | 
				
			||||||
 | 
					                                parallax, False)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                            self.fp.read(4)
 | 
				
			||||||
 | 
					                            camerainfo['Category'] = self.fp.read(2)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                            makernote = {0x1101: dict(self._fixup_dict(camerainfo))}
 | 
				
			||||||
 | 
					                    self._ifds[0x927c] = makernote
 | 
				
			||||||
 | 
					        return self._ifds.get(tag, {})
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def __str__(self):
 | 
				
			||||||
 | 
					        return str(self._data)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def __len__(self):
 | 
				
			||||||
 | 
					        return len(self._data)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def __getitem__(self, tag):
 | 
				
			||||||
 | 
					        return self._data[tag]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def __contains__(self, tag):
 | 
				
			||||||
 | 
					        return tag in self._data
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if not py3:
 | 
				
			||||||
 | 
					        def has_key(self, tag):
 | 
				
			||||||
 | 
					            return tag in self
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def __setitem__(self, tag, value):
 | 
				
			||||||
 | 
					        self._data[tag] = value
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def __delitem__(self, tag):
 | 
				
			||||||
 | 
					        del self._data[tag]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def __iter__(self):
 | 
				
			||||||
 | 
					        return iter(set(self._data))
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -686,11 +686,11 @@ def getProfileName(profile):
 | 
				
			||||||
        #    // name was "%s - %s" (model, manufacturer) || Description ,
 | 
					        #    // name was "%s - %s" (model, manufacturer) || Description ,
 | 
				
			||||||
        #    // but if the Model and Manufacturer were the same or the model
 | 
					        #    // but if the Model and Manufacturer were the same or the model
 | 
				
			||||||
        #    // was long, Just the model,  in 1.x
 | 
					        #    // was long, Just the model,  in 1.x
 | 
				
			||||||
        model = profile.profile.product_model
 | 
					        model = profile.profile.model
 | 
				
			||||||
        manufacturer = profile.profile.product_manufacturer
 | 
					        manufacturer = profile.profile.manufacturer
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        if not (model or manufacturer):
 | 
					        if not (model or manufacturer):
 | 
				
			||||||
            return profile.profile.product_description + "\n"
 | 
					            return (profile.profile.profile_description or "") + "\n"
 | 
				
			||||||
        if not manufacturer or len(model) > 30:
 | 
					        if not manufacturer or len(model) > 30:
 | 
				
			||||||
            return model + "\n"
 | 
					            return model + "\n"
 | 
				
			||||||
        return "%s - %s\n" % (model, manufacturer)
 | 
					        return "%s - %s\n" % (model, manufacturer)
 | 
				
			||||||
| 
						 | 
					@ -727,8 +727,8 @@ def getProfileInfo(profile):
 | 
				
			||||||
        # Python, not C. the white point bits weren't working well,
 | 
					        # Python, not C. the white point bits weren't working well,
 | 
				
			||||||
        # so skipping.
 | 
					        # so skipping.
 | 
				
			||||||
        # info was description \r\n\r\n copyright \r\n\r\n K007 tag \r\n\r\n whitepoint
 | 
					        # info was description \r\n\r\n copyright \r\n\r\n K007 tag \r\n\r\n whitepoint
 | 
				
			||||||
        description = profile.profile.product_description
 | 
					        description = profile.profile.profile_description
 | 
				
			||||||
        cpright = profile.profile.product_copyright
 | 
					        cpright = profile.profile.copyright
 | 
				
			||||||
        arr = []
 | 
					        arr = []
 | 
				
			||||||
        for elt in (description, cpright):
 | 
					        for elt in (description, cpright):
 | 
				
			||||||
            if elt:
 | 
					            if elt:
 | 
				
			||||||
| 
						 | 
					@ -762,7 +762,7 @@ def getProfileCopyright(profile):
 | 
				
			||||||
        # add an extra newline to preserve pyCMS compatibility
 | 
					        # add an extra newline to preserve pyCMS compatibility
 | 
				
			||||||
        if not isinstance(profile, ImageCmsProfile):
 | 
					        if not isinstance(profile, ImageCmsProfile):
 | 
				
			||||||
            profile = ImageCmsProfile(profile)
 | 
					            profile = ImageCmsProfile(profile)
 | 
				
			||||||
        return profile.profile.product_copyright + "\n"
 | 
					        return (profile.profile.copyright or "") + "\n"
 | 
				
			||||||
    except (AttributeError, IOError, TypeError, ValueError) as v:
 | 
					    except (AttributeError, IOError, TypeError, ValueError) as v:
 | 
				
			||||||
        raise PyCMSError(v)
 | 
					        raise PyCMSError(v)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -790,7 +790,7 @@ def getProfileManufacturer(profile):
 | 
				
			||||||
        # add an extra newline to preserve pyCMS compatibility
 | 
					        # add an extra newline to preserve pyCMS compatibility
 | 
				
			||||||
        if not isinstance(profile, ImageCmsProfile):
 | 
					        if not isinstance(profile, ImageCmsProfile):
 | 
				
			||||||
            profile = ImageCmsProfile(profile)
 | 
					            profile = ImageCmsProfile(profile)
 | 
				
			||||||
        return profile.profile.product_manufacturer + "\n"
 | 
					        return (profile.profile.manufacturer or "") + "\n"
 | 
				
			||||||
    except (AttributeError, IOError, TypeError, ValueError) as v:
 | 
					    except (AttributeError, IOError, TypeError, ValueError) as v:
 | 
				
			||||||
        raise PyCMSError(v)
 | 
					        raise PyCMSError(v)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -819,7 +819,7 @@ def getProfileModel(profile):
 | 
				
			||||||
        # add an extra newline to preserve pyCMS compatibility
 | 
					        # add an extra newline to preserve pyCMS compatibility
 | 
				
			||||||
        if not isinstance(profile, ImageCmsProfile):
 | 
					        if not isinstance(profile, ImageCmsProfile):
 | 
				
			||||||
            profile = ImageCmsProfile(profile)
 | 
					            profile = ImageCmsProfile(profile)
 | 
				
			||||||
        return profile.profile.product_model + "\n"
 | 
					        return (profile.profile.model or "") + "\n"
 | 
				
			||||||
    except (AttributeError, IOError, TypeError, ValueError) as v:
 | 
					    except (AttributeError, IOError, TypeError, ValueError) as v:
 | 
				
			||||||
        raise PyCMSError(v)
 | 
					        raise PyCMSError(v)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -848,7 +848,7 @@ def getProfileDescription(profile):
 | 
				
			||||||
        # add an extra newline to preserve pyCMS compatibility
 | 
					        # add an extra newline to preserve pyCMS compatibility
 | 
				
			||||||
        if not isinstance(profile, ImageCmsProfile):
 | 
					        if not isinstance(profile, ImageCmsProfile):
 | 
				
			||||||
            profile = ImageCmsProfile(profile)
 | 
					            profile = ImageCmsProfile(profile)
 | 
				
			||||||
        return profile.profile.product_description + "\n"
 | 
					        return (profile.profile.profile_description or "") + "\n"
 | 
				
			||||||
    except (AttributeError, IOError, TypeError, ValueError) as v:
 | 
					    except (AttributeError, IOError, TypeError, ValueError) as v:
 | 
				
			||||||
        raise PyCMSError(v)
 | 
					        raise PyCMSError(v)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -282,13 +282,17 @@ class ImageDraw(object):
 | 
				
			||||||
            self.draw.draw_bitmap(xy, mask, ink)
 | 
					            self.draw.draw_bitmap(xy, mask, ink)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def multiline_text(self, xy, text, fill=None, font=None, anchor=None,
 | 
					    def multiline_text(self, xy, text, fill=None, font=None, anchor=None,
 | 
				
			||||||
                       spacing=4, align="left", direction=None, features=None):
 | 
					                       spacing=4, align="left", direction=None, features=None,
 | 
				
			||||||
 | 
					                       language=None):
 | 
				
			||||||
        widths = []
 | 
					        widths = []
 | 
				
			||||||
        max_width = 0
 | 
					        max_width = 0
 | 
				
			||||||
        lines = self._multiline_split(text)
 | 
					        lines = self._multiline_split(text)
 | 
				
			||||||
        line_spacing = self.textsize('A', font=font)[1] + spacing
 | 
					        line_spacing = self.textsize('A', font=font)[1] + spacing
 | 
				
			||||||
        for line in lines:
 | 
					        for line in lines:
 | 
				
			||||||
            line_width, line_height = self.textsize(line, font)
 | 
					            line_width, line_height = self.textsize(line, font,
 | 
				
			||||||
 | 
					                                                    direction=direction,
 | 
				
			||||||
 | 
					                                                    features=features,
 | 
				
			||||||
 | 
					                                                    language=language)
 | 
				
			||||||
            widths.append(line_width)
 | 
					            widths.append(line_width)
 | 
				
			||||||
            max_width = max(max_width, line_width)
 | 
					            max_width = max(max_width, line_width)
 | 
				
			||||||
        left, top = xy
 | 
					        left, top = xy
 | 
				
			||||||
| 
						 | 
					@ -302,29 +306,30 @@ class ImageDraw(object):
 | 
				
			||||||
            else:
 | 
					            else:
 | 
				
			||||||
                raise ValueError('align must be "left", "center" or "right"')
 | 
					                raise ValueError('align must be "left", "center" or "right"')
 | 
				
			||||||
            self.text((left, top), line, fill, font, anchor,
 | 
					            self.text((left, top), line, fill, font, anchor,
 | 
				
			||||||
                      direction=direction, features=features)
 | 
					                      direction=direction, features=features, language=language)
 | 
				
			||||||
            top += line_spacing
 | 
					            top += line_spacing
 | 
				
			||||||
            left = xy[0]
 | 
					            left = xy[0]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def textsize(self, text, font=None, spacing=4, direction=None,
 | 
					    def textsize(self, text, font=None, spacing=4, direction=None,
 | 
				
			||||||
                 features=None):
 | 
					                 features=None, language=None):
 | 
				
			||||||
        """Get the size of a given string, in pixels."""
 | 
					        """Get the size of a given string, in pixels."""
 | 
				
			||||||
        if self._multiline_check(text):
 | 
					        if self._multiline_check(text):
 | 
				
			||||||
            return self.multiline_textsize(text, font, spacing,
 | 
					            return self.multiline_textsize(text, font, spacing,
 | 
				
			||||||
                                           direction, features)
 | 
					                                           direction, features, language)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        if font is None:
 | 
					        if font is None:
 | 
				
			||||||
            font = self.getfont()
 | 
					            font = self.getfont()
 | 
				
			||||||
        return font.getsize(text, direction, features)
 | 
					        return font.getsize(text, direction, features, language)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def multiline_textsize(self, text, font=None, spacing=4, direction=None,
 | 
					    def multiline_textsize(self, text, font=None, spacing=4, direction=None,
 | 
				
			||||||
                           features=None):
 | 
					                           features=None, language=None):
 | 
				
			||||||
        max_width = 0
 | 
					        max_width = 0
 | 
				
			||||||
        lines = self._multiline_split(text)
 | 
					        lines = self._multiline_split(text)
 | 
				
			||||||
        line_spacing = self.textsize('A', font=font)[1] + spacing
 | 
					        line_spacing = self.textsize('A', font=font)[1] + spacing
 | 
				
			||||||
        for line in lines:
 | 
					        for line in lines:
 | 
				
			||||||
            line_width, line_height = self.textsize(line, font, spacing,
 | 
					            line_width, line_height = self.textsize(line, font, spacing,
 | 
				
			||||||
                                                    direction, features)
 | 
					                                                    direction, features,
 | 
				
			||||||
 | 
					                                                    language)
 | 
				
			||||||
            max_width = max(max_width, line_width)
 | 
					            max_width = max(max_width, line_width)
 | 
				
			||||||
        return max_width, len(lines)*line_spacing - spacing
 | 
					        return max_width, len(lines)*line_spacing - spacing
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -596,8 +596,6 @@ class PyDecoder(object):
 | 
				
			||||||
        Override to perform the decoding process.
 | 
					        Override to perform the decoding process.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        :param buffer: A bytes object with the data to be decoded.
 | 
					        :param buffer: A bytes object with the data to be decoded.
 | 
				
			||||||
            If `handles_eof` is set, then `buffer` will be empty and `self.fd`
 | 
					 | 
				
			||||||
            will be set.
 | 
					 | 
				
			||||||
        :returns: A tuple of (bytes consumed, errcode).
 | 
					        :returns: A tuple of (bytes consumed, errcode).
 | 
				
			||||||
            If finished with decoding return <0 for the bytes consumed.
 | 
					            If finished with decoding return <0 for the bytes consumed.
 | 
				
			||||||
            Err codes are from `ERRORS`
 | 
					            Err codes are from `ERRORS`
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -158,17 +158,17 @@ class FreeTypeFont(object):
 | 
				
			||||||
    def getmetrics(self):
 | 
					    def getmetrics(self):
 | 
				
			||||||
        return self.font.ascent, self.font.descent
 | 
					        return self.font.ascent, self.font.descent
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def getsize(self, text, direction=None, features=None):
 | 
					    def getsize(self, text, direction=None, features=None, language=None):
 | 
				
			||||||
        size, offset = self.font.getsize(text, direction, features)
 | 
					        size, offset = self.font.getsize(text, direction, features, language)
 | 
				
			||||||
        return (size[0] + offset[0], size[1] + offset[1])
 | 
					        return (size[0] + offset[0], size[1] + offset[1])
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def getsize_multiline(self, text, direction=None,
 | 
					    def getsize_multiline(self, text, direction=None, spacing=4,
 | 
				
			||||||
                          spacing=4, features=None):
 | 
					                          features=None, language=None):
 | 
				
			||||||
        max_width = 0
 | 
					        max_width = 0
 | 
				
			||||||
        lines = self._multiline_split(text)
 | 
					        lines = self._multiline_split(text)
 | 
				
			||||||
        line_spacing = self.getsize('A')[1] + spacing
 | 
					        line_spacing = self.getsize('A')[1] + spacing
 | 
				
			||||||
        for line in lines:
 | 
					        for line in lines:
 | 
				
			||||||
            line_width, line_height = self.getsize(line, direction, features)
 | 
					            line_width, line_height = self.getsize(line, direction, features, language)
 | 
				
			||||||
            max_width = max(max_width, line_width)
 | 
					            max_width = max(max_width, line_width)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        return max_width, len(lines)*line_spacing - spacing
 | 
					        return max_width, len(lines)*line_spacing - spacing
 | 
				
			||||||
| 
						 | 
					@ -176,15 +176,15 @@ class FreeTypeFont(object):
 | 
				
			||||||
    def getoffset(self, text):
 | 
					    def getoffset(self, text):
 | 
				
			||||||
        return self.font.getsize(text)[1]
 | 
					        return self.font.getsize(text)[1]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def getmask(self, text, mode="", direction=None, features=None):
 | 
					    def getmask(self, text, mode="", direction=None, features=None, language=None):
 | 
				
			||||||
        return self.getmask2(text, mode, direction=direction,
 | 
					        return self.getmask2(text, mode, direction=direction, features=features,
 | 
				
			||||||
                             features=features)[0]
 | 
					                             language=language)[0]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def getmask2(self, text, mode="", fill=Image.core.fill, direction=None,
 | 
					    def getmask2(self, text, mode="", fill=Image.core.fill, direction=None,
 | 
				
			||||||
                 features=None, *args, **kwargs):
 | 
					                 features=None, language=None, *args, **kwargs):
 | 
				
			||||||
        size, offset = self.font.getsize(text, direction, features)
 | 
					        size, offset = self.font.getsize(text, direction, features, language)
 | 
				
			||||||
        im = fill("L", size, 0)
 | 
					        im = fill("L", size, 0)
 | 
				
			||||||
        self.font.render(text, im.id, mode == "1", direction, features)
 | 
					        self.font.render(text, im.id, mode == "1", direction, features, language)
 | 
				
			||||||
        return im, offset
 | 
					        return im, offset
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def font_variant(self, font=None, size=None, index=None, encoding=None,
 | 
					    def font_variant(self, font=None, size=None, index=None, encoding=None,
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -522,3 +522,30 @@ def solarize(image, threshold=128):
 | 
				
			||||||
        else:
 | 
					        else:
 | 
				
			||||||
            lut.append(255-i)
 | 
					            lut.append(255-i)
 | 
				
			||||||
    return _lut(image, lut)
 | 
					    return _lut(image, lut)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					def exif_transpose(image):
 | 
				
			||||||
 | 
					    """
 | 
				
			||||||
 | 
					    If an image has an EXIF Orientation tag, return a new image that is
 | 
				
			||||||
 | 
					    transposed accordingly. Otherwise, return a copy of the image.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    :param image: The image to transpose.
 | 
				
			||||||
 | 
					    :return: An image.
 | 
				
			||||||
 | 
					    """
 | 
				
			||||||
 | 
					    exif = image.getexif()
 | 
				
			||||||
 | 
					    orientation = exif.get(0x0112)
 | 
				
			||||||
 | 
					    method = {
 | 
				
			||||||
 | 
					        2: Image.FLIP_LEFT_RIGHT,
 | 
				
			||||||
 | 
					        3: Image.ROTATE_180,
 | 
				
			||||||
 | 
					        4: Image.FLIP_TOP_BOTTOM,
 | 
				
			||||||
 | 
					        5: Image.TRANSPOSE,
 | 
				
			||||||
 | 
					        6: Image.ROTATE_270,
 | 
				
			||||||
 | 
					        7: Image.TRANSVERSE,
 | 
				
			||||||
 | 
					        8: Image.ROTATE_90
 | 
				
			||||||
 | 
					    }.get(orientation)
 | 
				
			||||||
 | 
					    if method is not None:
 | 
				
			||||||
 | 
					        transposed_image = image.transpose(method)
 | 
				
			||||||
 | 
					        del exif[0x0112]
 | 
				
			||||||
 | 
					        transposed_image.info["exif"] = exif.tobytes()
 | 
				
			||||||
 | 
					        return transposed_image
 | 
				
			||||||
 | 
					    return image.copy()
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -198,35 +198,9 @@ def getiptcinfo(im):
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    elif isinstance(im, JpegImagePlugin.JpegImageFile):
 | 
					    elif isinstance(im, JpegImagePlugin.JpegImageFile):
 | 
				
			||||||
        # extract the IPTC/NAA resource
 | 
					        # extract the IPTC/NAA resource
 | 
				
			||||||
        try:
 | 
					        photoshop = im.info.get("photoshop")
 | 
				
			||||||
            app = im.app["APP13"]
 | 
					        if photoshop:
 | 
				
			||||||
            if app[:14] == b"Photoshop 3.0\x00":
 | 
					            data = photoshop.get(0x0404)
 | 
				
			||||||
                app = app[14:]
 | 
					 | 
				
			||||||
                # parse the image resource block
 | 
					 | 
				
			||||||
                offset = 0
 | 
					 | 
				
			||||||
                while app[offset:offset+4] == b"8BIM":
 | 
					 | 
				
			||||||
                    offset += 4
 | 
					 | 
				
			||||||
                    # resource code
 | 
					 | 
				
			||||||
                    code = i16(app, offset)
 | 
					 | 
				
			||||||
                    offset += 2
 | 
					 | 
				
			||||||
                    # resource name (usually empty)
 | 
					 | 
				
			||||||
                    name_len = i8(app[offset])
 | 
					 | 
				
			||||||
                    # name = app[offset+1:offset+1+name_len]
 | 
					 | 
				
			||||||
                    offset = 1 + offset + name_len
 | 
					 | 
				
			||||||
                    if offset & 1:
 | 
					 | 
				
			||||||
                        offset += 1
 | 
					 | 
				
			||||||
                    # resource data block
 | 
					 | 
				
			||||||
                    size = i32(app, offset)
 | 
					 | 
				
			||||||
                    offset += 4
 | 
					 | 
				
			||||||
                    if code == 0x0404:
 | 
					 | 
				
			||||||
                        # 0x0404 contains IPTC/NAA data
 | 
					 | 
				
			||||||
                        data = app[offset:offset+size]
 | 
					 | 
				
			||||||
                        break
 | 
					 | 
				
			||||||
                    offset = offset + size
 | 
					 | 
				
			||||||
                    if offset & 1:
 | 
					 | 
				
			||||||
                        offset += 1
 | 
					 | 
				
			||||||
        except (AttributeError, KeyError):
 | 
					 | 
				
			||||||
            pass
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
    elif isinstance(im, TiffImagePlugin.TiffImageFile):
 | 
					    elif isinstance(im, TiffImagePlugin.TiffImageFile):
 | 
				
			||||||
        # get raw data from the IPTC/NAA tag (PhotoShop tags the data
 | 
					        # get raw data from the IPTC/NAA tag (PhotoShop tags the data
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -39,7 +39,7 @@ import struct
 | 
				
			||||||
import io
 | 
					import io
 | 
				
			||||||
import warnings
 | 
					import warnings
 | 
				
			||||||
from . import Image, ImageFile, TiffImagePlugin
 | 
					from . import Image, ImageFile, TiffImagePlugin
 | 
				
			||||||
from ._binary import i8, o8, i16be as i16
 | 
					from ._binary import i8, o8, i16be as i16, i32be as i32
 | 
				
			||||||
from .JpegPresets import presets
 | 
					from .JpegPresets import presets
 | 
				
			||||||
from ._util import isStringType
 | 
					from ._util import isStringType
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -86,7 +86,7 @@ def APP(self, marker):
 | 
				
			||||||
            self.info["jfif_density"] = jfif_density
 | 
					            self.info["jfif_density"] = jfif_density
 | 
				
			||||||
    elif marker == 0xFFE1 and s[:5] == b"Exif\0":
 | 
					    elif marker == 0xFFE1 and s[:5] == b"Exif\0":
 | 
				
			||||||
        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
 | 
				
			||||||
    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)
 | 
				
			||||||
| 
						 | 
					@ -104,6 +104,39 @@ def APP(self, marker):
 | 
				
			||||||
        # reassemble the profile, rather than assuming that the APP2
 | 
					        # reassemble the profile, rather than assuming that the APP2
 | 
				
			||||||
        # markers appear in the correct sequence.
 | 
					        # markers appear in the correct sequence.
 | 
				
			||||||
        self.icclist.append(s)
 | 
					        self.icclist.append(s)
 | 
				
			||||||
 | 
					    elif marker == 0xFFED:
 | 
				
			||||||
 | 
					        if s[:14] == b"Photoshop 3.0\x00":
 | 
				
			||||||
 | 
					            blocks = s[14:]
 | 
				
			||||||
 | 
					            # parse the image resource block
 | 
				
			||||||
 | 
					            offset = 0
 | 
				
			||||||
 | 
					            photoshop = {}
 | 
				
			||||||
 | 
					            while blocks[offset:offset+4] == b"8BIM":
 | 
				
			||||||
 | 
					                offset += 4
 | 
				
			||||||
 | 
					                # resource code
 | 
				
			||||||
 | 
					                code = i16(blocks, offset)
 | 
				
			||||||
 | 
					                offset += 2
 | 
				
			||||||
 | 
					                # resource name (usually empty)
 | 
				
			||||||
 | 
					                name_len = i8(blocks[offset])
 | 
				
			||||||
 | 
					                # name = blocks[offset+1:offset+1+name_len]
 | 
				
			||||||
 | 
					                offset = 1 + offset + name_len
 | 
				
			||||||
 | 
					                if offset & 1:
 | 
				
			||||||
 | 
					                    offset += 1
 | 
				
			||||||
 | 
					                # resource data block
 | 
				
			||||||
 | 
					                size = i32(blocks, offset)
 | 
				
			||||||
 | 
					                offset += 4
 | 
				
			||||||
 | 
					                data = blocks[offset:offset+size]
 | 
				
			||||||
 | 
					                if code == 0x03ED:  # ResolutionInfo
 | 
				
			||||||
 | 
					                    data = {
 | 
				
			||||||
 | 
					                        'XResolution': i32(data[:4]) / 65536,
 | 
				
			||||||
 | 
					                        'DisplayedUnitsX': i16(data[4:8]),
 | 
				
			||||||
 | 
					                        'YResolution': i32(data[8:12]) / 65536,
 | 
				
			||||||
 | 
					                        'DisplayedUnitsY': i16(data[12:]),
 | 
				
			||||||
 | 
					                    }
 | 
				
			||||||
 | 
					                photoshop[code] = data
 | 
				
			||||||
 | 
					                offset = offset + size
 | 
				
			||||||
 | 
					                if offset & 1:
 | 
				
			||||||
 | 
					                    offset += 1
 | 
				
			||||||
 | 
					        self.info["photoshop"] = photoshop
 | 
				
			||||||
    elif marker == 0xFFEE and s[:5] == b"Adobe":
 | 
					    elif marker == 0xFFEE and s[:5] == b"Adobe":
 | 
				
			||||||
        self.info["adobe"] = i16(s, 5)
 | 
					        self.info["adobe"] = i16(s, 5)
 | 
				
			||||||
        # extract Adobe custom properties
 | 
					        # extract Adobe custom properties
 | 
				
			||||||
| 
						 | 
					@ -127,15 +160,15 @@ def APP(self, marker):
 | 
				
			||||||
            resolution_unit = exif[0x0128]
 | 
					            resolution_unit = exif[0x0128]
 | 
				
			||||||
            x_resolution = exif[0x011A]
 | 
					            x_resolution = exif[0x011A]
 | 
				
			||||||
            try:
 | 
					            try:
 | 
				
			||||||
                dpi = x_resolution[0] / x_resolution[1]
 | 
					                dpi = float(x_resolution[0]) / x_resolution[1]
 | 
				
			||||||
            except TypeError:
 | 
					            except TypeError:
 | 
				
			||||||
                dpi = x_resolution
 | 
					                dpi = x_resolution
 | 
				
			||||||
            if resolution_unit == 3:  # cm
 | 
					            if resolution_unit == 3:  # cm
 | 
				
			||||||
                # 1 dpcm = 2.54 dpi
 | 
					                # 1 dpcm = 2.54 dpi
 | 
				
			||||||
                dpi *= 2.54
 | 
					                dpi *= 2.54
 | 
				
			||||||
            self.info["dpi"] = dpi, dpi
 | 
					            self.info["dpi"] = int(dpi + 0.5), int(dpi + 0.5)
 | 
				
			||||||
        except (KeyError, SyntaxError, ZeroDivisionError):
 | 
					        except (KeyError, SyntaxError, ZeroDivisionError):
 | 
				
			||||||
            # SyntaxError for invalid/unreadable exif
 | 
					            # SyntaxError for invalid/unreadable EXIF
 | 
				
			||||||
            # KeyError for dpi not included
 | 
					            # KeyError for dpi not included
 | 
				
			||||||
            # ZeroDivisionError for invalid dpi rational value
 | 
					            # ZeroDivisionError for invalid dpi rational value
 | 
				
			||||||
            self.info["dpi"] = 72, 72
 | 
					            self.info["dpi"] = 72, 72
 | 
				
			||||||
| 
						 | 
					@ -439,60 +472,23 @@ class JpegImageFile(ImageFile.ImageFile):
 | 
				
			||||||
def _fixup_dict(src_dict):
 | 
					def _fixup_dict(src_dict):
 | 
				
			||||||
    # Helper function for _getexif()
 | 
					    # Helper function for _getexif()
 | 
				
			||||||
    # returns a dict with any single item tuples/lists as individual values
 | 
					    # returns a dict with any single item tuples/lists as individual values
 | 
				
			||||||
    def _fixup(value):
 | 
					    exif = Image.Exif()
 | 
				
			||||||
        try:
 | 
					    return exif._fixup_dict(src_dict)
 | 
				
			||||||
            if len(value) == 1 and not isinstance(value, dict):
 | 
					 | 
				
			||||||
                return value[0]
 | 
					 | 
				
			||||||
        except Exception:
 | 
					 | 
				
			||||||
            pass
 | 
					 | 
				
			||||||
        return value
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    return {k: _fixup(v) for k, v in src_dict.items()}
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def _getexif(self):
 | 
					def _getexif(self):
 | 
				
			||||||
    # Extract EXIF information.  This method is highly experimental,
 | 
					    # Use the cached version if possible
 | 
				
			||||||
    # and is likely to be replaced with something better in a future
 | 
					 | 
				
			||||||
    # version.
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    # The EXIF record consists of a TIFF file embedded in a JPEG
 | 
					 | 
				
			||||||
    # application marker (!).
 | 
					 | 
				
			||||||
    try:
 | 
					    try:
 | 
				
			||||||
        data = self.info["exif"]
 | 
					        return self.info["parsed_exif"]
 | 
				
			||||||
    except KeyError:
 | 
					    except KeyError:
 | 
				
			||||||
        return None
 | 
					 | 
				
			||||||
    fp = io.BytesIO(data[6:])
 | 
					 | 
				
			||||||
    head = fp.read(8)
 | 
					 | 
				
			||||||
    # process dictionary
 | 
					 | 
				
			||||||
    info = TiffImagePlugin.ImageFileDirectory_v1(head)
 | 
					 | 
				
			||||||
    fp.seek(info.next)
 | 
					 | 
				
			||||||
    info.load(fp)
 | 
					 | 
				
			||||||
    exif = dict(_fixup_dict(info))
 | 
					 | 
				
			||||||
    # get exif extension
 | 
					 | 
				
			||||||
    try:
 | 
					 | 
				
			||||||
        # exif field 0x8769 is an offset pointer to the location
 | 
					 | 
				
			||||||
        # of the nested embedded exif ifd.
 | 
					 | 
				
			||||||
        # It should be a long, but may be corrupted.
 | 
					 | 
				
			||||||
        fp.seek(exif[0x8769])
 | 
					 | 
				
			||||||
    except (KeyError, TypeError):
 | 
					 | 
				
			||||||
        pass
 | 
					        pass
 | 
				
			||||||
    else:
 | 
					 | 
				
			||||||
        info = TiffImagePlugin.ImageFileDirectory_v1(head)
 | 
					 | 
				
			||||||
        info.load(fp)
 | 
					 | 
				
			||||||
        exif.update(_fixup_dict(info))
 | 
					 | 
				
			||||||
    # get gpsinfo extension
 | 
					 | 
				
			||||||
    try:
 | 
					 | 
				
			||||||
        # exif field 0x8825 is an offset pointer to the location
 | 
					 | 
				
			||||||
        # of the nested embedded gps exif ifd.
 | 
					 | 
				
			||||||
        # It should be a long, but may be corrupted.
 | 
					 | 
				
			||||||
        fp.seek(exif[0x8825])
 | 
					 | 
				
			||||||
    except (KeyError, TypeError):
 | 
					 | 
				
			||||||
        pass
 | 
					 | 
				
			||||||
    else:
 | 
					 | 
				
			||||||
        info = TiffImagePlugin.ImageFileDirectory_v1(head)
 | 
					 | 
				
			||||||
        info.load(fp)
 | 
					 | 
				
			||||||
        exif[0x8825] = _fixup_dict(info)
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if "exif" not in self.info:
 | 
				
			||||||
 | 
					        return None
 | 
				
			||||||
 | 
					    exif = dict(self.getexif())
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    # Cache the result for future use
 | 
				
			||||||
 | 
					    self.info["parsed_exif"] = exif
 | 
				
			||||||
    return exif
 | 
					    return exif
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -728,6 +724,10 @@ def _save(im, fp, filename):
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    optimize = info.get("optimize", False)
 | 
					    optimize = info.get("optimize", False)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    exif = info.get("exif", b"")
 | 
				
			||||||
 | 
					    if isinstance(exif, Image.Exif):
 | 
				
			||||||
 | 
					        exif = exif.tobytes()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    # get keyword arguments
 | 
					    # get keyword arguments
 | 
				
			||||||
    im.encoderconfig = (
 | 
					    im.encoderconfig = (
 | 
				
			||||||
        quality,
 | 
					        quality,
 | 
				
			||||||
| 
						 | 
					@ -739,7 +739,7 @@ def _save(im, fp, filename):
 | 
				
			||||||
        subsampling,
 | 
					        subsampling,
 | 
				
			||||||
        qtables,
 | 
					        qtables,
 | 
				
			||||||
        extra,
 | 
					        extra,
 | 
				
			||||||
        info.get("exif", b"")
 | 
					        exif
 | 
				
			||||||
        )
 | 
					        )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    # if we optimize, libjpeg needs a buffer big enough to hold the whole image
 | 
					    # if we optimize, libjpeg needs a buffer big enough to hold the whole image
 | 
				
			||||||
| 
						 | 
					@ -757,9 +757,9 @@ def _save(im, fp, filename):
 | 
				
			||||||
        else:
 | 
					        else:
 | 
				
			||||||
            bufsize = im.size[0] * im.size[1]
 | 
					            bufsize = im.size[0] * im.size[1]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    # The exif info needs to be written as one block, + APP1, + one spare byte.
 | 
					    # The EXIF info needs to be written as one block, + APP1, + one spare byte.
 | 
				
			||||||
    # Ensure that our buffer is big enough. Same with the icc_profile block.
 | 
					    # Ensure that our buffer is big enough. Same with the icc_profile block.
 | 
				
			||||||
    bufsize = max(ImageFile.MAXBLOCK, bufsize, len(info.get("exif", b"")) + 5,
 | 
					    bufsize = max(ImageFile.MAXBLOCK, bufsize, len(exif) + 5,
 | 
				
			||||||
                  len(extra) + 1)
 | 
					                  len(extra) + 1)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    ImageFile._save(im, fp, [("jpeg", (0, 0)+im.size, 0, rawmode)], bufsize)
 | 
					    ImageFile._save(im, fp, [("jpeg", (0, 0)+im.size, 0, rawmode)], bufsize)
 | 
				
			||||||
| 
						 | 
					@ -786,7 +786,8 @@ def jpeg_factory(fp=None, filename=None):
 | 
				
			||||||
        if mpheader[45057] > 1:
 | 
					        if mpheader[45057] > 1:
 | 
				
			||||||
            # It's actually an MPO
 | 
					            # It's actually an MPO
 | 
				
			||||||
            from .MpoImagePlugin import MpoImageFile
 | 
					            from .MpoImagePlugin import MpoImageFile
 | 
				
			||||||
            im = MpoImageFile(fp, filename)
 | 
					            # Don't reload everything, just convert it.
 | 
				
			||||||
 | 
					            im = MpoImageFile.adopt(im, mpheader)
 | 
				
			||||||
    except (TypeError, IndexError):
 | 
					    except (TypeError, IndexError):
 | 
				
			||||||
        # It is really a JPEG
 | 
					        # It is really a JPEG
 | 
				
			||||||
        pass
 | 
					        pass
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -18,7 +18,8 @@
 | 
				
			||||||
# See the README file for information on usage and redistribution.
 | 
					# See the README file for information on usage and redistribution.
 | 
				
			||||||
#
 | 
					#
 | 
				
			||||||
 | 
					
 | 
				
			||||||
from . import Image, JpegImagePlugin
 | 
					from . import Image, ImageFile, JpegImagePlugin
 | 
				
			||||||
 | 
					from ._binary import i16be as i16
 | 
				
			||||||
 | 
					
 | 
				
			||||||
# __version__ is deprecated and will be removed in a future version. Use
 | 
					# __version__ is deprecated and will be removed in a future version. Use
 | 
				
			||||||
# PIL.__version__ instead.
 | 
					# PIL.__version__ instead.
 | 
				
			||||||
| 
						 | 
					@ -46,7 +47,10 @@ class MpoImageFile(JpegImagePlugin.JpegImageFile):
 | 
				
			||||||
    def _open(self):
 | 
					    def _open(self):
 | 
				
			||||||
        self.fp.seek(0)  # prep the fp in order to pass the JPEG test
 | 
					        self.fp.seek(0)  # prep the fp in order to pass the JPEG test
 | 
				
			||||||
        JpegImagePlugin.JpegImageFile._open(self)
 | 
					        JpegImagePlugin.JpegImageFile._open(self)
 | 
				
			||||||
        self.mpinfo = self._getmp()
 | 
					        self._after_jpeg_open()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def _after_jpeg_open(self, mpheader=None):
 | 
				
			||||||
 | 
					        self.mpinfo = mpheader if mpheader is not None else self._getmp()
 | 
				
			||||||
        self.__framecount = self.mpinfo[0xB001]
 | 
					        self.__framecount = self.mpinfo[0xB001]
 | 
				
			||||||
        self.__mpoffsets = [mpent['DataOffset'] + self.info['mpoffset']
 | 
					        self.__mpoffsets = [mpent['DataOffset'] + self.info['mpoffset']
 | 
				
			||||||
                            for mpent in self.mpinfo[0xB002]]
 | 
					                            for mpent in self.mpinfo[0xB002]]
 | 
				
			||||||
| 
						 | 
					@ -78,6 +82,20 @@ class MpoImageFile(JpegImagePlugin.JpegImageFile):
 | 
				
			||||||
            return
 | 
					            return
 | 
				
			||||||
        self.fp = self.__fp
 | 
					        self.fp = self.__fp
 | 
				
			||||||
        self.offset = self.__mpoffsets[frame]
 | 
					        self.offset = self.__mpoffsets[frame]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        self.fp.seek(self.offset + 2)  # skip SOI marker
 | 
				
			||||||
 | 
					        if "parsed_exif" in self.info:
 | 
				
			||||||
 | 
					            del self.info["parsed_exif"]
 | 
				
			||||||
 | 
					        if i16(self.fp.read(2)) == 0xFFE1:  # APP1
 | 
				
			||||||
 | 
					            n = i16(self.fp.read(2))-2
 | 
				
			||||||
 | 
					            self.info["exif"] = ImageFile._safe_read(self.fp, n)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            exif = self._getexif()
 | 
				
			||||||
 | 
					            if 40962 in exif and 40963 in exif:
 | 
				
			||||||
 | 
					                self._size = (exif[40962], exif[40963])
 | 
				
			||||||
 | 
					        elif "exif" in self.info:
 | 
				
			||||||
 | 
					            del self.info["exif"]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        self.tile = [
 | 
					        self.tile = [
 | 
				
			||||||
            ("jpeg", (0, 0) + self.size, self.offset, (self.mode, ""))
 | 
					            ("jpeg", (0, 0) + self.size, self.offset, (self.mode, ""))
 | 
				
			||||||
        ]
 | 
					        ]
 | 
				
			||||||
| 
						 | 
					@ -95,6 +113,22 @@ class MpoImageFile(JpegImagePlugin.JpegImageFile):
 | 
				
			||||||
        finally:
 | 
					        finally:
 | 
				
			||||||
            self.__fp = None
 | 
					            self.__fp = None
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    @staticmethod
 | 
				
			||||||
 | 
					    def adopt(jpeg_instance, mpheader=None):
 | 
				
			||||||
 | 
					        """
 | 
				
			||||||
 | 
					        Transform the instance of JpegImageFile into
 | 
				
			||||||
 | 
					        an instance of MpoImageFile.
 | 
				
			||||||
 | 
					        After the call, the JpegImageFile is extended
 | 
				
			||||||
 | 
					        to be an MpoImageFile.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        This is essentially useful when opening a JPEG
 | 
				
			||||||
 | 
					        file that reveals itself as an MPO, to avoid
 | 
				
			||||||
 | 
					        double call to _open.
 | 
				
			||||||
 | 
					        """
 | 
				
			||||||
 | 
					        jpeg_instance.__class__ = MpoImageFile
 | 
				
			||||||
 | 
					        jpeg_instance._after_jpeg_open(mpheader)
 | 
				
			||||||
 | 
					        return jpeg_instance
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
# ---------------------------------------------------------------------
 | 
					# ---------------------------------------------------------------------
 | 
				
			||||||
# Registry stuff
 | 
					# Registry stuff
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -180,3 +180,5 @@ Image.register_open(PcxImageFile.format, PcxImageFile, _accept)
 | 
				
			||||||
Image.register_save(PcxImageFile.format, _save)
 | 
					Image.register_save(PcxImageFile.format, _save)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
Image.register_extension(PcxImageFile.format, ".pcx")
 | 
					Image.register_extension(PcxImageFile.format, ".pcx")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Image.register_mime(PcxImageFile.format, "image/x-pcx")
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -54,19 +54,24 @@ _MAGIC = b"\211PNG\r\n\032\n"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
_MODES = {
 | 
					_MODES = {
 | 
				
			||||||
    # supported bits/color combinations, and corresponding modes/rawmodes
 | 
					    # supported bits/color combinations, and corresponding modes/rawmodes
 | 
				
			||||||
 | 
					    # Greyscale
 | 
				
			||||||
    (1, 0):  ("1", "1"),
 | 
					    (1, 0):  ("1", "1"),
 | 
				
			||||||
    (2, 0):  ("L", "L;2"),
 | 
					    (2, 0):  ("L", "L;2"),
 | 
				
			||||||
    (4, 0):  ("L", "L;4"),
 | 
					    (4, 0):  ("L", "L;4"),
 | 
				
			||||||
    (8, 0):  ("L", "L"),
 | 
					    (8, 0):  ("L", "L"),
 | 
				
			||||||
    (16, 0): ("I", "I;16B"),
 | 
					    (16, 0): ("I", "I;16B"),
 | 
				
			||||||
 | 
					    # Truecolour
 | 
				
			||||||
    (8, 2):  ("RGB", "RGB"),
 | 
					    (8, 2):  ("RGB", "RGB"),
 | 
				
			||||||
    (16, 2): ("RGB", "RGB;16B"),
 | 
					    (16, 2): ("RGB", "RGB;16B"),
 | 
				
			||||||
 | 
					    # Indexed-colour
 | 
				
			||||||
    (1, 3):  ("P", "P;1"),
 | 
					    (1, 3):  ("P", "P;1"),
 | 
				
			||||||
    (2, 3):  ("P", "P;2"),
 | 
					    (2, 3):  ("P", "P;2"),
 | 
				
			||||||
    (4, 3):  ("P", "P;4"),
 | 
					    (4, 3):  ("P", "P;4"),
 | 
				
			||||||
    (8, 3):  ("P", "P"),
 | 
					    (8, 3):  ("P", "P"),
 | 
				
			||||||
 | 
					    # Greyscale with alpha
 | 
				
			||||||
    (8, 4):  ("LA", "LA"),
 | 
					    (8, 4):  ("LA", "LA"),
 | 
				
			||||||
    (16, 4): ("RGBA", "LA;16B"),  # LA;16B->LA not yet available
 | 
					    (16, 4): ("RGBA", "LA;16B"),  # LA;16B->LA not yet available
 | 
				
			||||||
 | 
					    # Truecolour with alpha
 | 
				
			||||||
    (8, 6):  ("RGBA", "RGBA"),
 | 
					    (8, 6):  ("RGBA", "RGBA"),
 | 
				
			||||||
    (16, 6): ("RGBA", "RGBA;16B"),
 | 
					    (16, 6): ("RGBA", "RGBA;16B"),
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
| 
						 | 
					@ -386,7 +391,7 @@ class PngStream(ChunkStream):
 | 
				
			||||||
                # otherwise, we have a byte string with one alpha value
 | 
					                # otherwise, we have a byte string with one alpha value
 | 
				
			||||||
                # for each palette entry
 | 
					                # for each palette entry
 | 
				
			||||||
                self.im_info["transparency"] = s
 | 
					                self.im_info["transparency"] = s
 | 
				
			||||||
        elif self.im_mode == "L":
 | 
					        elif self.im_mode in ("1", "L", "I"):
 | 
				
			||||||
            self.im_info["transparency"] = i16(s)
 | 
					            self.im_info["transparency"] = i16(s)
 | 
				
			||||||
        elif self.im_mode == "RGB":
 | 
					        elif self.im_mode == "RGB":
 | 
				
			||||||
            self.im_info["transparency"] = i16(s), i16(s[2:]), i16(s[4:])
 | 
					            self.im_info["transparency"] = i16(s), i16(s[2:]), i16(s[4:])
 | 
				
			||||||
| 
						 | 
					@ -691,8 +696,14 @@ class PngImageFile(ImageFile.ImageFile):
 | 
				
			||||||
    def _getexif(self):
 | 
					    def _getexif(self):
 | 
				
			||||||
        if "exif" not in self.info:
 | 
					        if "exif" not in self.info:
 | 
				
			||||||
            self.load()
 | 
					            self.load()
 | 
				
			||||||
        from .JpegImagePlugin import _getexif
 | 
					        if "exif" not in self.info:
 | 
				
			||||||
        return _getexif(self)
 | 
					            return None
 | 
				
			||||||
 | 
					        return dict(self.getexif())
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def getexif(self):
 | 
				
			||||||
 | 
					        if "exif" not in self.info:
 | 
				
			||||||
 | 
					            self.load()
 | 
				
			||||||
 | 
					        return ImageFile.ImageFile.getexif(self)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
# --------------------------------------------------------------------
 | 
					# --------------------------------------------------------------------
 | 
				
			||||||
| 
						 | 
					@ -707,6 +718,7 @@ _OUTMODES = {
 | 
				
			||||||
    "L":    ("L",       b'\x08\x00'),
 | 
					    "L":    ("L",       b'\x08\x00'),
 | 
				
			||||||
    "LA":   ("LA",      b'\x08\x04'),
 | 
					    "LA":   ("LA",      b'\x08\x04'),
 | 
				
			||||||
    "I":    ("I;16B",   b'\x10\x00'),
 | 
					    "I":    ("I;16B",   b'\x10\x00'),
 | 
				
			||||||
 | 
					    "I;16": ("I;16B",   b'\x10\x00'),
 | 
				
			||||||
    "P;1":  ("P;1",     b'\x01\x03'),
 | 
					    "P;1":  ("P;1",     b'\x01\x03'),
 | 
				
			||||||
    "P;2":  ("P;2",     b'\x02\x03'),
 | 
					    "P;2":  ("P;2",     b'\x02\x03'),
 | 
				
			||||||
    "P;4":  ("P;4",     b'\x04\x03'),
 | 
					    "P;4":  ("P;4",     b'\x04\x03'),
 | 
				
			||||||
| 
						 | 
					@ -840,7 +852,7 @@ def _save(im, fp, filename, chunk=putchunk):
 | 
				
			||||||
                transparency = max(0, min(255, transparency))
 | 
					                transparency = max(0, min(255, transparency))
 | 
				
			||||||
                alpha = b'\xFF' * transparency + b'\0'
 | 
					                alpha = b'\xFF' * transparency + b'\0'
 | 
				
			||||||
                chunk(fp, b"tRNS", alpha[:alpha_bytes])
 | 
					                chunk(fp, b"tRNS", alpha[:alpha_bytes])
 | 
				
			||||||
        elif im.mode == "L":
 | 
					        elif im.mode in ("1", "L", "I"):
 | 
				
			||||||
            transparency = max(0, min(65535, transparency))
 | 
					            transparency = max(0, min(65535, transparency))
 | 
				
			||||||
            chunk(fp, b"tRNS", o16(transparency))
 | 
					            chunk(fp, b"tRNS", o16(transparency))
 | 
				
			||||||
        elif im.mode == "RGB":
 | 
					        elif im.mode == "RGB":
 | 
				
			||||||
| 
						 | 
					@ -874,6 +886,8 @@ def _save(im, fp, filename, chunk=putchunk):
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    exif = im.encoderinfo.get("exif", im.info.get("exif"))
 | 
					    exif = im.encoderinfo.get("exif", im.info.get("exif"))
 | 
				
			||||||
    if exif:
 | 
					    if exif:
 | 
				
			||||||
 | 
					        if isinstance(exif, Image.Exif):
 | 
				
			||||||
 | 
					            exif = exif.tobytes(8)
 | 
				
			||||||
        if exif.startswith(b"Exif\x00\x00"):
 | 
					        if exif.startswith(b"Exif\x00\x00"):
 | 
				
			||||||
            exif = exif[6:]
 | 
					            exif = exif[6:]
 | 
				
			||||||
        chunk(fp, b"eXIf", exif)
 | 
					        chunk(fp, b"eXIf", exif)
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -164,6 +164,6 @@ def _save(im, fp, filename):
 | 
				
			||||||
Image.register_open(PpmImageFile.format, PpmImageFile, _accept)
 | 
					Image.register_open(PpmImageFile.format, PpmImageFile, _accept)
 | 
				
			||||||
Image.register_save(PpmImageFile.format, _save)
 | 
					Image.register_save(PpmImageFile.format, _save)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
Image.register_extensions(PpmImageFile.format, [".pbm", ".pgm", ".ppm"])
 | 
					Image.register_extensions(PpmImageFile.format, [".pbm", ".pgm", ".ppm", ".pnm"])
 | 
				
			||||||
 | 
					
 | 
				
			||||||
Image.register_mime(PpmImageFile.format, "image/x-portable-anymap")
 | 
					Image.register_mime(PpmImageFile.format, "image/x-portable-anymap")
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -208,7 +208,7 @@ class SpiderImageFile(ImageFile.ImageFile):
 | 
				
			||||||
 | 
					
 | 
				
			||||||
# given a list of filenames, return a list of images
 | 
					# given a list of filenames, return a list of images
 | 
				
			||||||
def loadImageSeries(filelist=None):
 | 
					def loadImageSeries(filelist=None):
 | 
				
			||||||
    """create a list of Image.images for use in montage"""
 | 
					    """create a list of :py:class:`~PIL.Image.Image` objects for use in a montage"""
 | 
				
			||||||
    if filelist is None or len(filelist) < 1:
 | 
					    if filelist is None or len(filelist) < 1:
 | 
				
			||||||
        return
 | 
					        return
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -226,4 +226,6 @@ def _save(im, fp, filename):
 | 
				
			||||||
Image.register_open(TgaImageFile.format, TgaImageFile)
 | 
					Image.register_open(TgaImageFile.format, TgaImageFile)
 | 
				
			||||||
Image.register_save(TgaImageFile.format, _save)
 | 
					Image.register_save(TgaImageFile.format, _save)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
Image.register_extension(TgaImageFile.format, ".tga")
 | 
					Image.register_extensions(TgaImageFile.format, [".tga", ".icb", ".vda", ".vst"])
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Image.register_mime(TgaImageFile.format, "image/x-tga")
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -263,10 +263,10 @@ OPEN_INFO = {
 | 
				
			||||||
    (II, 5, (1,), 1, (8, 8, 8, 8, 8, 8), (0, 0)): ("CMYK", "CMYKXX"),
 | 
					    (II, 5, (1,), 1, (8, 8, 8, 8, 8, 8), (0, 0)): ("CMYK", "CMYKXX"),
 | 
				
			||||||
    (MM, 5, (1,), 1, (8, 8, 8, 8, 8, 8), (0, 0)): ("CMYK", "CMYKXX"),
 | 
					    (MM, 5, (1,), 1, (8, 8, 8, 8, 8, 8), (0, 0)): ("CMYK", "CMYKXX"),
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    # JPEG compressed images handled by LibTiff and auto-converted to RGB
 | 
					    # JPEG compressed images handled by LibTiff and auto-converted to RGBX
 | 
				
			||||||
    # Minimal Baseline TIFF requires YCbCr images to have 3 SamplesPerPixel
 | 
					    # Minimal Baseline TIFF requires YCbCr images to have 3 SamplesPerPixel
 | 
				
			||||||
    (II, 6, (1,), 1, (8, 8, 8), ()): ("RGB", "RGB"),
 | 
					    (II, 6, (1,), 1, (8, 8, 8), ()): ("RGB", "RGBX"),
 | 
				
			||||||
    (MM, 6, (1,), 1, (8, 8, 8), ()): ("RGB", "RGB"),
 | 
					    (MM, 6, (1,), 1, (8, 8, 8), ()): ("RGB", "RGBX"),
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    (II, 8, (1,), 1, (8, 8, 8), ()): ("LAB", "LAB"),
 | 
					    (II, 8, (1,), 1, (8, 8, 8), ()): ("LAB", "LAB"),
 | 
				
			||||||
    (MM, 8, (1,), 1, (8, 8, 8), ()): ("LAB", "LAB"),
 | 
					    (MM, 8, (1,), 1, (8, 8, 8), ()): ("LAB", "LAB"),
 | 
				
			||||||
| 
						 | 
					@ -785,17 +785,12 @@ class ImageFileDirectory_v2(MutableMapping):
 | 
				
			||||||
            warnings.warn(str(msg))
 | 
					            warnings.warn(str(msg))
 | 
				
			||||||
            return
 | 
					            return
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def save(self, fp):
 | 
					    def tobytes(self, offset=0):
 | 
				
			||||||
 | 
					 | 
				
			||||||
        if fp.tell() == 0:  # skip TIFF header on subsequent pages
 | 
					 | 
				
			||||||
            # tiff header -- PIL always starts the first IFD at offset 8
 | 
					 | 
				
			||||||
            fp.write(self._prefix + self._pack("HL", 42, 8))
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        # FIXME What about tagdata?
 | 
					        # FIXME What about tagdata?
 | 
				
			||||||
        fp.write(self._pack("H", len(self._tags_v2)))
 | 
					        result = self._pack("H", len(self._tags_v2))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        entries = []
 | 
					        entries = []
 | 
				
			||||||
        offset = fp.tell() + len(self._tags_v2) * 12 + 4
 | 
					        offset = offset + len(result) + len(self._tags_v2) * 12 + 4
 | 
				
			||||||
        stripoffsets = None
 | 
					        stripoffsets = None
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        # pass 1: convert tags to binary format
 | 
					        # pass 1: convert tags to binary format
 | 
				
			||||||
| 
						 | 
					@ -844,18 +839,29 @@ class ImageFileDirectory_v2(MutableMapping):
 | 
				
			||||||
        for tag, typ, count, value, data in entries:
 | 
					        for tag, typ, count, value, data in entries:
 | 
				
			||||||
            if DEBUG > 1:
 | 
					            if DEBUG > 1:
 | 
				
			||||||
                print(tag, typ, count, repr(value), repr(data))
 | 
					                print(tag, typ, count, repr(value), repr(data))
 | 
				
			||||||
            fp.write(self._pack("HHL4s", tag, typ, count, value))
 | 
					            result += self._pack("HHL4s", tag, typ, count, value)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        # -- overwrite here for multi-page --
 | 
					        # -- overwrite here for multi-page --
 | 
				
			||||||
        fp.write(b"\0\0\0\0")  # end of entries
 | 
					        result += b"\0\0\0\0"  # end of entries
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        # pass 3: write auxiliary data to file
 | 
					        # pass 3: write auxiliary data to file
 | 
				
			||||||
        for tag, typ, count, value, data in entries:
 | 
					        for tag, typ, count, value, data in entries:
 | 
				
			||||||
            fp.write(data)
 | 
					            result += data
 | 
				
			||||||
            if len(data) & 1:
 | 
					            if len(data) & 1:
 | 
				
			||||||
                fp.write(b"\0")
 | 
					                result += b"\0"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        return offset
 | 
					        return result
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def save(self, fp):
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if fp.tell() == 0:  # skip TIFF header on subsequent pages
 | 
				
			||||||
 | 
					            # tiff header -- PIL always starts the first IFD at offset 8
 | 
				
			||||||
 | 
					            fp.write(self._prefix + self._pack("HL", 42, 8))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        offset = fp.tell()
 | 
				
			||||||
 | 
					        result = self.tobytes(offset)
 | 
				
			||||||
 | 
					        fp.write(result)
 | 
				
			||||||
 | 
					        return offset + len(result)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
ImageFileDirectory_v2._load_dispatch = _load_dispatch
 | 
					ImageFileDirectory_v2._load_dispatch = _load_dispatch
 | 
				
			||||||
| 
						 | 
					@ -985,7 +991,6 @@ class TiffImageFile(ImageFile.ImageFile):
 | 
				
			||||||
        self.__fp = self.fp
 | 
					        self.__fp = self.fp
 | 
				
			||||||
        self._frame_pos = []
 | 
					        self._frame_pos = []
 | 
				
			||||||
        self._n_frames = None
 | 
					        self._n_frames = None
 | 
				
			||||||
        self._is_animated = None
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
        if DEBUG:
 | 
					        if DEBUG:
 | 
				
			||||||
            print("*** TiffImageFile._open ***")
 | 
					            print("*** TiffImageFile._open ***")
 | 
				
			||||||
| 
						 | 
					@ -999,29 +1004,14 @@ class TiffImageFile(ImageFile.ImageFile):
 | 
				
			||||||
    def n_frames(self):
 | 
					    def n_frames(self):
 | 
				
			||||||
        if self._n_frames is None:
 | 
					        if self._n_frames is None:
 | 
				
			||||||
            current = self.tell()
 | 
					            current = self.tell()
 | 
				
			||||||
            try:
 | 
					            self._seek(len(self._frame_pos))
 | 
				
			||||||
                while True:
 | 
					            while self._n_frames is None:
 | 
				
			||||||
                    self._seek(self.tell() + 1)
 | 
					                self._seek(self.tell() + 1)
 | 
				
			||||||
            except EOFError:
 | 
					 | 
				
			||||||
                self._n_frames = self.tell() + 1
 | 
					 | 
				
			||||||
            self.seek(current)
 | 
					            self.seek(current)
 | 
				
			||||||
        return self._n_frames
 | 
					        return self._n_frames
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    @property
 | 
					    @property
 | 
				
			||||||
    def is_animated(self):
 | 
					    def is_animated(self):
 | 
				
			||||||
        if self._is_animated is None:
 | 
					 | 
				
			||||||
            if self._n_frames is not None:
 | 
					 | 
				
			||||||
                self._is_animated = self._n_frames != 1
 | 
					 | 
				
			||||||
            else:
 | 
					 | 
				
			||||||
                current = self.tell()
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                try:
 | 
					 | 
				
			||||||
                    self.seek(1)
 | 
					 | 
				
			||||||
                    self._is_animated = True
 | 
					 | 
				
			||||||
                except EOFError:
 | 
					 | 
				
			||||||
                    self._is_animated = False
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                self.seek(current)
 | 
					 | 
				
			||||||
        return self._is_animated
 | 
					        return self._is_animated
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def seek(self, frame):
 | 
					    def seek(self, frame):
 | 
				
			||||||
| 
						 | 
					@ -1053,10 +1043,13 @@ class TiffImageFile(ImageFile.ImageFile):
 | 
				
			||||||
                print("Loading tags, location: %s" % self.fp.tell())
 | 
					                print("Loading tags, location: %s" % self.fp.tell())
 | 
				
			||||||
            self.tag_v2.load(self.fp)
 | 
					            self.tag_v2.load(self.fp)
 | 
				
			||||||
            self.__next = self.tag_v2.next
 | 
					            self.__next = self.tag_v2.next
 | 
				
			||||||
 | 
					            if self.__next == 0:
 | 
				
			||||||
 | 
					                self._n_frames = frame + 1
 | 
				
			||||||
 | 
					            if len(self._frame_pos) == 1:
 | 
				
			||||||
 | 
					                self._is_animated = self.__next != 0
 | 
				
			||||||
            self.__frame += 1
 | 
					            self.__frame += 1
 | 
				
			||||||
        self.fp.seek(self._frame_pos[frame])
 | 
					        self.fp.seek(self._frame_pos[frame])
 | 
				
			||||||
        self.tag_v2.load(self.fp)
 | 
					        self.tag_v2.load(self.fp)
 | 
				
			||||||
        self.__next = self.tag_v2.next
 | 
					 | 
				
			||||||
        # fill the legacy tag/ifd entries
 | 
					        # fill the legacy tag/ifd entries
 | 
				
			||||||
        self.tag = self.ifd = ImageFileDirectory_v1.from_v2(self.tag_v2)
 | 
					        self.tag = self.ifd = ImageFileDirectory_v1.from_v2(self.tag_v2)
 | 
				
			||||||
        self.__frame = frame
 | 
					        self.__frame = frame
 | 
				
			||||||
| 
						 | 
					@ -1087,7 +1080,7 @@ class TiffImageFile(ImageFile.ImageFile):
 | 
				
			||||||
    def load_end(self):
 | 
					    def load_end(self):
 | 
				
			||||||
        # allow closing if we're on the first frame, there's no next
 | 
					        # allow closing if we're on the first frame, there's no next
 | 
				
			||||||
        # This is the ImageFile.load path only, libtiff specific below.
 | 
					        # This is the ImageFile.load path only, libtiff specific below.
 | 
				
			||||||
        if self.__frame == 0 and not self.__next:
 | 
					        if not self._is_animated:
 | 
				
			||||||
            self._close_exclusive_fp_after_loading = True
 | 
					            self._close_exclusive_fp_after_loading = True
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def _load_libtiff(self):
 | 
					    def _load_libtiff(self):
 | 
				
			||||||
| 
						 | 
					@ -1167,10 +1160,9 @@ class TiffImageFile(ImageFile.ImageFile):
 | 
				
			||||||
        self.tile = []
 | 
					        self.tile = []
 | 
				
			||||||
        self.readonly = 0
 | 
					        self.readonly = 0
 | 
				
			||||||
        # libtiff closed the fp in a, we need to close self.fp, if possible
 | 
					        # libtiff closed the fp in a, we need to close self.fp, if possible
 | 
				
			||||||
        if self._exclusive_fp:
 | 
					        if self._exclusive_fp and not self._is_animated:
 | 
				
			||||||
            if self.__frame == 0 and not self.__next:
 | 
					            self.fp.close()
 | 
				
			||||||
                self.fp.close()
 | 
					            self.fp = None  # might be shared
 | 
				
			||||||
                self.fp = None  # might be shared
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
        if err < 0:
 | 
					        if err < 0:
 | 
				
			||||||
            raise IOError(err)
 | 
					            raise IOError(err)
 | 
				
			||||||
| 
						 | 
					@ -1191,6 +1183,10 @@ class TiffImageFile(ImageFile.ImageFile):
 | 
				
			||||||
        # the specification
 | 
					        # the specification
 | 
				
			||||||
        photo = self.tag_v2.get(PHOTOMETRIC_INTERPRETATION, 0)
 | 
					        photo = self.tag_v2.get(PHOTOMETRIC_INTERPRETATION, 0)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        # old style jpeg compression images most certainly are YCbCr
 | 
				
			||||||
 | 
					        if self._compression == "tiff_jpeg":
 | 
				
			||||||
 | 
					            photo = 6
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        fillorder = self.tag_v2.get(FILLORDER, 1)
 | 
					        fillorder = self.tag_v2.get(FILLORDER, 1)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        if DEBUG:
 | 
					        if DEBUG:
 | 
				
			||||||
| 
						 | 
					@ -1257,11 +1253,11 @@ class TiffImageFile(ImageFile.ImageFile):
 | 
				
			||||||
        if xres and yres:
 | 
					        if xres and yres:
 | 
				
			||||||
            resunit = self.tag_v2.get(RESOLUTION_UNIT)
 | 
					            resunit = self.tag_v2.get(RESOLUTION_UNIT)
 | 
				
			||||||
            if resunit == 2:  # dots per inch
 | 
					            if resunit == 2:  # dots per inch
 | 
				
			||||||
                self.info["dpi"] = xres, yres
 | 
					                self.info["dpi"] = int(xres + 0.5), int(yres + 0.5)
 | 
				
			||||||
            elif resunit == 3:  # dots per centimeter. convert to dpi
 | 
					            elif resunit == 3:  # dots per centimeter. convert to dpi
 | 
				
			||||||
                self.info["dpi"] = xres * 2.54, yres * 2.54
 | 
					                self.info["dpi"] = int(xres * 2.54 + 0.5), int(yres * 2.54 + 0.5)
 | 
				
			||||||
            elif resunit is None:  # used to default to 1, but now 2)
 | 
					            elif resunit is None:  # used to default to 1, but now 2)
 | 
				
			||||||
                self.info["dpi"] = xres, yres
 | 
					                self.info["dpi"] = int(xres + 0.5), int(yres + 0.5)
 | 
				
			||||||
                # For backward compatibility,
 | 
					                # For backward compatibility,
 | 
				
			||||||
                # we also preserve the old behavior
 | 
					                # we also preserve the old behavior
 | 
				
			||||||
                self.info["resolution"] = xres, yres
 | 
					                self.info["resolution"] = xres, yres
 | 
				
			||||||
| 
						 | 
					@ -1472,8 +1468,8 @@ def _save(im, fp, filename):
 | 
				
			||||||
    dpi = im.encoderinfo.get("dpi")
 | 
					    dpi = im.encoderinfo.get("dpi")
 | 
				
			||||||
    if dpi:
 | 
					    if dpi:
 | 
				
			||||||
        ifd[RESOLUTION_UNIT] = 2
 | 
					        ifd[RESOLUTION_UNIT] = 2
 | 
				
			||||||
        ifd[X_RESOLUTION] = dpi[0]
 | 
					        ifd[X_RESOLUTION] = int(dpi[0] + 0.5)
 | 
				
			||||||
        ifd[Y_RESOLUTION] = dpi[1]
 | 
					        ifd[Y_RESOLUTION] = int(dpi[1] + 0.5)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    if bits != (1,):
 | 
					    if bits != (1,):
 | 
				
			||||||
        ifd[BITSPERSAMPLE] = bits
 | 
					        ifd[BITSPERSAMPLE] = bits
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -93,8 +93,9 @@ class WebPImageFile(ImageFile.ImageFile):
 | 
				
			||||||
        self.seek(0)
 | 
					        self.seek(0)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def _getexif(self):
 | 
					    def _getexif(self):
 | 
				
			||||||
        from .JpegImagePlugin import _getexif
 | 
					        if "exif" not in self.info:
 | 
				
			||||||
        return _getexif(self)
 | 
					            return None
 | 
				
			||||||
 | 
					        return dict(self.getexif())
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    @property
 | 
					    @property
 | 
				
			||||||
    def n_frames(self):
 | 
					    def n_frames(self):
 | 
				
			||||||
| 
						 | 
					@ -186,7 +187,7 @@ def _save_all(im, fp, filename):
 | 
				
			||||||
    # will preserve non-alpha modes
 | 
					    # will preserve non-alpha modes
 | 
				
			||||||
    total = 0
 | 
					    total = 0
 | 
				
			||||||
    for ims in [im]+append_images:
 | 
					    for ims in [im]+append_images:
 | 
				
			||||||
        total += 1 if not hasattr(ims, "n_frames") else ims.n_frames
 | 
					        total += getattr(ims, "n_frames", 1)
 | 
				
			||||||
    if total == 1:
 | 
					    if total == 1:
 | 
				
			||||||
        _save(im, fp, filename)
 | 
					        _save(im, fp, filename)
 | 
				
			||||||
        return
 | 
					        return
 | 
				
			||||||
| 
						 | 
					@ -216,6 +217,8 @@ def _save_all(im, fp, filename):
 | 
				
			||||||
    method = im.encoderinfo.get("method", 0)
 | 
					    method = im.encoderinfo.get("method", 0)
 | 
				
			||||||
    icc_profile = im.encoderinfo.get("icc_profile", "")
 | 
					    icc_profile = im.encoderinfo.get("icc_profile", "")
 | 
				
			||||||
    exif = im.encoderinfo.get("exif", "")
 | 
					    exif = im.encoderinfo.get("exif", "")
 | 
				
			||||||
 | 
					    if isinstance(exif, Image.Exif):
 | 
				
			||||||
 | 
					        exif = exif.tobytes()
 | 
				
			||||||
    xmp = im.encoderinfo.get("xmp", "")
 | 
					    xmp = im.encoderinfo.get("xmp", "")
 | 
				
			||||||
    if allow_mixed:
 | 
					    if allow_mixed:
 | 
				
			||||||
        lossless = False
 | 
					        lossless = False
 | 
				
			||||||
| 
						 | 
					@ -254,10 +257,7 @@ def _save_all(im, fp, filename):
 | 
				
			||||||
    try:
 | 
					    try:
 | 
				
			||||||
        for ims in [im]+append_images:
 | 
					        for ims in [im]+append_images:
 | 
				
			||||||
            # Get # of frames in this image
 | 
					            # Get # of frames in this image
 | 
				
			||||||
            if not hasattr(ims, "n_frames"):
 | 
					            nfr = getattr(ims, "n_frames", 1)
 | 
				
			||||||
                nfr = 1
 | 
					 | 
				
			||||||
            else:
 | 
					 | 
				
			||||||
                nfr = ims.n_frames
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
            for idx in range(nfr):
 | 
					            for idx in range(nfr):
 | 
				
			||||||
                ims.seek(idx)
 | 
					                ims.seek(idx)
 | 
				
			||||||
| 
						 | 
					@ -318,6 +318,8 @@ def _save(im, fp, filename):
 | 
				
			||||||
    quality = im.encoderinfo.get("quality", 80)
 | 
					    quality = im.encoderinfo.get("quality", 80)
 | 
				
			||||||
    icc_profile = im.encoderinfo.get("icc_profile", "")
 | 
					    icc_profile = im.encoderinfo.get("icc_profile", "")
 | 
				
			||||||
    exif = im.encoderinfo.get("exif", "")
 | 
					    exif = im.encoderinfo.get("exif", "")
 | 
				
			||||||
 | 
					    if isinstance(exif, Image.Exif):
 | 
				
			||||||
 | 
					        exif = exif.tobytes()
 | 
				
			||||||
    xmp = im.encoderinfo.get("xmp", "")
 | 
					    xmp = im.encoderinfo.get("xmp", "")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    if im.mode not in _VALID_WEBP_LEGACY_MODES:
 | 
					    if im.mode not in _VALID_WEBP_LEGACY_MODES:
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||