|  | @ -52,7 +52,7 @@ install: | |||
|       } | ||||
|       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 | ||||
|         $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. | ||||
| - Create a branch from master. | ||||
| - 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. | ||||
| 
 | ||||
| ### Guidelines | ||||
|  |  | |||
							
								
								
									
										27
									
								
								.travis.yml
									
									
									
									
									
								
							
							
						
						|  | @ -18,40 +18,25 @@ matrix: | |||
|       env: LINT="true" | ||||
|     - python: "pypy2.7-6.0" | ||||
|       name: "PyPy2 Xenial" | ||||
|       dist: xenial | ||||
|     - python: "pypy3.5-6.0" | ||||
|       name: "PyPy3 Xenial" | ||||
|       dist: xenial | ||||
|     - python: '3.7' | ||||
|       name: "3.7 Xenial" | ||||
|     - python: '2.7' | ||||
|       name: "2.7 Xenial" | ||||
|     - python: '2.7' | ||||
|       name: "2.7 Trusty" | ||||
|       dist: trusty | ||||
|     - python:  "2.7_with_system_site_packages" # For PyQt4 | ||||
|       name: "2.7_with_system_site_packages Xenial" | ||||
|       services: xvfb | ||||
|     - python:  "2.7_with_system_site_packages" # For PyQt4 | ||||
|       name: "2.7_with_system_site_packages Trusty" | ||||
|       dist: trusty | ||||
|     - python: '3.6' | ||||
|       name: "3.6 Xenial" | ||||
|     - python: '3.6' | ||||
|       name: "3.6 Trusty PYTHONOPTIMIZE=1" | ||||
|       dist: trusty | ||||
|       name: "3.6 Xenial PYTHONOPTIMIZE=1" | ||||
|       env: PYTHONOPTIMIZE=1 | ||||
|     - python: '3.5' | ||||
|       name: "3.5 Xenial" | ||||
|     - python: '3.5' | ||||
|       name: "3.5 Trusty PYTHONOPTIMIZE=2" | ||||
|       dist: trusty | ||||
|       name: "3.5 Xenial PYTHONOPTIMIZE=2" | ||||
|       env: PYTHONOPTIMIZE=2 | ||||
|     - python: "3.8-dev" | ||||
|       name: "3.8-dev Xenial" | ||||
|     - env: DOCKER="alpine" DOCKER_TAG="master" | ||||
|     - env: DOCKER="arch" DOCKER_TAG="master" # contains PyQt5 | ||||
|     - env: DOCKER="ubuntu-trusty-x86" DOCKER_TAG="master" | ||||
|     - env: DOCKER="ubuntu-xenial-amd64" DOCKER_TAG="master" | ||||
|     - env: DOCKER="debian-stretch-x86" DOCKER_TAG="master" | ||||
|     - env: DOCKER="centos-6-amd64" DOCKER_TAG="master" | ||||
|  | @ -75,14 +60,6 @@ install: | |||
|       .travis/install.sh; | ||||
|     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: | ||||
| - | | ||||
|   if [ "$LINT" == "true" ]; then | ||||
|  |  | |||
							
								
								
									
										62
									
								
								CHANGES.rst
									
									
									
									
									
								
							
							
						
						|  | @ -2,12 +2,72 @@ | |||
| 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 | ||||
|   [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] | ||||
| 
 | ||||
|  |  | |||
							
								
								
									
										49
									
								
								README.rst
									
									
									
									
									
								
							
							
						
						|  | @ -20,6 +20,30 @@ Pillow is the friendly PIL fork by `Alex Clark and Contributors <https://github. | |||
|     * - social | ||||
|       - |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 | ||||
|    :target: https://pillow.readthedocs.io/?badge=latest | ||||
|    :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 | ||||
|    :alt: AppVeyor CI build status (Windows) | ||||
| 
 | ||||
| .. |coverage| image:: https://coveralls.io/repos/python-pillow/Pillow/badge.svg?branch=master&service=github | ||||
|    :target: https://coveralls.io/github/python-pillow/Pillow?branch=master | ||||
| .. |coverage| image:: https://codecov.io/gh/python-pillow/Pillow/branch/master/graph/badge.svg | ||||
|    :target: https://codecov.io/gh/python-pillow/Pillow | ||||
|    :alt: Code coverage | ||||
| 
 | ||||
| .. |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 | ||||
|    :target: 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 | ||||
| 
 | ||||
| * [ ] 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_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/sugarshack_frame_size.mpo
									
									
									
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 117 KiB | 
|  | @ -71,6 +71,27 @@ class TestFileBmp(PillowTestCase): | |||
|         self.assertEqual(im.size, reloaded.size) | ||||
|         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): | ||||
|         # test for #1293, Imagegrab returning Unsupported Bitfields Format | ||||
|         im = Image.open('Tests/images/clipboard.dib') | ||||
|  |  | |||
|  | @ -524,6 +524,27 @@ class TestFileJpeg(PillowTestCase): | |||
|         reloaded.load() | ||||
|         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): | ||||
|         # Arrange | ||||
|         # This Photoshop CC 2017 image has DPI in EXIF not metadata | ||||
|  | @ -590,6 +611,15 @@ class TestFileJpeg(PillowTestCase): | |||
|         # Act / Assert | ||||
|         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") | ||||
| class TestFileCloseW32(PillowTestCase): | ||||
|  |  | |||
|  | @ -55,6 +55,27 @@ class TestFileMpo(PillowTestCase): | |||
|             self.assertEqual(info[296], 2) | ||||
|             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): | ||||
|         for test_file in test_files: | ||||
|             im = Image.open(test_file) | ||||
|  |  | |||
|  | @ -291,30 +291,32 @@ class TestFilePng(PillowTestCase): | |||
|         self.assert_image(im, "RGBA", (10, 10)) | ||||
|         self.assertEqual(im.getcolors(), [(100, (0, 0, 0, 0))]) | ||||
| 
 | ||||
|     def test_save_l_transparency(self): | ||||
|         # There are 559 transparent pixels in l_trns.png. | ||||
|         num_transparent = 559 | ||||
|     def test_save_greyscale_transparency(self): | ||||
|         for mode, num_transparent in { | ||||
|             "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 = Image.open(in_file) | ||||
|         self.assertEqual(im.mode, "L") | ||||
|         self.assertEqual(im.info["transparency"], 255) | ||||
|             im_rgba = im.convert('RGBA') | ||||
|             self.assertEqual( | ||||
|                 im_rgba.getchannel("A").getcolors()[0][0], num_transparent) | ||||
| 
 | ||||
|         im_rgba = im.convert('RGBA') | ||||
|         self.assertEqual( | ||||
|             im_rgba.getchannel("A").getcolors()[0][0], num_transparent) | ||||
|             test_file = self.tempfile("temp.png") | ||||
|             im.save(test_file) | ||||
| 
 | ||||
|         test_file = self.tempfile("temp.png") | ||||
|         im.save(test_file) | ||||
|             test_im = Image.open(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) | ||||
|         self.assertEqual(test_im.mode, "L") | ||||
|         self.assertEqual(test_im.info["transparency"], 255) | ||||
|         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) | ||||
|             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): | ||||
|         in_file = "Tests/images/caption_6_33_22.png" | ||||
|  | @ -387,6 +389,24 @@ class TestFilePng(PillowTestCase): | |||
|         im = roundtrip(im, 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): | ||||
|         # Check text roundtripping | ||||
| 
 | ||||
|  |  | |||
|  | @ -126,6 +126,30 @@ class TestFileTiff(PillowTestCase): | |||
|         im._setup() | ||||
|         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): | ||||
|         b = BytesIO() | ||||
|         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.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) | ||||
|             self.assertEqual(im.n_frames, n_frames) | ||||
|             self.assertEqual(im.is_animated, n_frames != 1) | ||||
|  | @ -263,6 +282,11 @@ class TestFileTiff(PillowTestCase): | |||
|         self.assertEqual(im.size, (10, 10)) | ||||
|         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.load() | ||||
|         self.assertEqual(im.size, (20, 20)) | ||||
|  |  | |||
|  | @ -45,6 +45,15 @@ class TestFileWmf(PillowTestCase): | |||
|         # Restore the state before this test | ||||
|         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): | ||||
|         im = hopper() | ||||
| 
 | ||||
|  |  | |||
|  | @ -3,6 +3,8 @@ from .helper import unittest, PillowTestCase, hopper | |||
| from PIL import Image | ||||
| from PIL._util import py3 | ||||
| import os | ||||
| import sys | ||||
| import shutil | ||||
| 
 | ||||
| 
 | ||||
| class TestImage(PillowTestCase): | ||||
|  | @ -121,6 +123,16 @@ class TestImage(PillowTestCase): | |||
|         im.paste(0, (0, 0, 100, 100)) | ||||
|         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): | ||||
|         im = Image.new("L", (10, 10)) | ||||
|         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_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): | ||||
|         # https://github.com/python-pillow/Pillow/issues/835 | ||||
|         # Arrange | ||||
|  |  | |||
|  | @ -12,7 +12,8 @@ class TestImageConvert(PillowTestCase): | |||
|             self.assertEqual(out.mode, mode) | ||||
|             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: | ||||
|             im = hopper(mode) | ||||
|  |  | |||
|  | @ -42,7 +42,7 @@ class TestImageMode(PillowTestCase): | |||
|             self.assertEqual(signature, result) | ||||
|         check("1", "L", "L", 1, ("1",)) | ||||
|         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("F", "L", "F", 1, ("F",)) | ||||
|         check("RGB", "RGB", "L", 3, ("R", "G", "B")) | ||||
|  |  | |||
|  | @ -28,6 +28,13 @@ class TestImagePutAlpha(PillowTestCase): | |||
|         self.assertEqual(im.mode, 'LA') | ||||
|         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)) | ||||
|         self.assertEqual(im.getpixel((0, 0)), (1, 2, 3)) | ||||
| 
 | ||||
|  |  | |||
|  | @ -14,8 +14,10 @@ class TestImagePutPalette(PillowTestCase): | |||
|                 return im.mode, p[:10] | ||||
|             return im.mode | ||||
|         self.assertRaises(ValueError, palette, "1") | ||||
|         self.assertEqual(palette("L"), ("P", [0, 1, 2, 3, 4, 5, 6, 7, 8, 9])) | ||||
|         self.assertEqual(palette("P"), ("P", [0, 1, 2, 3, 4, 5, 6, 7, 8, 9])) | ||||
|         for mode in ["L", "LA", "P", "PA"]: | ||||
|             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, "F") | ||||
|         self.assertRaises(ValueError, palette, "RGB") | ||||
|  |  | |||
|  | @ -38,9 +38,10 @@ class TestImageQuantize(PillowTestCase): | |||
| 
 | ||||
|     def test_rgba_quantize(self): | ||||
|         image = hopper('RGBA') | ||||
|         image.quantize() | ||||
|         self.assertRaises(ValueError, image.quantize, method=0) | ||||
| 
 | ||||
|         self.assertEqual(image.quantize().convert().mode, "RGBA") | ||||
| 
 | ||||
|     def test_quantize(self): | ||||
|         image = Image.open('Tests/images/caption_6_33_22.png').convert('RGB') | ||||
|         converted = image.quantize() | ||||
|  |  | |||
|  | @ -309,7 +309,7 @@ class TestImageCms(PillowTestCase): | |||
|             2: (False, False, True), | ||||
|             3: (False, False, True) | ||||
|         }) | ||||
|         self.assertEqual(p.color_space, 'RGB') | ||||
| 
 | ||||
|         self.assertIsNone(p.colorant_table) | ||||
|         self.assertIsNone(p.colorant_table_out) | ||||
|         self.assertIsNone(p.colorimetric_intent) | ||||
|  | @ -361,16 +361,9 @@ class TestImageCms(PillowTestCase): | |||
|             (5000.722328847392,)) | ||||
|         self.assertEqual(p.model, | ||||
|                          'IEC 61966-2-1 Default RGB Colour Space - sRGB') | ||||
|         self.assertEqual(p.pcs, 'XYZ') | ||||
| 
 | ||||
|         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( | ||||
|             p.profile_description, 'sRGB IEC61966-2-1 black scaled') | ||||
|         self.assertEqual( | ||||
|  | @ -393,6 +386,40 @@ class TestImageCms(PillowTestCase): | |||
|                          'Reference Viewing Condition in IEC 61966-2-1') | ||||
|         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): | ||||
|         """ 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 | ||||
| 
 | ||||
|  | @ -6,6 +6,12 @@ from PIL import Image | |||
| from PIL import ImageFile | ||||
| from PIL import EpsImagePlugin | ||||
| 
 | ||||
| try: | ||||
|     from PIL import _webp | ||||
|     HAVE_WEBP = True | ||||
| except ImportError: | ||||
|     HAVE_WEBP = False | ||||
| 
 | ||||
| 
 | ||||
| codecs = dir(Image.core) | ||||
| 
 | ||||
|  | @ -233,3 +239,97 @@ class TestPyDecoder(PillowTestCase): | |||
|         im = MockImageFile(buf) | ||||
|         self.assertIsNone(im.format) | ||||
|         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, | ||||
|         }) | ||||
|  |  | |||
|  | @ -1,7 +1,13 @@ | |||
| from .helper import PillowTestCase, hopper | ||||
| 
 | ||||
| from PIL import ImageOps | ||||
| from PIL import Image | ||||
| from PIL import ImageOps | ||||
| 
 | ||||
| try: | ||||
|     from PIL import _webp | ||||
|     HAVE_WEBP = True | ||||
| except ImportError: | ||||
|     HAVE_WEBP = False | ||||
| 
 | ||||
| 
 | ||||
| class TestImageOps(PillowTestCase): | ||||
|  | @ -62,6 +68,9 @@ class TestImageOps(PillowTestCase): | |||
|         ImageOps.solarize(hopper("L")) | ||||
|         ImageOps.solarize(hopper("RGB")) | ||||
| 
 | ||||
|         ImageOps.exif_transpose(hopper("L")) | ||||
|         ImageOps.exif_transpose(hopper("RGB")) | ||||
| 
 | ||||
|     def test_1pxfit(self): | ||||
|         # Division by zero in equalize if image is 1 pixel high | ||||
|         newimg = ImageOps.fit(hopper("RGB").resize((1, 1)), (35, 35)) | ||||
|  | @ -218,3 +227,36 @@ class TestImageOps(PillowTestCase): | |||
|                                        (0, 127, 0), | ||||
|                                        threshold=1, | ||||
|                                        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') | ||||
|         self.assertEqual(img.mode, '1') | ||||
|         arr_back = numpy.array(img) | ||||
|         # numpy 1.8 and earlier return this as a boolean. (trusty/precise) | ||||
|         if arr_back.dtype == numpy.bool: | ||||
|             arr_bool = numpy.array([[1, 0, 0, 1, 0], [0, 1, 0, 0, 0]], numpy.bool) | ||||
|             numpy.testing.assert_array_equal(arr_bool, arr_back) | ||||
|         else: | ||||
|             numpy.testing.assert_array_equal(arr, arr_back) | ||||
|         numpy.testing.assert_array_equal(arr, arr_back) | ||||
| 
 | ||||
|     def test_save_tiff_uint16(self): | ||||
|         # Tests that we're getting the pixel value in the right byte order. | ||||
|  |  | |||
|  | @ -10,7 +10,6 @@ except ImportError: | |||
| 
 | ||||
| @unittest.skipIf(pyroma is None, "Pyroma is not installed") | ||||
| class TestPyroma(PillowTestCase): | ||||
| 
 | ||||
|     def test_pyroma(self): | ||||
|         # Arrange | ||||
|         data = pyroma.projectdata.get_data(".") | ||||
|  | @ -19,12 +18,13 @@ class TestPyroma(PillowTestCase): | |||
|         rating = pyroma.ratings.rate(data) | ||||
| 
 | ||||
|         # Assert | ||||
|         if 'rc' in __version__: | ||||
|             # Pyroma needs to chill about RC versions | ||||
|             # and not kill all our tests. | ||||
|             self.assertEqual(rating, (9, [ | ||||
|                 "The package's version number does not comply with PEP-386."])) | ||||
|         if "rc" in __version__: | ||||
|             # Pyroma needs to chill about RC versions and not kill all our tests. | ||||
|             self.assertEqual( | ||||
|                 rating, | ||||
|                 (9, ["The package's version number does not comply with PEP-386."]), | ||||
|             ) | ||||
| 
 | ||||
|         else: | ||||
|             # Should have a perfect score | ||||
|             self.assertEqual(rating, (10, [])) | ||||
|             # Should have a near-perfect score | ||||
|             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 | ||||
|         self.assert_image_equal(result, expected.convert('RGB')) | ||||
| 
 | ||||
|     def test_sanity_1(self): | ||||
|         self.roundtrip(hopper('1')) | ||||
| 
 | ||||
|     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')) | ||||
|     def test_sanity(self): | ||||
|         for mode in ('1', 'RGB', 'RGBA', 'L', 'P'): | ||||
|             self.roundtrip(hopper(mode)) | ||||
|  |  | |||
|  | @ -21,11 +21,6 @@ jobs: | |||
|     docker: 'arch' | ||||
|     name:   'arch' | ||||
| 
 | ||||
| - template: .azure-pipelines/jobs/test-docker.yml | ||||
|   parameters: | ||||
|     docker: 'ubuntu-trusty-x86' | ||||
|     name:   'ubuntu_trusty_x86' | ||||
| 
 | ||||
| - template: .azure-pipelines/jobs/test-docker.yml | ||||
|   parameters: | ||||
|     docker: 'ubuntu-xenial-amd64' | ||||
|  |  | |||
|  | @ -79,6 +79,26 @@ PILLOW_VERSION constant | |||
| ``PILLOW_VERSION`` has been deprecated and will be removed in the next | ||||
| 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 | ||||
| ---------------- | ||||
| 
 | ||||
|  |  | |||
|  | @ -104,11 +104,11 @@ The :py:meth:`~PIL.Image.Image.open` method sets the following | |||
| Reading sequences | ||||
| ~~~~~~~~~~~~~~~~~ | ||||
| 
 | ||||
| The GIF loader supports the :py:meth:`~file.seek` and :py:meth:`~file.tell` | ||||
| methods. You can combine these methods to seek to the next frame | ||||
| (``im.seek(im.tell() + 1)``). | ||||
| The GIF loader supports the :py:meth:`~PIL.Image.Image.seek` and | ||||
| :py:meth:`~PIL.Image.Image.tell` methods. You can combine these methods | ||||
| 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 | ||||
| ~~~~~~ | ||||
|  | @ -464,8 +464,9 @@ Pillow identifies, reads, and writes PNG files containing ``1``, ``L``, ``LA``, | |||
| v1.1.7. | ||||
| 
 | ||||
| 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 | ||||
| :py:meth:`~PIL.Image.Image.load` has been called. | ||||
| image formats, EXIF data is not guaranteed to be present in | ||||
| :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 | ||||
| :py:attr:`~PIL.Image.Image.info` properties, when appropriate: | ||||
|  | @ -490,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, | ||||
|     or a byte string with alpha values for each palette entry. | ||||
| 
 | ||||
|     For ``L`` and ``RGB`` images, the color that represents full transparent | ||||
|     pixels in this image. | ||||
|     For ``1``, ``L``, ``I`` and ``RGB`` images, the color that represents | ||||
|     full transparent pixels in this 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 | ||||
| compressed chunks are limited to a decompressed size of | ||||
| ``PngImagePlugin.MAX_TEXT_CHUNK``, by default 1MB, to prevent | ||||
|  | @ -511,8 +512,8 @@ The :py:meth:`~PIL.Image.Image.save` method supports the following options: | |||
|     encoder settings. | ||||
| 
 | ||||
| **transparency** | ||||
|     For ``P``, ``L``, and ``RGB`` images, this option controls what | ||||
|     color image to mark as transparent. | ||||
|     For ``P``, ``1``, ``L``, ``I``, and ``RGB`` images, this option controls | ||||
|     what color from the image to mark as transparent. | ||||
| 
 | ||||
|     For ``P`` images, this can be a either the palette index, | ||||
|     or a byte string with alpha values for each palette entry. | ||||
|  | @ -569,7 +570,7 @@ Pillow reads and writes SPIDER image files of 32-bit floating point data | |||
| ("F;32F"). | ||||
| 
 | ||||
| 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. | ||||
| 
 | ||||
| The :py:meth:`~PIL.Image.Image.open` method sets the following attributes: | ||||
|  | @ -580,7 +581,7 @@ The :py:meth:`~PIL.Image.Image.open` method sets the following attributes: | |||
| **istack** | ||||
|     Set to 1 if the file is an image stack, else 0. | ||||
| 
 | ||||
| **nimages** | ||||
| **n_frames** | ||||
|     Set to the number of images in the stack. | ||||
| 
 | ||||
| A convenience method, :py:meth:`~PIL.Image.Image.convert2byte`, is provided for | ||||
|  | @ -664,6 +665,17 @@ numbers are returned as a tuple of ``(numerator, denominator)``. | |||
| 
 | ||||
|     .. 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 | ||||
| ~~~~~~~~~~~~~~~~~~ | ||||
|  | @ -851,7 +863,7 @@ is commonly used in fax applications. The DCX decoder can read files containing | |||
| ``1``, ``L``, ``P``, or ``RGB`` data. | ||||
| 
 | ||||
| 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 | ||||
|  | @ -943,8 +955,8 @@ MIC | |||
| ^^^ | ||||
| 
 | ||||
| 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 | ||||
| :py:meth:`~file.tell` to read other sprites from the file. | ||||
| the first sprite in the file is loaded. You can use :py:meth:`~PIL.Image.Image.seek` and | ||||
| :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. | ||||
| 
 | ||||
|  | @ -952,7 +964,7 @@ MPO | |||
| ^^^ | ||||
| 
 | ||||
| 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 | ||||
| zero-indexed and random access is supported. | ||||
| 
 | ||||
|  |  | |||
|  | @ -30,8 +30,8 @@ Pillow is the friendly PIL fork by `Alex Clark and Contributors <https://github. | |||
|    :target: https://pypi.org/project/Pillow/ | ||||
|    :alt: Number of PyPI downloads | ||||
| 
 | ||||
| .. image:: https://coveralls.io/repos/python-pillow/Pillow/badge.svg?branch=master&service=github | ||||
|    :target: https://coveralls.io/github/python-pillow/Pillow?branch=master | ||||
| .. image:: https://codecov.io/gh/python-pillow/Pillow/branch/master/graph/badge.svg | ||||
|    :target: https://codecov.io/gh/python-pillow/Pillow | ||||
|    :alt: Code coverage | ||||
| 
 | ||||
| .. toctree:: | ||||
|  |  | |||
|  | @ -151,7 +151,7 @@ Many of Pillow's features require external libraries: | |||
| * **littlecms** provides color management | ||||
| 
 | ||||
|   * 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. | ||||
| 
 | ||||
|  | @ -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 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 | ||||
| 
 | ||||
|  | @ -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                 | | ||||
| |                                  | 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            | | ||||
| |                                  +-------------------------------+-----------------------+ | ||||
| |                                  | PyPy, 3.7/MinGW               |x86                    | | ||||
|  |  | |||
|  | @ -132,21 +132,21 @@ can be easily displayed in a chromaticity diagram, for example). | |||
| 
 | ||||
|     .. 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). | ||||
| 
 | ||||
|         :type: :py:class:`unicode` or ``None`` | ||||
| 
 | ||||
|     .. 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). | ||||
| 
 | ||||
|         :type: :py:class:`unicode` or ``None`` | ||||
| 
 | ||||
|     .. 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). | ||||
| 
 | ||||
|         :type: :py:class:`unicode` or ``None`` | ||||
|  | @ -269,14 +269,14 @@ can be easily displayed in a chromaticity diagram, for example). | |||
| 
 | ||||
|     .. 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). | ||||
| 
 | ||||
|         :type: :py:class:`unicode` or ``None`` | ||||
| 
 | ||||
|     .. 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 | ||||
|         version 4. | ||||
|  |  | |||
|  | @ -99,6 +99,24 @@ version. | |||
| 
 | ||||
| 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 | ||||
| ^^^^^^^^^^^^^^^^^^^^^^ | ||||
| 
 | ||||
|  | @ -131,7 +149,7 @@ 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``. | ||||
| This parameter takes the same values used in :py:meth:`~PIL.Image.Image.convert`. | ||||
| 
 | ||||
| New language parameter | ||||
| ^^^^^^^^^^^^^^^^^^^^^^ | ||||
|  | @ -147,12 +165,26 @@ language-specific glyphs and ligatures from the font: | |||
| * ``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 | ||||
| formats, EXIF data is not guaranteed to have been read until | ||||
| :py:meth:`~PIL.Image.Image.load` has been called. | ||||
| formats, EXIF data is not guaranteed to be present in :py:attr:`~PIL.Image.Image.info` | ||||
| until :py:meth:`~PIL.Image.Image.load` has been called. | ||||
| 
 | ||||
| Other Changes | ||||
| ============= | ||||
|  | @ -172,3 +204,9 @@ 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', | ||||
|           classifiers=[ | ||||
|               "Development Status :: 6 - Mature", | ||||
|               "Topic :: Multimedia :: Graphics", | ||||
|               "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", | ||||
|               "License :: OSI Approved :: Historical Permission Notice and Disclaimer (HPND)",  # noqa: E501 | ||||
|               "Programming Language :: Python :: 2", | ||||
|               "Programming Language :: Python :: 2.7", | ||||
|               "Programming Language :: Python :: 3", | ||||
|  | @ -779,6 +774,11 @@ try: | |||
|               "Programming Language :: Python :: 3.7", | ||||
|               "Programming Language :: Python :: Implementation :: CPython", | ||||
|               "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.*", | ||||
|           cmdclass={"build_ext": pil_build_ext}, | ||||
|  | @ -789,7 +789,6 @@ try: | |||
|           packages=["PIL"], | ||||
|           package_dir={'': 'src'}, | ||||
|           keywords=["Imaging", ], | ||||
|           license='Standard PIL License', | ||||
|           zip_safe=not (debug_build() or PLATFORM_MINGW), ) | ||||
| except RequiredDependencyException as err: | ||||
|     msg = """ | ||||
|  |  | |||
|  | @ -27,7 +27,6 @@ | |||
| from . import Image, ImageFile, ImagePalette | ||||
| from ._binary import i8, i16le as i16, i32le as i32, \ | ||||
|                      o8, o16le as o16, o32le as o32 | ||||
| import math | ||||
| 
 | ||||
| # __version__ is deprecated and will be removed in a future version. Use | ||||
| # PIL.__version__ instead. | ||||
|  | @ -121,8 +120,7 @@ class BmpImageFile(ImageFile.ImageFile): | |||
|             file_info['colors'] = i32(header_data[28:32]) | ||||
|             file_info['palette_padding'] = 4 | ||||
|             self.info["dpi"] = tuple( | ||||
|                 map(lambda x: int(math.ceil(x / 39.3701)), | ||||
|                     file_info['pixels_per_meter'])) | ||||
|                 int(x / 39.3701 + 0.5) for x in file_info['pixels_per_meter']) | ||||
|             if file_info['compression'] == self.BITFIELDS: | ||||
|                 if len(header_data) >= 52: | ||||
|                     for idx, mask in enumerate(['r_mask', | ||||
|  | @ -311,7 +309,7 @@ def _save(im, fp, filename, bitmap_header=True): | |||
|     dpi = info.get("dpi", (96, 96)) | ||||
| 
 | ||||
|     # 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) | ||||
|     header = 40  # or 64 for OS/2 version 2 | ||||
|  |  | |||
|  | @ -32,14 +32,12 @@ class GimpPaletteFile(object): | |||
|         if fp.readline()[:12] != b"GIMP Palette": | ||||
|             raise SyntaxError("not a GIMP palette file") | ||||
| 
 | ||||
|         i = 0 | ||||
| 
 | ||||
|         while i <= 255: | ||||
|         for i in range(256): | ||||
| 
 | ||||
|             s = fp.readline() | ||||
| 
 | ||||
|             if not s: | ||||
|                 break | ||||
| 
 | ||||
|             # skip fields and comment lines | ||||
|             if re.match(br"\w+:|#", s): | ||||
|                 continue | ||||
|  | @ -50,10 +48,7 @@ class GimpPaletteFile(object): | |||
|             if len(v) != 3: | ||||
|                 raise ValueError("bad palette entry") | ||||
| 
 | ||||
|             if 0 <= i <= 255: | ||||
|                 self.palette[i] = o8(v[0]) + o8(v[1]) + o8(v[2]) | ||||
| 
 | ||||
|             i += 1 | ||||
|             self.palette[i] = o8(v[0]) + o8(v[1]) + o8(v[2]) | ||||
| 
 | ||||
|         self.palette = b"".join(self.palette) | ||||
| 
 | ||||
|  |  | |||
							
								
								
									
										236
									
								
								src/PIL/Image.py
									
									
									
									
									
								
							
							
						
						|  | @ -40,8 +40,8 @@ except ImportError: | |||
|     import __builtin__ | ||||
|     builtins = __builtin__ | ||||
| 
 | ||||
| from . import ImageMode | ||||
| from ._binary import i8 | ||||
| from . import ImageMode, TiffTags | ||||
| from ._binary import i8, i32le | ||||
| from ._util import isPath, isStringType, deferred_error | ||||
| 
 | ||||
| import os | ||||
|  | @ -54,10 +54,10 @@ import atexit | |||
| import numbers | ||||
| try: | ||||
|     # Python 3 | ||||
|     from collections.abc import Callable | ||||
|     from collections.abc import Callable, MutableMapping | ||||
| except ImportError: | ||||
|     # Python 2.7 | ||||
|     from collections import Callable | ||||
|     from collections import Callable, MutableMapping | ||||
| 
 | ||||
| 
 | ||||
| # Silence warning | ||||
|  | @ -247,7 +247,7 @@ _MODEINFO = { | |||
|     "L": ("L", "L", ("L",)), | ||||
|     "I": ("L", "I", ("I",)), | ||||
|     "F": ("L", "F", ("F",)), | ||||
|     "P": ("RGB", "L", ("P",)), | ||||
|     "P": ("P", "L", ("P",)), | ||||
|     "RGB": ("RGB", "L", ("R", "G", "B")), | ||||
|     "RGBX": ("RGB", "L", ("R", "G", "B", "X")), | ||||
|     "RGBA": ("RGB", "L", ("R", "G", "B", "A")), | ||||
|  | @ -950,7 +950,7 @@ class Image(object): | |||
|         delete_trns = False | ||||
|         # transparency handling | ||||
|         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 | ||||
|                 # color to an alpha channel. | ||||
|                 new_im = self._new(self.im.convert_transparent( | ||||
|  | @ -1096,7 +1096,13 @@ class Image(object): | |||
|             im = self.im.convert("P", dither, palette.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): | ||||
|         """ | ||||
|  | @ -1291,6 +1297,12 @@ class Image(object): | |||
|             return tuple(extrema) | ||||
|         return self.im.getextrema() | ||||
| 
 | ||||
|     def getexif(self): | ||||
|         exif = Exif() | ||||
|         if "exif" in self.info: | ||||
|             exif.load(self.info["exif"]) | ||||
|         return exif | ||||
| 
 | ||||
|     def getim(self): | ||||
|         """ | ||||
|         Returns a capsule that points to the internal image memory. | ||||
|  | @ -1559,7 +1571,7 @@ class Image(object): | |||
| 
 | ||||
|         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 | ||||
|             try: | ||||
|                 mode = getmodebase(self.mode) + "A" | ||||
|  | @ -1568,7 +1580,7 @@ class Image(object): | |||
|                 except (AttributeError, ValueError): | ||||
|                     # do things the hard way | ||||
|                     im = self.im.convert(mode) | ||||
|                     if im.mode not in ("LA", "RGBA"): | ||||
|                     if im.mode not in ("LA", "PA", "RGBA"): | ||||
|                         raise ValueError  # sanity check | ||||
|                     self.im = im | ||||
|                 self.pyaccess = None | ||||
|  | @ -1576,7 +1588,7 @@ class Image(object): | |||
|             except (KeyError, ValueError): | ||||
|                 raise ValueError("illegal image mode") | ||||
| 
 | ||||
|         if self.mode == "LA": | ||||
|         if self.mode in ("LA", "PA"): | ||||
|             band = 1 | ||||
|         else: | ||||
|             band = 3 | ||||
|  | @ -1619,10 +1631,10 @@ class Image(object): | |||
| 
 | ||||
|     def putpalette(self, data, rawmode="RGB"): | ||||
|         """ | ||||
|         Attaches a palette to this image.  The image must be a "P" or | ||||
|         "L" image, and the palette sequence must contain 768 integer | ||||
|         values, where each group of three values represent the red, | ||||
|         green, and blue values for the corresponding pixel | ||||
|         Attaches a palette to this image.  The image must be a "P", | ||||
|         "PA", "L" or "LA" image, and the palette sequence must contain | ||||
|         768 integer values, where each group of three values represent | ||||
|         the red, green, and blue values for the corresponding pixel | ||||
|         index. Instead of an integer sequence, you can use an 8-bit | ||||
|         string. | ||||
| 
 | ||||
|  | @ -1631,7 +1643,7 @@ class Image(object): | |||
|         """ | ||||
|         from . import ImagePalette | ||||
| 
 | ||||
|         if self.mode not in ("L", "P"): | ||||
|         if self.mode not in ("L", "LA", "P", "PA"): | ||||
|             raise ValueError("illegal image mode") | ||||
|         self.load() | ||||
|         if isinstance(data, ImagePalette.ImagePalette): | ||||
|  | @ -1643,7 +1655,7 @@ class Image(object): | |||
|                 else: | ||||
|                     data = "".join(chr(x) for x in data) | ||||
|             palette = ImagePalette.raw(rawmode, data) | ||||
|         self.mode = "P" | ||||
|         self.mode = "PA" if "A" in self.mode else "P" | ||||
|         self.palette = palette | ||||
|         self.palette.mode = "RGB" | ||||
|         self.load()  # install new palette | ||||
|  | @ -1688,7 +1700,7 @@ class Image(object): | |||
|         Rewrites the image to reorder the 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. | ||||
|         :param source_palette: Bytes or None. | ||||
|         :returns:  An :py:class:`~PIL.Image.Image` object. | ||||
|  | @ -1958,7 +1970,7 @@ class Image(object): | |||
|             filename = fp.name | ||||
| 
 | ||||
|         # may mutate self! | ||||
|         self.load() | ||||
|         self._ensure_mutable() | ||||
| 
 | ||||
|         save_all = params.pop('save_all', False) | ||||
|         self.encoderinfo = params | ||||
|  | @ -2371,7 +2383,14 @@ def new(mode, size, color=0): | |||
|         from . import ImageColor | ||||
|         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): | ||||
|  | @ -2992,3 +3011,182 @@ def _apply_env_variables(env=None): | |||
| 
 | ||||
| _apply_env_variables() | ||||
| 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 , | ||||
|         #    // but if the Model and Manufacturer were the same or the model | ||||
|         #    // was long, Just the model,  in 1.x | ||||
|         model = profile.profile.product_model | ||||
|         manufacturer = profile.profile.product_manufacturer | ||||
|         model = profile.profile.model | ||||
|         manufacturer = profile.profile.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: | ||||
|             return model + "\n" | ||||
|         return "%s - %s\n" % (model, manufacturer) | ||||
|  | @ -727,8 +727,8 @@ def getProfileInfo(profile): | |||
|         # Python, not C. the white point bits weren't working well, | ||||
|         # so skipping. | ||||
|         # info was description \r\n\r\n copyright \r\n\r\n K007 tag \r\n\r\n whitepoint | ||||
|         description = profile.profile.product_description | ||||
|         cpright = profile.profile.product_copyright | ||||
|         description = profile.profile.profile_description | ||||
|         cpright = profile.profile.copyright | ||||
|         arr = [] | ||||
|         for elt in (description, cpright): | ||||
|             if elt: | ||||
|  | @ -762,7 +762,7 @@ def getProfileCopyright(profile): | |||
|         # add an extra newline to preserve pyCMS compatibility | ||||
|         if not isinstance(profile, ImageCmsProfile): | ||||
|             profile = ImageCmsProfile(profile) | ||||
|         return profile.profile.product_copyright + "\n" | ||||
|         return (profile.profile.copyright or "") + "\n" | ||||
|     except (AttributeError, IOError, TypeError, ValueError) as v: | ||||
|         raise PyCMSError(v) | ||||
| 
 | ||||
|  | @ -790,7 +790,7 @@ def getProfileManufacturer(profile): | |||
|         # add an extra newline to preserve pyCMS compatibility | ||||
|         if not isinstance(profile, ImageCmsProfile): | ||||
|             profile = ImageCmsProfile(profile) | ||||
|         return profile.profile.product_manufacturer + "\n" | ||||
|         return (profile.profile.manufacturer or "") + "\n" | ||||
|     except (AttributeError, IOError, TypeError, ValueError) as v: | ||||
|         raise PyCMSError(v) | ||||
| 
 | ||||
|  | @ -819,7 +819,7 @@ def getProfileModel(profile): | |||
|         # add an extra newline to preserve pyCMS compatibility | ||||
|         if not isinstance(profile, ImageCmsProfile): | ||||
|             profile = ImageCmsProfile(profile) | ||||
|         return profile.profile.product_model + "\n" | ||||
|         return (profile.profile.model or "") + "\n" | ||||
|     except (AttributeError, IOError, TypeError, ValueError) as v: | ||||
|         raise PyCMSError(v) | ||||
| 
 | ||||
|  | @ -848,7 +848,7 @@ def getProfileDescription(profile): | |||
|         # add an extra newline to preserve pyCMS compatibility | ||||
|         if not isinstance(profile, ImageCmsProfile): | ||||
|             profile = ImageCmsProfile(profile) | ||||
|         return profile.profile.product_description + "\n" | ||||
|         return (profile.profile.profile_description or "") + "\n" | ||||
|     except (AttributeError, IOError, TypeError, ValueError) as v: | ||||
|         raise PyCMSError(v) | ||||
| 
 | ||||
|  |  | |||
|  | @ -522,3 +522,30 @@ def solarize(image, threshold=128): | |||
|         else: | ||||
|             lut.append(255-i) | ||||
|     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): | ||||
|         # extract the IPTC/NAA resource | ||||
|         try: | ||||
|             app = im.app["APP13"] | ||||
|             if app[:14] == b"Photoshop 3.0\x00": | ||||
|                 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 | ||||
|         photoshop = im.info.get("photoshop") | ||||
|         if photoshop: | ||||
|             data = photoshop.get(0x0404) | ||||
| 
 | ||||
|     elif isinstance(im, TiffImagePlugin.TiffImageFile): | ||||
|         # get raw data from the IPTC/NAA tag (PhotoShop tags the data | ||||
|  |  | |||
|  | @ -39,7 +39,7 @@ import struct | |||
| import io | ||||
| import warnings | ||||
| 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 ._util import isStringType | ||||
| 
 | ||||
|  | @ -86,7 +86,7 @@ def APP(self, marker): | |||
|             self.info["jfif_density"] = jfif_density | ||||
|     elif marker == 0xFFE1 and s[:5] == b"Exif\0": | ||||
|         if "exif" not in self.info: | ||||
|             # extract Exif information (incomplete) | ||||
|             # extract EXIF information (incomplete) | ||||
|             self.info["exif"] = s  # FIXME: value will change | ||||
|     elif marker == 0xFFE2 and s[:5] == b"FPXR\0": | ||||
|         # extract FlashPix information (incomplete) | ||||
|  | @ -104,6 +104,39 @@ def APP(self, marker): | |||
|         # reassemble the profile, rather than assuming that the APP2 | ||||
|         # markers appear in the correct sequence. | ||||
|         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": | ||||
|         self.info["adobe"] = i16(s, 5) | ||||
|         # extract Adobe custom properties | ||||
|  | @ -127,15 +160,15 @@ def APP(self, marker): | |||
|             resolution_unit = exif[0x0128] | ||||
|             x_resolution = exif[0x011A] | ||||
|             try: | ||||
|                 dpi = x_resolution[0] / x_resolution[1] | ||||
|                 dpi = float(x_resolution[0]) / x_resolution[1] | ||||
|             except TypeError: | ||||
|                 dpi = x_resolution | ||||
|             if resolution_unit == 3:  # cm | ||||
|                 # 1 dpcm = 2.54 dpi | ||||
|                 dpi *= 2.54 | ||||
|             self.info["dpi"] = dpi, dpi | ||||
|             self.info["dpi"] = int(dpi + 0.5), int(dpi + 0.5) | ||||
|         except (KeyError, SyntaxError, ZeroDivisionError): | ||||
|             # SyntaxError for invalid/unreadable exif | ||||
|             # SyntaxError for invalid/unreadable EXIF | ||||
|             # KeyError for dpi not included | ||||
|             # ZeroDivisionError for invalid dpi rational value | ||||
|             self.info["dpi"] = 72, 72 | ||||
|  | @ -439,60 +472,23 @@ class JpegImageFile(ImageFile.ImageFile): | |||
| def _fixup_dict(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()} | ||||
|     exif = Image.Exif() | ||||
|     return exif._fixup_dict(src_dict) | ||||
| 
 | ||||
| 
 | ||||
| def _getexif(self): | ||||
|     # Extract EXIF information.  This method 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 (!). | ||||
|     # Use the cached version if possible | ||||
|     try: | ||||
|         data = self.info["exif"] | ||||
|         return self.info["parsed_exif"] | ||||
|     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 | ||||
|     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 | ||||
| 
 | ||||
| 
 | ||||
|  | @ -728,6 +724,10 @@ def _save(im, fp, filename): | |||
| 
 | ||||
|     optimize = info.get("optimize", False) | ||||
| 
 | ||||
|     exif = info.get("exif", b"") | ||||
|     if isinstance(exif, Image.Exif): | ||||
|         exif = exif.tobytes() | ||||
| 
 | ||||
|     # get keyword arguments | ||||
|     im.encoderconfig = ( | ||||
|         quality, | ||||
|  | @ -739,7 +739,7 @@ def _save(im, fp, filename): | |||
|         subsampling, | ||||
|         qtables, | ||||
|         extra, | ||||
|         info.get("exif", b"") | ||||
|         exif | ||||
|         ) | ||||
| 
 | ||||
|     # if we optimize, libjpeg needs a buffer big enough to hold the whole image | ||||
|  | @ -757,9 +757,9 @@ def _save(im, fp, filename): | |||
|         else: | ||||
|             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. | ||||
|     bufsize = max(ImageFile.MAXBLOCK, bufsize, len(info.get("exif", b"")) + 5, | ||||
|     bufsize = max(ImageFile.MAXBLOCK, bufsize, len(exif) + 5, | ||||
|                   len(extra) + 1) | ||||
| 
 | ||||
|     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: | ||||
|             # It's actually an MPO | ||||
|             from .MpoImagePlugin import MpoImageFile | ||||
|             im = MpoImageFile(fp, filename) | ||||
|             # Don't reload everything, just convert it. | ||||
|             im = MpoImageFile.adopt(im, mpheader) | ||||
|     except (TypeError, IndexError): | ||||
|         # It is really a JPEG | ||||
|         pass | ||||
|  |  | |||
|  | @ -18,7 +18,8 @@ | |||
| # 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 | ||||
| # PIL.__version__ instead. | ||||
|  | @ -46,7 +47,10 @@ class MpoImageFile(JpegImagePlugin.JpegImageFile): | |||
|     def _open(self): | ||||
|         self.fp.seek(0)  # prep the fp in order to pass the JPEG test | ||||
|         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.__mpoffsets = [mpent['DataOffset'] + self.info['mpoffset'] | ||||
|                             for mpent in self.mpinfo[0xB002]] | ||||
|  | @ -78,6 +82,20 @@ class MpoImageFile(JpegImagePlugin.JpegImageFile): | |||
|             return | ||||
|         self.fp = self.__fp | ||||
|         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 = [ | ||||
|             ("jpeg", (0, 0) + self.size, self.offset, (self.mode, "")) | ||||
|         ] | ||||
|  | @ -95,6 +113,22 @@ class MpoImageFile(JpegImagePlugin.JpegImageFile): | |||
|         finally: | ||||
|             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 | ||||
|  |  | |||
|  | @ -54,19 +54,24 @@ _MAGIC = b"\211PNG\r\n\032\n" | |||
| 
 | ||||
| _MODES = { | ||||
|     # supported bits/color combinations, and corresponding modes/rawmodes | ||||
|     # Greyscale | ||||
|     (1, 0):  ("1", "1"), | ||||
|     (2, 0):  ("L", "L;2"), | ||||
|     (4, 0):  ("L", "L;4"), | ||||
|     (8, 0):  ("L", "L"), | ||||
|     (16, 0): ("I", "I;16B"), | ||||
|     # Truecolour | ||||
|     (8, 2):  ("RGB", "RGB"), | ||||
|     (16, 2): ("RGB", "RGB;16B"), | ||||
|     # Indexed-colour | ||||
|     (1, 3):  ("P", "P;1"), | ||||
|     (2, 3):  ("P", "P;2"), | ||||
|     (4, 3):  ("P", "P;4"), | ||||
|     (8, 3):  ("P", "P"), | ||||
|     # Greyscale with alpha | ||||
|     (8, 4):  ("LA", "LA"), | ||||
|     (16, 4): ("RGBA", "LA;16B"),  # LA;16B->LA not yet available | ||||
|     # Truecolour with alpha | ||||
|     (8, 6):  ("RGBA", "RGBA"), | ||||
|     (16, 6): ("RGBA", "RGBA;16B"), | ||||
| } | ||||
|  | @ -386,7 +391,7 @@ class PngStream(ChunkStream): | |||
|                 # otherwise, we have a byte string with one alpha value | ||||
|                 # for each palette entry | ||||
|                 self.im_info["transparency"] = s | ||||
|         elif self.im_mode == "L": | ||||
|         elif self.im_mode in ("1", "L", "I"): | ||||
|             self.im_info["transparency"] = i16(s) | ||||
|         elif self.im_mode == "RGB": | ||||
|             self.im_info["transparency"] = i16(s), i16(s[2:]), i16(s[4:]) | ||||
|  | @ -691,8 +696,14 @@ class PngImageFile(ImageFile.ImageFile): | |||
|     def _getexif(self): | ||||
|         if "exif" not in self.info: | ||||
|             self.load() | ||||
|         from .JpegImagePlugin import _getexif | ||||
|         return _getexif(self) | ||||
|         if "exif" not in self.info: | ||||
|             return None | ||||
|         return dict(self.getexif()) | ||||
| 
 | ||||
|     def getexif(self): | ||||
|         if "exif" not in self.info: | ||||
|             self.load() | ||||
|         return ImageFile.ImageFile.getexif(self) | ||||
| 
 | ||||
| 
 | ||||
| # -------------------------------------------------------------------- | ||||
|  | @ -841,7 +852,7 @@ def _save(im, fp, filename, chunk=putchunk): | |||
|                 transparency = max(0, min(255, transparency)) | ||||
|                 alpha = b'\xFF' * transparency + b'\0' | ||||
|                 chunk(fp, b"tRNS", alpha[:alpha_bytes]) | ||||
|         elif im.mode == "L": | ||||
|         elif im.mode in ("1", "L", "I"): | ||||
|             transparency = max(0, min(65535, transparency)) | ||||
|             chunk(fp, b"tRNS", o16(transparency)) | ||||
|         elif im.mode == "RGB": | ||||
|  | @ -875,6 +886,8 @@ def _save(im, fp, filename, chunk=putchunk): | |||
| 
 | ||||
|     exif = im.encoderinfo.get("exif", im.info.get("exif")) | ||||
|     if exif: | ||||
|         if isinstance(exif, Image.Exif): | ||||
|             exif = exif.tobytes(8) | ||||
|         if exif.startswith(b"Exif\x00\x00"): | ||||
|             exif = exif[6:] | ||||
|         chunk(fp, b"eXIf", exif) | ||||
|  |  | |||
|  | @ -208,7 +208,7 @@ class SpiderImageFile(ImageFile.ImageFile): | |||
| 
 | ||||
| # given a list of filenames, return a list of images | ||||
| 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: | ||||
|         return | ||||
| 
 | ||||
|  |  | |||
|  | @ -785,17 +785,12 @@ class ImageFileDirectory_v2(MutableMapping): | |||
|             warnings.warn(str(msg)) | ||||
|             return | ||||
| 
 | ||||
|     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)) | ||||
| 
 | ||||
|     def tobytes(self, offset=0): | ||||
|         # FIXME What about tagdata? | ||||
|         fp.write(self._pack("H", len(self._tags_v2))) | ||||
|         result = self._pack("H", len(self._tags_v2)) | ||||
| 
 | ||||
|         entries = [] | ||||
|         offset = fp.tell() + len(self._tags_v2) * 12 + 4 | ||||
|         offset = offset + len(result) + len(self._tags_v2) * 12 + 4 | ||||
|         stripoffsets = None | ||||
| 
 | ||||
|         # pass 1: convert tags to binary format | ||||
|  | @ -844,18 +839,29 @@ class ImageFileDirectory_v2(MutableMapping): | |||
|         for tag, typ, count, value, data in entries: | ||||
|             if DEBUG > 1: | ||||
|                 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 -- | ||||
|         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 | ||||
|         for tag, typ, count, value, data in entries: | ||||
|             fp.write(data) | ||||
|             result += data | ||||
|             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 | ||||
|  | @ -985,7 +991,6 @@ class TiffImageFile(ImageFile.ImageFile): | |||
|         self.__fp = self.fp | ||||
|         self._frame_pos = [] | ||||
|         self._n_frames = None | ||||
|         self._is_animated = None | ||||
| 
 | ||||
|         if DEBUG: | ||||
|             print("*** TiffImageFile._open ***") | ||||
|  | @ -999,29 +1004,14 @@ class TiffImageFile(ImageFile.ImageFile): | |||
|     def n_frames(self): | ||||
|         if self._n_frames is None: | ||||
|             current = self.tell() | ||||
|             try: | ||||
|                 while True: | ||||
|                     self._seek(self.tell() + 1) | ||||
|             except EOFError: | ||||
|                 self._n_frames = self.tell() + 1 | ||||
|             self._seek(len(self._frame_pos)) | ||||
|             while self._n_frames is None: | ||||
|                 self._seek(self.tell() + 1) | ||||
|             self.seek(current) | ||||
|         return self._n_frames | ||||
| 
 | ||||
|     @property | ||||
|     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 | ||||
| 
 | ||||
|     def seek(self, frame): | ||||
|  | @ -1053,10 +1043,13 @@ class TiffImageFile(ImageFile.ImageFile): | |||
|                 print("Loading tags, location: %s" % self.fp.tell()) | ||||
|             self.tag_v2.load(self.fp) | ||||
|             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.fp.seek(self._frame_pos[frame]) | ||||
|         self.tag_v2.load(self.fp) | ||||
|         self.__next = self.tag_v2.next | ||||
|         # fill the legacy tag/ifd entries | ||||
|         self.tag = self.ifd = ImageFileDirectory_v1.from_v2(self.tag_v2) | ||||
|         self.__frame = frame | ||||
|  | @ -1087,7 +1080,7 @@ class TiffImageFile(ImageFile.ImageFile): | |||
|     def load_end(self): | ||||
|         # allow closing if we're on the first frame, there's no next | ||||
|         # 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 | ||||
| 
 | ||||
|     def _load_libtiff(self): | ||||
|  | @ -1167,10 +1160,9 @@ class TiffImageFile(ImageFile.ImageFile): | |||
|         self.tile = [] | ||||
|         self.readonly = 0 | ||||
|         # libtiff closed the fp in a, we need to close self.fp, if possible | ||||
|         if self._exclusive_fp: | ||||
|             if self.__frame == 0 and not self.__next: | ||||
|                 self.fp.close() | ||||
|                 self.fp = None  # might be shared | ||||
|         if self._exclusive_fp and not self._is_animated: | ||||
|             self.fp.close() | ||||
|             self.fp = None  # might be shared | ||||
| 
 | ||||
|         if err < 0: | ||||
|             raise IOError(err) | ||||
|  | @ -1261,11 +1253,11 @@ class TiffImageFile(ImageFile.ImageFile): | |||
|         if xres and yres: | ||||
|             resunit = self.tag_v2.get(RESOLUTION_UNIT) | ||||
|             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 | ||||
|                 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) | ||||
|                 self.info["dpi"] = xres, yres | ||||
|                 self.info["dpi"] = int(xres + 0.5), int(yres + 0.5) | ||||
|                 # For backward compatibility, | ||||
|                 # we also preserve the old behavior | ||||
|                 self.info["resolution"] = xres, yres | ||||
|  | @ -1476,8 +1468,8 @@ def _save(im, fp, filename): | |||
|     dpi = im.encoderinfo.get("dpi") | ||||
|     if dpi: | ||||
|         ifd[RESOLUTION_UNIT] = 2 | ||||
|         ifd[X_RESOLUTION] = dpi[0] | ||||
|         ifd[Y_RESOLUTION] = dpi[1] | ||||
|         ifd[X_RESOLUTION] = int(dpi[0] + 0.5) | ||||
|         ifd[Y_RESOLUTION] = int(dpi[1] + 0.5) | ||||
| 
 | ||||
|     if bits != (1,): | ||||
|         ifd[BITSPERSAMPLE] = bits | ||||
|  |  | |||
|  | @ -93,8 +93,9 @@ class WebPImageFile(ImageFile.ImageFile): | |||
|         self.seek(0) | ||||
| 
 | ||||
|     def _getexif(self): | ||||
|         from .JpegImagePlugin import _getexif | ||||
|         return _getexif(self) | ||||
|         if "exif" not in self.info: | ||||
|             return None | ||||
|         return dict(self.getexif()) | ||||
| 
 | ||||
|     @property | ||||
|     def n_frames(self): | ||||
|  | @ -216,6 +217,8 @@ def _save_all(im, fp, filename): | |||
|     method = im.encoderinfo.get("method", 0) | ||||
|     icc_profile = im.encoderinfo.get("icc_profile", "") | ||||
|     exif = im.encoderinfo.get("exif", "") | ||||
|     if isinstance(exif, Image.Exif): | ||||
|         exif = exif.tobytes() | ||||
|     xmp = im.encoderinfo.get("xmp", "") | ||||
|     if allow_mixed: | ||||
|         lossless = False | ||||
|  | @ -315,6 +318,8 @@ def _save(im, fp, filename): | |||
|     quality = im.encoderinfo.get("quality", 80) | ||||
|     icc_profile = im.encoderinfo.get("icc_profile", "") | ||||
|     exif = im.encoderinfo.get("exif", "") | ||||
|     if isinstance(exif, Image.Exif): | ||||
|         exif = exif.tobytes() | ||||
|     xmp = im.encoderinfo.get("xmp", "") | ||||
| 
 | ||||
|     if im.mode not in _VALID_WEBP_LEGACY_MODES: | ||||
|  |  | |||
|  | @ -131,8 +131,8 @@ class WmfStubImageFile(ImageFile.StubImageFile): | |||
|             size = x1 - x0, y1 - y0 | ||||
| 
 | ||||
|             # calculate dots per inch from bbox and frame | ||||
|             xdpi = 2540 * (x1 - y0) // (frame[2] - frame[0]) | ||||
|             ydpi = 2540 * (y1 - y0) // (frame[3] - frame[1]) | ||||
|             xdpi = int(2540.0 * (x1 - y0) / (frame[2] - frame[0]) + 0.5) | ||||
|             ydpi = int(2540.0 * (y1 - y0) / (frame[3] - frame[1]) + 0.5) | ||||
| 
 | ||||
|             self.info["wmf_bbox"] = x0, y0, x1, y1 | ||||
| 
 | ||||
|  |  | |||
|  | @ -1,2 +1,2 @@ | |||
| # Master version for Pillow | ||||
| __version__ = '6.0.0.dev0' | ||||
| __version__ = '6.1.0.dev0' | ||||
|  |  | |||
|  | @ -71,6 +71,7 @@ | |||
|  * See the README file for information on usage and redistribution. | ||||
|  */ | ||||
| 
 | ||||
| #define PY_SSIZE_T_CLEAN | ||||
| #include "Python.h" | ||||
| 
 | ||||
| #ifdef HAVE_LIBJPEG | ||||
|  | @ -1564,11 +1565,12 @@ _putpalette(ImagingObject* self, PyObject* args) | |||
| 
 | ||||
|     char* rawmode; | ||||
|     UINT8* palette; | ||||
|     int palettesize; | ||||
|     Py_ssize_t palettesize; | ||||
|     if (!PyArg_ParseTuple(args, "s"PY_ARG_BYTES_LENGTH, &rawmode, &palette, &palettesize)) | ||||
|         return NULL; | ||||
| 
 | ||||
|     if (strcmp(self->image->mode, "L") != 0 && strcmp(self->image->mode, "P")) { | ||||
|     if (strcmp(self->image->mode, "L") && strcmp(self->image->mode, "LA") && | ||||
|         strcmp(self->image->mode, "P") && strcmp(self->image->mode, "PA")) { | ||||
|         PyErr_SetString(PyExc_ValueError, wrong_mode); | ||||
|         return NULL; | ||||
|     } | ||||
|  | @ -1626,7 +1628,7 @@ _putpalettealphas(ImagingObject* self, PyObject* args) | |||
| { | ||||
|     int i; | ||||
|     UINT8 *values; | ||||
|     int length; | ||||
|     Py_ssize_t length; | ||||
|     if (!PyArg_ParseTuple(args, PY_ARG_BYTES_LENGTH, &values, &length)) | ||||
|         return NULL; | ||||
| 
 | ||||
|  | @ -1770,7 +1772,7 @@ im_setmode(ImagingObject* self, PyObject* args) | |||
|     Imaging im; | ||||
| 
 | ||||
|     char* mode; | ||||
|     int modelen; | ||||
|     Py_ssize_t modelen; | ||||
|     if (!PyArg_ParseTuple(args, "s#:setmode", &mode, &modelen)) | ||||
|     return NULL; | ||||
| 
 | ||||
|  | @ -2066,8 +2068,8 @@ _getprojection(ImagingObject* self, PyObject* args) | |||
|     ImagingGetProjection(self->image, (unsigned char *)xprofile, (unsigned char *)yprofile); | ||||
| 
 | ||||
|     result = Py_BuildValue(PY_ARG_BYTES_LENGTH PY_ARG_BYTES_LENGTH, | ||||
|                            xprofile, self->image->xsize, | ||||
|                            yprofile, self->image->ysize); | ||||
|                            xprofile, (Py_ssize_t)self->image->xsize, | ||||
|                            yprofile, (Py_ssize_t)self->image->ysize); | ||||
| 
 | ||||
|     free(xprofile); | ||||
|     free(yprofile); | ||||
|  | @ -2342,7 +2344,7 @@ _font_new(PyObject* self_, PyObject* args) | |||
| 
 | ||||
|     ImagingObject* imagep; | ||||
|     unsigned char* glyphdata; | ||||
|     int glyphdata_length; | ||||
|     Py_ssize_t glyphdata_length; | ||||
|     if (!PyArg_ParseTuple(args, "O!"PY_ARG_BYTES_LENGTH, | ||||
|                           &Imaging_Type, &imagep, | ||||
|                           &glyphdata, &glyphdata_length)) | ||||
|  |  | |||
|  | @ -25,6 +25,7 @@ kevin@cazabon.com\n\ | |||
| http://www.cazabon.com\n\ | ||||
| " | ||||
| 
 | ||||
| #define PY_SSIZE_T_CLEAN | ||||
| #include "Python.h" // Include before wchar.h so _GNU_SOURCE is set | ||||
| #include "wchar.h" | ||||
| #include "datetime.h" | ||||
|  | @ -120,7 +121,7 @@ cms_profile_fromstring(PyObject* self, PyObject* args) | |||
|     cmsHPROFILE hProfile; | ||||
| 
 | ||||
|     char* pProfile; | ||||
|     int nProfile; | ||||
|     Py_ssize_t nProfile; | ||||
| #if PY_VERSION_HEX >= 0x03000000 | ||||
|     if (!PyArg_ParseTuple(args, "y#:profile_frombytes", &pProfile, &nProfile)) | ||||
|         return NULL; | ||||
|  | @ -735,12 +736,12 @@ _xyz3_py(cmsCIEXYZ* XYZ) | |||
|     cmsXYZ2xyY(&xyY[2], &XYZ[2]); | ||||
| 
 | ||||
|     return Py_BuildValue("(((d,d,d),(d,d,d),(d,d,d)),((d,d,d),(d,d,d),(d,d,d)))", | ||||
| 			 XYZ[0].X, XYZ[0].Y, XYZ[0].Z, | ||||
| 			 XYZ[1].X, XYZ[1].Y, XYZ[1].Z, | ||||
| 			 XYZ[2].X, XYZ[2].Y, XYZ[2].Z, | ||||
| 			 xyY[0].x, xyY[0].y, xyY[0].Y, | ||||
| 			 xyY[1].x, xyY[1].y, xyY[1].Y, | ||||
| 			 xyY[2].x, xyY[2].y, xyY[2].Y); | ||||
|         XYZ[0].X, XYZ[0].Y, XYZ[0].Z, | ||||
|         XYZ[1].X, XYZ[1].Y, XYZ[1].Z, | ||||
|         XYZ[2].X, XYZ[2].Y, XYZ[2].Z, | ||||
|         xyY[0].x, xyY[0].y, xyY[0].Y, | ||||
|         xyY[1].x, xyY[1].y, xyY[1].Y, | ||||
|         xyY[2].x, xyY[2].y, xyY[2].Y); | ||||
| } | ||||
| 
 | ||||
| static PyObject* | ||||
|  | @ -783,9 +784,9 @@ _profile_read_ciexyy_triple(CmsProfileObject* self, cmsTagSignature info) | |||
|     /* Note: lcms does all the heavy lifting and error checking (nr of
 | ||||
|        channels == 3).  */ | ||||
|     return Py_BuildValue("((d,d,d),(d,d,d),(d,d,d)),", | ||||
| 			 triple->Red.x, triple->Red.y, triple->Red.Y, | ||||
| 			 triple->Green.x, triple->Green.y, triple->Green.Y, | ||||
| 			 triple->Blue.x, triple->Blue.y, triple->Blue.Y); | ||||
|         triple->Red.x, triple->Red.y, triple->Red.Y, | ||||
|         triple->Green.x, triple->Green.y, triple->Green.Y, | ||||
|         triple->Blue.x, triple->Blue.y, triple->Blue.Y); | ||||
| } | ||||
| 
 | ||||
| static PyObject* | ||||
|  | @ -817,12 +818,12 @@ _profile_read_named_color_list(CmsProfileObject* self, cmsTagSignature info) | |||
|     for (i = 0; i < n; i++) { | ||||
|         PyObject* str; | ||||
|         cmsNamedColorInfo(ncl, i, name, NULL, NULL, NULL, NULL); | ||||
| 	str = PyUnicode_FromString(name); | ||||
| 	if (str == NULL) { | ||||
|  	    Py_DECREF(result); | ||||
| 	    Py_INCREF(Py_None); | ||||
| 	    return Py_None; | ||||
| 	} | ||||
|         str = PyUnicode_FromString(name); | ||||
|         if (str == NULL) { | ||||
|             Py_DECREF(result); | ||||
|             Py_INCREF(Py_None); | ||||
|             return Py_None; | ||||
|         } | ||||
|         PyList_SET_ITEM(result, i, str); | ||||
|     } | ||||
| 
 | ||||
|  | @ -844,9 +845,9 @@ static cmsBool _calculate_rgb_primaries(CmsProfileObject* self, cmsCIEXYZTRIPLE* | |||
| 
 | ||||
|     // transform from our profile to XYZ using doubles for highest precision
 | ||||
|     hTransform = cmsCreateTransform(self->profile, TYPE_RGB_DBL, | ||||
| 				    hXYZ, TYPE_XYZ_DBL, | ||||
| 				    INTENT_RELATIVE_COLORIMETRIC, | ||||
| 				    cmsFLAGS_NOCACHE | cmsFLAGS_NOOPTIMIZE); | ||||
|                                     hXYZ, TYPE_XYZ_DBL, | ||||
|                                     INTENT_RELATIVE_COLORIMETRIC, | ||||
|                                     cmsFLAGS_NOCACHE | cmsFLAGS_NOOPTIMIZE); | ||||
|     cmsCloseProfile(hXYZ); | ||||
|     if (hTransform == NULL) | ||||
|         return 0; | ||||
|  | @ -885,31 +886,31 @@ _is_intent_supported(CmsProfileObject* self, int clut) | |||
| 
 | ||||
| 
 | ||||
|     n = cmsGetSupportedIntents(INTENTS, | ||||
| 			       intent_ids, | ||||
| 			       intent_descs); | ||||
|                                intent_ids, | ||||
|                                intent_descs); | ||||
|     for (i = 0; i < n; i++) { | ||||
|         int intent = (int) intent_ids[i]; | ||||
|         PyObject* id; | ||||
| 	PyObject* entry; | ||||
|         PyObject* entry; | ||||
| 
 | ||||
| 	/* Only valid for ICC Intents (otherwise we read invalid memory in lcms cmsio1.c). */ | ||||
| 	if (!(intent == INTENT_PERCEPTUAL || intent == INTENT_RELATIVE_COLORIMETRIC | ||||
| 	      || intent == INTENT_SATURATION || intent == INTENT_ABSOLUTE_COLORIMETRIC)) | ||||
| 	  continue; | ||||
|         /* Only valid for ICC Intents (otherwise we read invalid memory in lcms cmsio1.c). */ | ||||
|         if (!(intent == INTENT_PERCEPTUAL || intent == INTENT_RELATIVE_COLORIMETRIC | ||||
|             || intent == INTENT_SATURATION || intent == INTENT_ABSOLUTE_COLORIMETRIC)) | ||||
|             continue; | ||||
| 
 | ||||
| 	id = PyInt_FromLong((long) intent); | ||||
| 	entry = Py_BuildValue("(OOO)", | ||||
| 			      _check_intent(clut, self->profile, intent, LCMS_USED_AS_INPUT) ? Py_True : Py_False, | ||||
| 			      _check_intent(clut, self->profile, intent, LCMS_USED_AS_OUTPUT) ? Py_True : Py_False, | ||||
| 			      _check_intent(clut, self->profile, intent, LCMS_USED_AS_PROOF) ? Py_True : Py_False); | ||||
| 	if (id == NULL || entry == NULL) { | ||||
| 	    Py_XDECREF(id); | ||||
| 	    Py_XDECREF(entry); | ||||
| 	    Py_XDECREF(result); | ||||
| 	    Py_INCREF(Py_None); | ||||
| 	    return Py_None; | ||||
| 	} | ||||
| 	PyDict_SetItem(result, id, entry); | ||||
|         id = PyInt_FromLong((long) intent); | ||||
|         entry = Py_BuildValue("(OOO)", | ||||
|             _check_intent(clut, self->profile, intent, LCMS_USED_AS_INPUT) ? Py_True : Py_False, | ||||
|             _check_intent(clut, self->profile, intent, LCMS_USED_AS_OUTPUT) ? Py_True : Py_False, | ||||
|             _check_intent(clut, self->profile, intent, LCMS_USED_AS_PROOF) ? Py_True : Py_False); | ||||
|         if (id == NULL || entry == NULL) { | ||||
|             Py_XDECREF(id); | ||||
|             Py_XDECREF(entry); | ||||
|             Py_XDECREF(result); | ||||
|             Py_INCREF(Py_None); | ||||
|             return Py_None; | ||||
|         } | ||||
|         PyDict_SetItem(result, id, entry); | ||||
|     } | ||||
|     return result; | ||||
| } | ||||
|  | @ -966,6 +967,8 @@ _profile_getattr(CmsProfileObject* self, cmsInfoType field) | |||
| static PyObject* | ||||
| cms_profile_getattr_product_desc(CmsProfileObject* self, void* closure) | ||||
| { | ||||
|     PyErr_WarnEx(PyExc_DeprecationWarning, | ||||
|                  "product_desc is deprecated. Use Unicode profile_description instead.", 1); | ||||
|     // description was Description != 'Copyright' || or  "%s - %s" (manufacturer, model) in 1.x
 | ||||
|     return _profile_getattr(self, cmsInfoDescription); | ||||
| } | ||||
|  | @ -975,24 +978,32 @@ cms_profile_getattr_product_desc(CmsProfileObject* self, void* closure) | |||
| static PyObject* | ||||
| cms_profile_getattr_product_description(CmsProfileObject* self, void* closure) | ||||
| { | ||||
|     PyErr_WarnEx(PyExc_DeprecationWarning, | ||||
|                  "product_description is deprecated. Use Unicode profile_description instead.", 1); | ||||
|     return _profile_getattr(self, cmsInfoDescription); | ||||
| } | ||||
| 
 | ||||
| static PyObject* | ||||
| cms_profile_getattr_product_model(CmsProfileObject* self, void* closure) | ||||
| { | ||||
|     PyErr_WarnEx(PyExc_DeprecationWarning, | ||||
|                  "product_model is deprecated. Use Unicode model instead.", 1); | ||||
|     return _profile_getattr(self, cmsInfoModel); | ||||
| } | ||||
| 
 | ||||
| static PyObject* | ||||
| cms_profile_getattr_product_manufacturer(CmsProfileObject* self, void* closure) | ||||
| { | ||||
|     PyErr_WarnEx(PyExc_DeprecationWarning, | ||||
|                  "product_manufacturer is deprecated. Use Unicode manufacturer instead.", 1); | ||||
|     return _profile_getattr(self, cmsInfoManufacturer); | ||||
| } | ||||
| 
 | ||||
| static PyObject* | ||||
| cms_profile_getattr_product_copyright(CmsProfileObject* self, void* closure) | ||||
| { | ||||
|     PyErr_WarnEx(PyExc_DeprecationWarning, | ||||
|                  "product_copyright is deprecated. Use Unicode copyright instead.", 1); | ||||
|     return _profile_getattr(self, cmsInfoCopyright); | ||||
| } | ||||
| 
 | ||||
|  | @ -1005,12 +1016,16 @@ cms_profile_getattr_rendering_intent(CmsProfileObject* self, void* closure) | |||
| static PyObject* | ||||
| cms_profile_getattr_pcs(CmsProfileObject* self, void* closure) | ||||
| { | ||||
|     PyErr_WarnEx(PyExc_DeprecationWarning, | ||||
|                  "pcs is deprecated. Use padded connection_space instead.", 1); | ||||
|     return PyUnicode_DecodeFSDefault(findICmode(cmsGetPCS(self->profile))); | ||||
| } | ||||
| 
 | ||||
| static PyObject* | ||||
| cms_profile_getattr_color_space(CmsProfileObject* self, void* closure) | ||||
| { | ||||
|     PyErr_WarnEx(PyExc_DeprecationWarning, | ||||
|                  "color_space is deprecated. Use padded xcolor_space instead.", 1); | ||||
|     return PyUnicode_DecodeFSDefault(findICmode(cmsGetColorSpace(self->profile))); | ||||
| } | ||||
| 
 | ||||
|  | @ -1070,7 +1085,7 @@ cms_profile_getattr_creation_date(CmsProfileObject* self, void* closure) | |||
|     } | ||||
| 
 | ||||
|     return PyDateTime_FromDateAndTime(1900 + ct.tm_year, ct.tm_mon, ct.tm_mday, | ||||
| 				      ct.tm_hour, ct.tm_min, ct.tm_sec, 0); | ||||
|                                       ct.tm_hour, ct.tm_min, ct.tm_sec, 0); | ||||
| } | ||||
| 
 | ||||
| static PyObject* | ||||
|  | @ -1295,7 +1310,7 @@ cms_profile_getattr_green_primary(CmsProfileObject* self, void* closure) | |||
|         result = _calculate_rgb_primaries(self, &primaries); | ||||
|     if (! result) { | ||||
|         Py_INCREF(Py_None); | ||||
| 	return Py_None; | ||||
|         return Py_None; | ||||
|     } | ||||
| 
 | ||||
|     return _xyz_py(&primaries.Green); | ||||
|  | @ -1311,7 +1326,7 @@ cms_profile_getattr_blue_primary(CmsProfileObject* self, void* closure) | |||
|         result = _calculate_rgb_primaries(self, &primaries); | ||||
|     if (! result) { | ||||
|         Py_INCREF(Py_None); | ||||
| 	return Py_None; | ||||
|         return Py_None; | ||||
|     } | ||||
| 
 | ||||
|     return _xyz_py(&primaries.Blue); | ||||
|  | @ -1394,11 +1409,11 @@ cms_profile_getattr_icc_measurement_condition (CmsProfileObject* self, void* clo | |||
|         geo = "unknown"; | ||||
| 
 | ||||
|     return Py_BuildValue("{s:i,s:(ddd),s:s,s:d,s:s}", | ||||
| 			 "observer", mc->Observer, | ||||
| 			 "backing", mc->Backing.X, mc->Backing.Y, mc->Backing.Z, | ||||
| 			 "geo", geo, | ||||
| 			 "flare", mc->Flare, | ||||
| 			 "illuminant_type", _illu_map(mc->IlluminantType)); | ||||
|                          "observer", mc->Observer, | ||||
|                          "backing", mc->Backing.X, mc->Backing.Y, mc->Backing.Z, | ||||
|                          "geo", geo, | ||||
|                          "flare", mc->Flare, | ||||
|                          "illuminant_type", _illu_map(mc->IlluminantType)); | ||||
| } | ||||
| 
 | ||||
| static PyObject* | ||||
|  | @ -1419,9 +1434,9 @@ cms_profile_getattr_icc_viewing_condition (CmsProfileObject* self, void* closure | |||
|     } | ||||
| 
 | ||||
|     return Py_BuildValue("{s:(ddd),s:(ddd),s:s}", | ||||
| 			 "illuminant", vc->IlluminantXYZ.X, vc->IlluminantXYZ.Y, vc->IlluminantXYZ.Z, | ||||
| 			 "surround", vc->SurroundXYZ.X, vc->SurroundXYZ.Y, vc->SurroundXYZ.Z, | ||||
| 			 "illuminant_type", _illu_map(vc->IlluminantType)); | ||||
|         "illuminant", vc->IlluminantXYZ.X, vc->IlluminantXYZ.Y, vc->IlluminantXYZ.Z, | ||||
|         "surround", vc->SurroundXYZ.X, vc->SurroundXYZ.Y, vc->SurroundXYZ.Z, | ||||
|         "illuminant_type", _illu_map(vc->IlluminantType)); | ||||
| } | ||||
| 
 | ||||
| 
 | ||||
|  |  | |||
|  | @ -18,6 +18,7 @@ | |||
|  * Copyright (c) 1998-2007 by Secret Labs AB | ||||
|  */ | ||||
| 
 | ||||
| #define PY_SSIZE_T_CLEAN | ||||
| #include "Python.h" | ||||
| #include "Imaging.h" | ||||
| 
 | ||||
|  | @ -237,12 +238,12 @@ getfont(PyObject* self_, PyObject* args, PyObject* kw) | |||
|     int error = 0; | ||||
| 
 | ||||
|     char* filename = NULL; | ||||
|     int size; | ||||
|     int index = 0; | ||||
|     int layout_engine = 0; | ||||
|     Py_ssize_t size; | ||||
|     Py_ssize_t index = 0; | ||||
|     Py_ssize_t layout_engine = 0; | ||||
|     unsigned char* encoding; | ||||
|     unsigned char* font_bytes; | ||||
|     int font_bytes_size = 0; | ||||
|     Py_ssize_t font_bytes_size = 0; | ||||
|     static char* kwlist[] = { | ||||
|         "filename", "size", "index", "encoding", "font_bytes", | ||||
|         "layout_engine", NULL | ||||
|  |  | |||
|  | @ -29,6 +29,7 @@ | |||
| 
 | ||||
| /* FIXME: make these pluggable! */ | ||||
| 
 | ||||
| #define PY_SSIZE_T_CLEAN | ||||
| #include "Python.h" | ||||
| 
 | ||||
| #include "Imaging.h" | ||||
|  | @ -117,7 +118,8 @@ static PyObject* | |||
| _decode(ImagingDecoderObject* decoder, PyObject* args) | ||||
| { | ||||
|     UINT8* buffer; | ||||
|     int bufsize, status; | ||||
|     Py_ssize_t bufsize; | ||||
|     int status; | ||||
|     ImagingSectionCookie cookie; | ||||
| 
 | ||||
|     if (!PyArg_ParseTuple(args, PY_ARG_BYTES_LENGTH, &buffer, &bufsize)) | ||||
|  |  | |||
							
								
								
									
										53
									
								
								src/encode.c
									
									
									
									
									
								
							
							
						
						|  | @ -22,6 +22,7 @@ | |||
| 
 | ||||
| /* FIXME: make these pluggable! */ | ||||
| 
 | ||||
| #define PY_SSIZE_T_CLEAN | ||||
| #include "Python.h" | ||||
| 
 | ||||
| #include "Imaging.h" | ||||
|  | @ -123,7 +124,7 @@ _encode(ImagingEncoderObject* encoder, PyObject* args) | |||
| 
 | ||||
|     /* Encode to a Python string (allocated by this method) */ | ||||
| 
 | ||||
|     int bufsize = 16384; | ||||
|     Py_ssize_t bufsize = 16384; | ||||
| 
 | ||||
|     if (!PyArg_ParseTuple(args, "|i", &bufsize)) | ||||
|         return NULL; | ||||
|  | @ -176,8 +177,8 @@ _encode_to_file(ImagingEncoderObject* encoder, PyObject* args) | |||
| 
 | ||||
|     /* Encode to a file handle */ | ||||
| 
 | ||||
|     int fh; | ||||
|     int bufsize = 16384; | ||||
|     Py_ssize_t fh; | ||||
|     Py_ssize_t bufsize = 16384; | ||||
| 
 | ||||
|     if (!PyArg_ParseTuple(args, "i|i", &fh, &bufsize)) | ||||
|         return NULL; | ||||
|  | @ -221,7 +222,7 @@ _setimage(ImagingEncoderObject* encoder, PyObject* args) | |||
|     PyObject* op; | ||||
|     Imaging im; | ||||
|     ImagingCodecState state; | ||||
|     int x0, y0, x1, y1; | ||||
|     Py_ssize_t x0, y0, x1, y1; | ||||
| 
 | ||||
|     /* Define where image data should be stored */ | ||||
| 
 | ||||
|  | @ -406,8 +407,8 @@ PyImaging_GifEncoderNew(PyObject* self, PyObject* args) | |||
| 
 | ||||
|     char *mode; | ||||
|     char *rawmode; | ||||
|     int bits = 8; | ||||
|     int interlace = 0; | ||||
|     Py_ssize_t bits = 8; | ||||
|     Py_ssize_t interlace = 0; | ||||
|     if (!PyArg_ParseTuple(args, "ss|ii", &mode, &rawmode, &bits, &interlace)) | ||||
|         return NULL; | ||||
| 
 | ||||
|  | @ -438,7 +439,7 @@ PyImaging_PcxEncoderNew(PyObject* self, PyObject* args) | |||
| 
 | ||||
|     char *mode; | ||||
|     char *rawmode; | ||||
|     int bits = 8; | ||||
|     Py_ssize_t bits = 8; | ||||
| 
 | ||||
|     if (!PyArg_ParseTuple(args, "ss|ii", &mode, &rawmode, &bits)) { | ||||
|         return NULL; | ||||
|  | @ -470,8 +471,8 @@ PyImaging_RawEncoderNew(PyObject* self, PyObject* args) | |||
| 
 | ||||
|     char *mode; | ||||
|     char *rawmode; | ||||
|     int stride = 0; | ||||
|     int ystep = 1; | ||||
|     Py_ssize_t stride = 0; | ||||
|     Py_ssize_t ystep = 1; | ||||
| 
 | ||||
|     if (!PyArg_ParseTuple(args, "ss|ii", &mode, &rawmode, &stride, &ystep)) | ||||
|         return NULL; | ||||
|  | @ -503,7 +504,7 @@ PyImaging_TgaRleEncoderNew(PyObject* self, PyObject* args) | |||
| 
 | ||||
|     char *mode; | ||||
|     char *rawmode; | ||||
|     int ystep = 1; | ||||
|     Py_ssize_t ystep = 1; | ||||
| 
 | ||||
|     if (!PyArg_ParseTuple(args, "ss|i", &mode, &rawmode, &ystep)) | ||||
|         return NULL; | ||||
|  | @ -561,11 +562,11 @@ PyImaging_ZipEncoderNew(PyObject* self, PyObject* args) | |||
| 
 | ||||
|     char* mode; | ||||
|     char* rawmode; | ||||
|     int optimize = 0; | ||||
|     int compress_level = -1; | ||||
|     int compress_type = -1; | ||||
|     Py_ssize_t optimize = 0; | ||||
|     Py_ssize_t compress_level = -1; | ||||
|     Py_ssize_t compress_type = -1; | ||||
|     char* dictionary = NULL; | ||||
|     int dictionary_size = 0; | ||||
|     Py_ssize_t dictionary_size = 0; | ||||
|     if (!PyArg_ParseTuple(args, "ss|iii"PY_ARG_BYTES_LENGTH, &mode, &rawmode, | ||||
|                           &optimize, | ||||
|                           &compress_level, &compress_type, | ||||
|  | @ -701,20 +702,20 @@ PyImaging_JpegEncoderNew(PyObject* self, PyObject* args) | |||
| 
 | ||||
|     char *mode; | ||||
|     char *rawmode; | ||||
|     int quality = 0; | ||||
|     int progressive = 0; | ||||
|     int smooth = 0; | ||||
|     int optimize = 0; | ||||
|     int streamtype = 0; /* 0=interchange, 1=tables only, 2=image only */ | ||||
|     int xdpi = 0, ydpi = 0; | ||||
|     int subsampling = -1; /* -1=default, 0=none, 1=medium, 2=high */ | ||||
|     Py_ssize_t quality = 0; | ||||
|     Py_ssize_t progressive = 0; | ||||
|     Py_ssize_t smooth = 0; | ||||
|     Py_ssize_t optimize = 0; | ||||
|     Py_ssize_t streamtype = 0; /* 0=interchange, 1=tables only, 2=image only */ | ||||
|     Py_ssize_t xdpi = 0, ydpi = 0; | ||||
|     Py_ssize_t subsampling = -1; /* -1=default, 0=none, 1=medium, 2=high */ | ||||
|     PyObject* qtables=NULL; | ||||
|     unsigned int *qarrays = NULL; | ||||
|     int qtablesLen = 0; | ||||
|     char* extra = NULL; | ||||
|     int extra_size; | ||||
|     Py_ssize_t extra_size; | ||||
|     char* rawExif = NULL; | ||||
|     int rawExifLen = 0; | ||||
|     Py_ssize_t rawExifLen = 0; | ||||
| 
 | ||||
|     if (!PyArg_ParseTuple(args, "ss|iiiiiiiiO"PY_ARG_BYTES_LENGTH""PY_ARG_BYTES_LENGTH, | ||||
|                           &mode, &rawmode, &quality, | ||||
|  | @ -805,7 +806,7 @@ PyImaging_LibTiffEncoderNew(PyObject* self, PyObject* args) | |||
|     char* rawmode; | ||||
|     char* compname; | ||||
|     char* filename; | ||||
|     int fp; | ||||
|     Py_ssize_t fp; | ||||
| 
 | ||||
|     PyObject *dir; | ||||
|     PyObject *key, *value; | ||||
|  | @ -985,14 +986,14 @@ PyImaging_Jpeg2KEncoderNew(PyObject *self, PyObject *args) | |||
|     PyObject *offset = NULL, *tile_offset = NULL, *tile_size = NULL; | ||||
|     char *quality_mode = "rates"; | ||||
|     PyObject *quality_layers = NULL; | ||||
|     int num_resolutions = 0; | ||||
|     Py_ssize_t num_resolutions = 0; | ||||
|     PyObject *cblk_size = NULL, *precinct_size = NULL; | ||||
|     PyObject *irreversible = NULL; | ||||
|     char *progression = "LRCP"; | ||||
|     OPJ_PROG_ORDER prog_order; | ||||
|     char *cinema_mode = "no"; | ||||
|     OPJ_CINEMA_MODE cine_mode; | ||||
|     int fd = -1; | ||||
|     Py_ssize_t fd = -1; | ||||
| 
 | ||||
|     if (!PyArg_ParseTuple(args, "ss|OOOsOIOOOssi", &mode, &format, | ||||
|                           &offset, &tile_offset, &tile_size, | ||||
|  |  | |||
|  | @ -517,6 +517,18 @@ l2cmyk(UINT8* out, const UINT8* in, int xsize) | |||
|     } | ||||
| } | ||||
| 
 | ||||
| static void | ||||
| la2cmyk(UINT8* out, const UINT8* in, int xsize) | ||||
| { | ||||
|     int x; | ||||
|     for (x = 0; x < xsize; x++, in += 4) { | ||||
|         *out++ = 0; | ||||
|         *out++ = 0; | ||||
|         *out++ = 0; | ||||
|         *out++ = ~(in[0]); | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| static void | ||||
| rgb2cmyk(UINT8* out, const UINT8* in, int xsize) | ||||
| { | ||||
|  | @ -592,6 +604,22 @@ i2f(UINT8* out_, const UINT8* in_, int xsize) | |||
|         *out++ = (FLOAT32) *in++; | ||||
| } | ||||
| 
 | ||||
| static void | ||||
| i2rgb(UINT8* out, const UINT8* in_, int xsize) | ||||
| { | ||||
|     int x; | ||||
|     INT32* in = (INT32*) in_; | ||||
|     for (x = 0; x < xsize; x++, in++, out+=4) { | ||||
|         if (*in <= 0) | ||||
|             out[0] = out[1] = out[2] = 0; | ||||
|         else if (*in >= 255) | ||||
|             out[0] = out[1] = out[2] = 255; | ||||
|         else | ||||
|             out[0] = out[1] = out[2] = (UINT8) *in; | ||||
|         out[3] = 255; | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| /* ------------- */ | ||||
| /* F conversions */ | ||||
| /* ------------- */ | ||||
|  | @ -657,6 +685,18 @@ l2ycbcr(UINT8* out, const UINT8* in, int xsize) | |||
|     } | ||||
| } | ||||
| 
 | ||||
| static void | ||||
| la2ycbcr(UINT8* out, const UINT8* in, int xsize) | ||||
| { | ||||
|     int x; | ||||
|     for (x = 0; x < xsize; x++, in += 4) { | ||||
|         *out++ = in[0]; | ||||
|         *out++ = 128; | ||||
|         *out++ = 128; | ||||
|         *out++ = 255; | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| static void | ||||
| ycbcr2l(UINT8* out, const UINT8* in, int xsize) | ||||
| { | ||||
|  | @ -665,6 +705,16 @@ ycbcr2l(UINT8* out, const UINT8* in, int xsize) | |||
|         *out++ = in[0]; | ||||
| } | ||||
| 
 | ||||
| static void | ||||
| ycbcr2la(UINT8* out, const UINT8* in, int xsize) | ||||
| { | ||||
|     int x; | ||||
|     for (x = 0; x < xsize; x++, in += 4, out += 4) { | ||||
|         out[0] = out[1] = out[2] = in[0]; | ||||
|         out[3] = 255; | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| /* ------------------------- */ | ||||
| /* I;16 (16-bit) conversions */ | ||||
| /* ------------------------- */ | ||||
|  | @ -802,16 +852,21 @@ static struct { | |||
|     { "LA", "L", la2l }, | ||||
|     { "LA", "La", lA2la }, | ||||
|     { "LA", "RGB", la2rgb }, | ||||
|     { "LA", "RGBX", la2rgb }, | ||||
|     { "LA", "RGBA", la2rgb }, | ||||
|     { "LA", "RGBX", la2rgb }, | ||||
|     { "LA", "CMYK", la2cmyk }, | ||||
|     { "LA", "YCbCr", la2ycbcr }, | ||||
| 
 | ||||
|     { "La", "LA", la2lA }, | ||||
| 
 | ||||
|     { "I",    "L",    i2l }, | ||||
|     { "I",    "F",    i2f }, | ||||
|     { "I", "L", i2l }, | ||||
|     { "I", "F", i2f }, | ||||
|     { "I", "RGB", i2rgb }, | ||||
|     { "I", "RGBA", i2rgb }, | ||||
|     { "I", "RGBX", i2rgb }, | ||||
| 
 | ||||
|     { "F",    "L",    f2l }, | ||||
|     { "F",    "I",    f2i }, | ||||
|     { "F", "L", f2l }, | ||||
|     { "F", "I", f2i }, | ||||
| 
 | ||||
|     { "RGB", "1", rgb2bit }, | ||||
|     { "RGB", "L", rgb2l }, | ||||
|  | @ -842,8 +897,9 @@ static struct { | |||
| 
 | ||||
|     { "RGBX", "1", rgb2bit }, | ||||
|     { "RGBX", "L", rgb2l }, | ||||
|     { "RGBA", "I", rgb2i }, | ||||
|     { "RGBA", "F", rgb2f }, | ||||
|     { "RGBX", "LA", rgb2la }, | ||||
|     { "RGBX", "I", rgb2i }, | ||||
|     { "RGBX", "F", rgb2f }, | ||||
|     { "RGBX", "RGB", rgba2rgb }, | ||||
|     { "RGBX", "CMYK", rgb2cmyk }, | ||||
|     { "RGBX", "YCbCr", ImagingConvertRGB2YCbCr }, | ||||
|  | @ -853,6 +909,7 @@ static struct { | |||
|     { "CMYK", "RGBX", cmyk2rgb }, | ||||
| 
 | ||||
|     { "YCbCr", "L", ycbcr2l }, | ||||
|     { "YCbCr", "LA", ycbcr2la }, | ||||
|     { "YCbCr", "RGB", ImagingConvertYCbCr2RGB }, | ||||
| 
 | ||||
|     { "HSV", "RGB", hsv2rgb }, | ||||
|  | @ -894,6 +951,15 @@ p2bit(UINT8* out, const UINT8* in, int xsize, const UINT8* palette) | |||
|         *out++ = (L(&palette[in[x]*4]) >= 128000) ? 255 : 0; | ||||
| } | ||||
| 
 | ||||
| static void | ||||
| pa2bit(UINT8* out, const UINT8* in, int xsize, const UINT8* palette) | ||||
| { | ||||
|     int x; | ||||
|     /* FIXME: precalculate greyscale palette? */ | ||||
|     for (x = 0; x < xsize; x++, in += 4) | ||||
|         *out++ = (L(&palette[in[0]*4]) >= 128000) ? 255 : 0; | ||||
| } | ||||
| 
 | ||||
| static void | ||||
| p2l(UINT8* out, const UINT8* in, int xsize, const UINT8* palette) | ||||
| { | ||||
|  | @ -903,6 +969,28 @@ p2l(UINT8* out, const UINT8* in, int xsize, const UINT8* palette) | |||
|         *out++ = L(&palette[in[x]*4]) / 1000; | ||||
| } | ||||
| 
 | ||||
| static void | ||||
| pa2l(UINT8* out, const UINT8* in, int xsize, const UINT8* palette) | ||||
| { | ||||
|     int x; | ||||
|     /* FIXME: precalculate greyscale palette? */ | ||||
|     for (x = 0; x < xsize; x++, in += 4) | ||||
|         *out++ = L(&palette[in[0]*4]) / 1000; | ||||
| } | ||||
| 
 | ||||
| static void | ||||
| p2pa(UINT8* out, const UINT8* in, int xsize, const UINT8* palette) | ||||
| { | ||||
|     int x; | ||||
|     for (x = 0; x < xsize; x++, in++) { | ||||
|         const UINT8* rgba = &palette[in[0]]; | ||||
|         *out++ = in[0]; | ||||
|         *out++ = in[0]; | ||||
|         *out++ = in[0]; | ||||
|         *out++ = rgba[3]; | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| static void | ||||
| p2la(UINT8* out, const UINT8* in, int xsize, const UINT8* palette) | ||||
| { | ||||
|  | @ -920,9 +1008,9 @@ pa2la(UINT8* out, const UINT8* in, int xsize, const UINT8* palette) | |||
| { | ||||
|     int x; | ||||
|     /* FIXME: precalculate greyscale palette? */ | ||||
|     for (x = 0; x < xsize; x++, in += 2) { | ||||
|         *out++ = L(&palette[in[0]*4]) / 1000; | ||||
|         *out++ = in[1]; | ||||
|     for (x = 0; x < xsize; x++, in += 4, out += 4) { | ||||
|         out[0] = out[1] = out[2] = L(&palette[in[0]*4]) / 1000; | ||||
|         out[3] = in[3]; | ||||
|     } | ||||
| } | ||||
| 
 | ||||
|  | @ -935,6 +1023,15 @@ p2i(UINT8* out_, const UINT8* in, int xsize, const UINT8* palette) | |||
|         *out++ = L(&palette[in[x]*4]) / 1000; | ||||
| } | ||||
| 
 | ||||
| static void | ||||
| pa2i(UINT8* out_, const UINT8* in, int xsize, const UINT8* palette) | ||||
| { | ||||
|     int x; | ||||
|     INT32* out = (INT32*) out_; | ||||
|     for (x = 0; x < xsize; x++, in += 4) | ||||
|         *out++ = L(&palette[in[0]*4]) / 1000; | ||||
| } | ||||
| 
 | ||||
| static void | ||||
| p2f(UINT8* out_, const UINT8* in, int xsize, const UINT8* palette) | ||||
| { | ||||
|  | @ -944,6 +1041,15 @@ p2f(UINT8* out_, const UINT8* in, int xsize, const UINT8* palette) | |||
|         *out++ = (float) L(&palette[in[x]*4]) / 1000.0F; | ||||
| } | ||||
| 
 | ||||
| static void | ||||
| pa2f(UINT8* out_, const UINT8* in, int xsize, const UINT8* palette) | ||||
| { | ||||
|     int x; | ||||
|     FLOAT32* out = (FLOAT32*) out_; | ||||
|     for (x = 0; x < xsize; x++, in += 4) | ||||
|         *out++ = (float) L(&palette[in[0]*4]) / 1000.0F; | ||||
| } | ||||
| 
 | ||||
| static void | ||||
| p2rgb(UINT8* out, const UINT8* in, int xsize, const UINT8* palette) | ||||
| { | ||||
|  | @ -957,6 +1063,19 @@ p2rgb(UINT8* out, const UINT8* in, int xsize, const UINT8* palette) | |||
|     } | ||||
| } | ||||
| 
 | ||||
| static void | ||||
| pa2rgb(UINT8* out, const UINT8* in, int xsize, const UINT8* palette) | ||||
| { | ||||
|     int x; | ||||
|     for (x = 0; x < xsize; x++, in += 4) { | ||||
|         const UINT8* rgb = &palette[in[0] * 4]; | ||||
|         *out++ = rgb[0]; | ||||
|         *out++ = rgb[1]; | ||||
|         *out++ = rgb[2]; | ||||
|         *out++ = 255; | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| static void | ||||
| p2rgba(UINT8* out, const UINT8* in, int xsize, const UINT8* palette) | ||||
| { | ||||
|  | @ -990,6 +1109,13 @@ p2cmyk(UINT8* out, const UINT8* in, int xsize, const UINT8* palette) | |||
|     rgb2cmyk(out, out, xsize); | ||||
| } | ||||
| 
 | ||||
| static void | ||||
| pa2cmyk(UINT8* out, const UINT8* in, int xsize, const UINT8* palette) | ||||
| { | ||||
|     pa2rgb(out, in, xsize, palette); | ||||
|     rgb2cmyk(out, out, xsize); | ||||
| } | ||||
| 
 | ||||
| static void | ||||
| p2ycbcr(UINT8* out, const UINT8* in, int xsize, const UINT8* palette) | ||||
| { | ||||
|  | @ -997,6 +1123,13 @@ p2ycbcr(UINT8* out, const UINT8* in, int xsize, const UINT8* palette) | |||
|     ImagingConvertRGB2YCbCr(out, out, xsize); | ||||
| } | ||||
| 
 | ||||
| static void | ||||
| pa2ycbcr(UINT8* out, const UINT8* in, int xsize, const UINT8* palette) | ||||
| { | ||||
|     pa2rgb(out, in, xsize, palette); | ||||
|     ImagingConvertRGB2YCbCr(out, out, xsize); | ||||
| } | ||||
| 
 | ||||
| static Imaging | ||||
| frompalette(Imaging imOut, Imaging imIn, const char *mode) | ||||
| { | ||||
|  | @ -1013,25 +1146,27 @@ frompalette(Imaging imOut, Imaging imIn, const char *mode) | |||
|     alpha = !strcmp(imIn->mode, "PA"); | ||||
| 
 | ||||
|     if (strcmp(mode, "1") == 0) | ||||
|         convert = p2bit; | ||||
|         convert = alpha ? pa2bit : p2bit; | ||||
|     else if (strcmp(mode, "L") == 0) | ||||
|         convert = p2l; | ||||
|         convert = alpha ? pa2l : p2l; | ||||
|     else if (strcmp(mode, "LA") == 0) | ||||
|         convert = (alpha) ? pa2la : p2la; | ||||
|         convert = alpha ? pa2la : p2la; | ||||
|     else if (strcmp(mode, "PA") == 0) | ||||
|         convert = p2pa; | ||||
|     else if (strcmp(mode, "I") == 0) | ||||
|         convert = p2i; | ||||
|         convert = alpha ? pa2i : p2i; | ||||
|     else if (strcmp(mode, "F") == 0) | ||||
|         convert = p2f; | ||||
|         convert = alpha ? pa2f : p2f; | ||||
|     else if (strcmp(mode, "RGB") == 0) | ||||
|         convert = p2rgb; | ||||
|         convert = alpha ? pa2rgb : p2rgb; | ||||
|     else if (strcmp(mode, "RGBA") == 0) | ||||
|         convert = (alpha) ? pa2rgba : p2rgba; | ||||
|         convert = alpha ? pa2rgba : p2rgba; | ||||
|     else if (strcmp(mode, "RGBX") == 0) | ||||
|         convert = p2rgba; | ||||
|         convert = alpha ? pa2rgba : p2rgba; | ||||
|     else if (strcmp(mode, "CMYK") == 0) | ||||
|         convert = p2cmyk; | ||||
|         convert = alpha ? pa2cmyk : p2cmyk; | ||||
|     else if (strcmp(mode, "YCbCr") == 0) | ||||
|         convert = p2ycbcr; | ||||
|         convert = alpha ? pa2ycbcr : p2ycbcr; | ||||
|     else | ||||
|         return (Imaging) ImagingError_ValueError("conversion not supported"); | ||||
| 
 | ||||
|  | @ -1052,9 +1187,10 @@ frompalette(Imaging imOut, Imaging imIn, const char *mode) | |||
| #pragma optimize("", off) | ||||
| #endif | ||||
| static Imaging | ||||
| topalette(Imaging imOut, Imaging imIn, ImagingPalette inpalette, int dither) | ||||
| topalette(Imaging imOut, Imaging imIn, const char *mode, ImagingPalette inpalette, int dither) | ||||
| { | ||||
|     ImagingSectionCookie cookie; | ||||
|     int alpha; | ||||
|     int x, y; | ||||
|     ImagingPalette palette = inpalette;; | ||||
| 
 | ||||
|  | @ -1062,6 +1198,8 @@ topalette(Imaging imOut, Imaging imIn, ImagingPalette inpalette, int dither) | |||
|     if (strcmp(imIn->mode, "L") != 0 && strncmp(imIn->mode, "RGB", 3) != 0) | ||||
|         return (Imaging) ImagingError_ValueError("conversion not supported"); | ||||
| 
 | ||||
|     alpha = !strcmp(mode, "PA"); | ||||
| 
 | ||||
|     if (palette == NULL) { | ||||
|       /* FIXME: make user configurable */ | ||||
|       if (imIn->bands == 1) | ||||
|  | @ -1073,7 +1211,7 @@ topalette(Imaging imOut, Imaging imIn, ImagingPalette inpalette, int dither) | |||
|     if (!palette) | ||||
|         return (Imaging) ImagingError_ValueError("no palette"); | ||||
| 
 | ||||
|     imOut = ImagingNew2Dirty("P", imOut, imIn); | ||||
|     imOut = ImagingNew2Dirty(mode, imOut, imIn); | ||||
|     if (!imOut) { | ||||
|       if (palette != inpalette) | ||||
|         ImagingPaletteDelete(palette); | ||||
|  | @ -1088,8 +1226,13 @@ topalette(Imaging imOut, Imaging imIn, ImagingPalette inpalette, int dither) | |||
| 
 | ||||
|         /* Greyscale palette: copy data as is */ | ||||
|         ImagingSectionEnter(&cookie); | ||||
|         for (y = 0; y < imIn->ysize; y++) | ||||
|             memcpy(imOut->image[y], imIn->image[y], imIn->linesize); | ||||
|         for (y = 0; y < imIn->ysize; y++) { | ||||
|             if (alpha) { | ||||
|                 l2la((UINT8*) imOut->image[y], (UINT8*) imIn->image[y], imIn->xsize); | ||||
|             } else { | ||||
|                 memcpy(imOut->image[y], imIn->image[y], imIn->linesize); | ||||
|             } | ||||
|         } | ||||
|         ImagingSectionLeave(&cookie); | ||||
| 
 | ||||
|     } else { | ||||
|  | @ -1120,7 +1263,7 @@ topalette(Imaging imOut, Imaging imIn, ImagingPalette inpalette, int dither) | |||
|                 int g, g0, g1, g2; | ||||
|                 int b, b0, b1, b2; | ||||
|                 UINT8* in  = (UINT8*) imIn->image[y]; | ||||
|                 UINT8* out = imOut->image8[y]; | ||||
|                 UINT8* out = alpha ? (UINT8*) imOut->image32[y] : imOut->image8[y]; | ||||
|                 int* e = errors; | ||||
| 
 | ||||
|                 r = r0 = r1 = 0; | ||||
|  | @ -1139,7 +1282,12 @@ topalette(Imaging imOut, Imaging imIn, ImagingPalette inpalette, int dither) | |||
|                     cache = &ImagingPaletteCache(palette, r, g, b); | ||||
|                     if (cache[0] == 0x100) | ||||
|                         ImagingPaletteCacheUpdate(palette, r, g, b); | ||||
|                     out[x] = (UINT8) cache[0]; | ||||
|                     if (alpha) { | ||||
|                         out[x*4] = out[x*4+1] = out[x*4+2] = (UINT8) cache[0]; | ||||
|                         out[x*4+3] = 255; | ||||
|                     } else { | ||||
|                         out[x] = (UINT8) cache[0]; | ||||
|                     } | ||||
| 
 | ||||
|                     r -= (int) palette->palette[cache[0]*4]; | ||||
|                     g -= (int) palette->palette[cache[0]*4+1]; | ||||
|  | @ -1172,7 +1320,7 @@ topalette(Imaging imOut, Imaging imIn, ImagingPalette inpalette, int dither) | |||
|             for (y = 0; y < imIn->ysize; y++) { | ||||
|                 int r, g, b; | ||||
|                 UINT8* in  = (UINT8*) imIn->image[y]; | ||||
|                 UINT8* out = imOut->image8[y]; | ||||
|                 UINT8* out = alpha ? (UINT8*) imOut->image32[y] : imOut->image8[y]; | ||||
| 
 | ||||
|                 for (x = 0; x < imIn->xsize; x++, in += 4) { | ||||
|                     INT16* cache; | ||||
|  | @ -1183,8 +1331,12 @@ topalette(Imaging imOut, Imaging imIn, ImagingPalette inpalette, int dither) | |||
|                     cache = &ImagingPaletteCache(palette, r, g, b); | ||||
|                     if (cache[0] == 0x100) | ||||
|                         ImagingPaletteCacheUpdate(palette, r, g, b); | ||||
|                     out[x] = (UINT8) cache[0]; | ||||
| 
 | ||||
|                     if (alpha) { | ||||
|                         out[x*4] = out[x*4+1] = out[x*4+2] = (UINT8) cache[0]; | ||||
|                         out[x*4+3] = 255; | ||||
|                     } else { | ||||
|                         out[x] = (UINT8) cache[0]; | ||||
|                     } | ||||
|                 } | ||||
|             } | ||||
|             ImagingSectionLeave(&cookie); | ||||
|  | @ -1314,8 +1466,8 @@ convert(Imaging imOut, Imaging imIn, const char *mode, | |||
|     if (strcmp(imIn->mode, "P") == 0 || strcmp(imIn->mode, "PA") == 0) | ||||
|         return frompalette(imOut, imIn, mode); | ||||
| 
 | ||||
|     if (strcmp(mode, "P") == 0) | ||||
|         return topalette(imOut, imIn, palette, dither); | ||||
|     if (strcmp(mode, "P") == 0 || strcmp(mode, "PA") == 0) | ||||
|         return topalette(imOut, imIn, mode, palette, dither); | ||||
| 
 | ||||
|     if (dither && strcmp(mode, "1") == 0) | ||||
|         return tobilevel(imOut, imIn, dither); | ||||
|  | @ -1385,6 +1537,8 @@ ImagingConvertTransparent(Imaging imIn, const char *mode, | |||
|     } | ||||
| 
 | ||||
|     if (!((strcmp(imIn->mode, "RGB") == 0 || | ||||
|            strcmp(imIn->mode, "1") == 0 || | ||||
|            strcmp(imIn->mode, "I") == 0 || | ||||
|            strcmp(imIn->mode, "L") == 0) | ||||
|           && strcmp(mode, "RGBA") == 0)) | ||||
| #ifdef notdef | ||||
|  | @ -1403,7 +1557,13 @@ ImagingConvertTransparent(Imaging imIn, const char *mode, | |||
|     if (strcmp(imIn->mode, "RGB") == 0) { | ||||
|         convert = rgb2rgba; | ||||
|     } else { | ||||
|         convert = l2rgb; | ||||
|         if (strcmp(imIn->mode, "1") == 0) { | ||||
|             convert = bit2rgb; | ||||
|         } else if (strcmp(imIn->mode, "I") == 0) { | ||||
|             convert = i2rgb; | ||||
|         } else { | ||||
|             convert = l2rgb; | ||||
|         } | ||||
|         g = b = r; | ||||
|     } | ||||
| 
 | ||||
|  |  | |||
|  | @ -29,8 +29,8 @@ ImagingFlipLeftRight(Imaging imOut, Imaging imIn) | |||
| 
 | ||||
| #define FLIP_LEFT_RIGHT(INT, image) \ | ||||
|     for (y = 0; y < imIn->ysize; y++) { \ | ||||
|         INT* in = imIn->image[y]; \ | ||||
|         INT* out = imOut->image[y]; \ | ||||
|         INT* in = (INT *)imIn->image[y]; \ | ||||
|         INT* out = (INT *)imOut->image[y]; \ | ||||
|         xr = imIn->xsize-1; \ | ||||
|         for (x = 0; x < imIn->xsize; x++, xr--) \ | ||||
|             out[xr] = in[x]; \ | ||||
|  | @ -105,10 +105,10 @@ ImagingRotate90(Imaging imOut, Imaging imIn) | |||
|                     yyysize = yy + ROTATE_SMALL_CHUNK < imIn->ysize ? yy + ROTATE_SMALL_CHUNK : imIn->ysize; \ | ||||
|                     xxxsize = xx + ROTATE_SMALL_CHUNK < imIn->xsize ? xx + ROTATE_SMALL_CHUNK : imIn->xsize; \ | ||||
|                     for (yyy = yy; yyy < yyysize; yyy++) { \ | ||||
|                         INT* in = imIn->image[yyy]; \ | ||||
|                         INT* in = (INT *)imIn->image[yyy]; \ | ||||
|                         xr = imIn->xsize - 1 - xx; \ | ||||
|                         for (xxx = xx; xxx < xxxsize; xxx++, xr--) { \ | ||||
|                             INT* out = imOut->image[xr]; \ | ||||
|                             INT* out = (INT *)imOut->image[xr]; \ | ||||
|                             out[yyy] = in[xxx]; \ | ||||
|                         } \ | ||||
|                     } \ | ||||
|  | @ -161,9 +161,9 @@ ImagingTranspose(Imaging imOut, Imaging imIn) | |||
|                     yyysize = yy + ROTATE_SMALL_CHUNK < imIn->ysize ? yy + ROTATE_SMALL_CHUNK : imIn->ysize; \ | ||||
|                     xxxsize = xx + ROTATE_SMALL_CHUNK < imIn->xsize ? xx + ROTATE_SMALL_CHUNK : imIn->xsize; \ | ||||
|                     for (yyy = yy; yyy < yyysize; yyy++) { \ | ||||
|                         INT* in = imIn->image[yyy]; \ | ||||
|                         INT* in = (INT *)imIn->image[yyy]; \ | ||||
|                         for (xxx = xx; xxx < xxxsize; xxx++) { \ | ||||
|                             INT* out = imOut->image[xxx]; \ | ||||
|                             INT* out = (INT *)imOut->image[xxx]; \ | ||||
|                             out[yyy] = in[xxx]; \ | ||||
|                         } \ | ||||
|                     } \ | ||||
|  | @ -217,10 +217,10 @@ ImagingTransverse(Imaging imOut, Imaging imIn) | |||
|                     xxxsize = xx + ROTATE_SMALL_CHUNK < imIn->xsize ? xx + ROTATE_SMALL_CHUNK : imIn->xsize; \ | ||||
|                     yr = imIn->ysize - 1 - yy; \ | ||||
|                     for (yyy = yy; yyy < yyysize; yyy++, yr--) { \ | ||||
|                         INT* in = imIn->image[yyy]; \ | ||||
|                         INT* in = (INT *)imIn->image[yyy]; \ | ||||
|                         xr = imIn->xsize - 1 - xx; \ | ||||
|                         for (xxx = xx; xxx < xxxsize; xxx++, xr--) { \ | ||||
|                             INT* out = imOut->image[xr]; \ | ||||
|                             INT* out = (INT *)imOut->image[xr]; \ | ||||
|                             out[yr] = in[xxx]; \ | ||||
|                         } \ | ||||
|                     } \ | ||||
|  | @ -264,8 +264,8 @@ ImagingRotate180(Imaging imOut, Imaging imIn) | |||
| 
 | ||||
| #define ROTATE_180(INT, image) \ | ||||
|     for (y = 0; y < imIn->ysize; y++, yr--) { \ | ||||
|         INT* in = imIn->image[y]; \ | ||||
|         INT* out = imOut->image[yr]; \ | ||||
|         INT* in = (INT *)imIn->image[y]; \ | ||||
|         INT* out = (INT *)imOut->image[yr]; \ | ||||
|         xr = imIn->xsize-1; \ | ||||
|         for (x = 0; x < imIn->xsize; x++, xr--) \ | ||||
|             out[xr] = in[x]; \ | ||||
|  | @ -317,9 +317,9 @@ ImagingRotate270(Imaging imOut, Imaging imIn) | |||
|                     xxxsize = xx + ROTATE_SMALL_CHUNK < imIn->xsize ? xx + ROTATE_SMALL_CHUNK : imIn->xsize; \ | ||||
|                     yr = imIn->ysize - 1 - yy; \ | ||||
|                     for (yyy = yy; yyy < yyysize; yyy++, yr--) { \ | ||||
|                         INT* in = imIn->image[yyy]; \ | ||||
|                         INT* in = (INT *)imIn->image[yyy]; \ | ||||
|                         for (xxx = xx; xxx < xxxsize; xxx++) { \ | ||||
|                             INT* out = imOut->image[xxx]; \ | ||||
|                             INT* out = (INT *)imOut->image[xxx]; \ | ||||
|                             out[yr] = in[xxx]; \ | ||||
|                         } \ | ||||
|                     } \ | ||||
|  |  | |||
|  | @ -1331,6 +1331,7 @@ static struct { | |||
|     {"RGB",     "BGR;5",        16,     ImagingUnpackBGR15}, /* compat */ | ||||
|     {"RGB",     "RGBX",         32,     copy4}, | ||||
|     {"RGB",     "RGBX;L",       32,     unpackRGBAL}, | ||||
|     {"RGB",     "RGBA;L",       32,     unpackRGBAL}, | ||||
|     {"RGB",     "BGRX",         32,     ImagingUnpackBGRX}, | ||||
|     {"RGB",     "XRGB",         24,     ImagingUnpackXRGB}, | ||||
|     {"RGB",     "XBGR",         32,     ImagingUnpackXBGR}, | ||||
|  |  | |||
|  | @ -1,3 +1,3 @@ | |||
| curl -fsSL -o pypy2.zip https://bitbucket.org/pypy/pypy/downloads/pypy2.7-v7.0.0-win32.zip | ||||
| curl -fsSL -o pypy2.zip https://bitbucket.org/pypy/pypy/downloads/pypy2.7-v7.1.0-win32.zip | ||||
| 7z x pypy2.zip -oc:\ | ||||
| c:\Python37\Scripts\virtualenv.exe -p c:\pypy2.7-v7.0.0-win32\pypy.exe c:\vp\pypy2 | ||||
| c:\Python37\Scripts\virtualenv.exe -p c:\pypy2.7-v7.1.0-win32\pypy.exe c:\vp\pypy2 | ||||
|  |  | |||
|  | @ -109,6 +109,7 @@ set INCLUDE=%%INCLUDE%%;%%INCLIB%%\%(inc_dir)s;%%INCLIB%%\tcl%(tcl_ver)s\include | |||
| setlocal | ||||
| set LIB=%%LIB%%;C:\Python%(py_ver)s\tcl%(vc_setup)s | ||||
| call %(python_path)s\%(executable)s setup.py %(imaging_libs)s %%BLDOPT%% | ||||
| call %(python_path)s\%(executable)s -c "from PIL import _webp;import os, shutil;shutil.copy('%%INCLIB%%\\freetype.dll', os.path.dirname(_webp.__file__));" | ||||
| endlocal | ||||
| 
 | ||||
| endlocal | ||||
|  |  | |||
|  | @ -187,37 +187,30 @@ endlocal | |||
|     return script % compiler | ||||
| 
 | ||||
| 
 | ||||
| def msbuild_freetype(compiler): | ||||
|     if compiler['env_version'] == 'v7.1': | ||||
|         return msbuild_freetype_71(compiler) | ||||
|     return msbuild_freetype_70(compiler) | ||||
| 
 | ||||
| 
 | ||||
| def msbuild_freetype_71(compiler): | ||||
|     return r""" | ||||
| def msbuild_freetype(compiler, bit): | ||||
|     script = r""" | ||||
| rem Build freetype | ||||
| setlocal | ||||
| rd /S /Q %%FREETYPE%%\objs | ||||
| %%MSBUILD%% %%FREETYPE%%\builds\windows\vc%(vc_version)s\freetype.sln /t:Clean;Build /p:Configuration="Release" /p:Platform=%(platform)s /m | ||||
| set DefaultPlatformToolset=v100 | ||||
| """ | ||||
|     properties = r"""/p:Configuration="Release" /p:Platform=%(platform)s""" | ||||
|     if bit == 64: | ||||
|         script += r'copy /Y /B ' +\ | ||||
|             r'"C:\Program Files (x86)\Microsoft SDKs\Windows\v7.1A\Lib\x64\*.Lib" ' +\ | ||||
|             r'%%FREETYPE%%\builds\windows\vc2010' | ||||
|         properties += r" /p:_IsNativeEnvironment=false" | ||||
|     script += r""" | ||||
| %%MSBUILD%% %%FREETYPE%%\builds\windows\vc2010\freetype.sln /t:Clean;Build """+properties+r""" /m | ||||
| xcopy /Y /E /Q %%FREETYPE%%\include %%INCLIB%% | ||||
| copy /Y /B %%FREETYPE%%\objs\vc%(vc_version)s\%(platform)s\*.lib %%INCLIB%%\freetype.lib | ||||
| """ | ||||
|     freetypeReleaseDir = r"%%FREETYPE%%\objs\%(platform)s\Release" | ||||
|     script += r""" | ||||
| copy /Y /B """+freetypeReleaseDir+r"""\freetype.lib %%INCLIB%%\freetype.lib | ||||
| copy /Y /B """+freetypeReleaseDir+r"""\freetype.dll %%INCLIB%%\..\freetype.dll | ||||
| endlocal | ||||
| """ % compiler  # noqa: E501 | ||||
| 
 | ||||
| 
 | ||||
| def msbuild_freetype_70(compiler): | ||||
|     return r""" | ||||
| rem Build freetype | ||||
| setlocal | ||||
| py -3 %%~dp0\fixproj.py %%FREETYPE%%\builds\windows\vc%(vc_version)s\freetype.sln %(platform)s | ||||
| py -3 %%~dp0\fixproj.py %%FREETYPE%%\builds\windows\vc%(vc_version)s\freetype.vcproj %(platform)s | ||||
| rd /S /Q %%FREETYPE%%\objs | ||||
| %%MSBUILD%% %%FREETYPE%%\builds\windows\vc%(vc_version)s\freetype.sln /t:Clean;Build /p:Configuration="LIB Release";Platform=%(platform)s /m | ||||
| xcopy /Y /E /Q %%FREETYPE%%\include %%INCLIB%% | ||||
| xcopy /Y /E /Q %%FREETYPE%%\objs\win32\vc%(vc_version)s %%INCLIB%% | ||||
| copy /Y /B %%FREETYPE%%\objs\win32\vc%(vc_version)s\*.lib %%INCLIB%%\freetype.lib | ||||
| endlocal | ||||
| """ % compiler  # noqa: E501 | ||||
| """ | ||||
|     return script % compiler  # noqa: E501 | ||||
| 
 | ||||
| 
 | ||||
| def build_lcms2(compiler): | ||||
|  | @ -238,9 +231,9 @@ setlocal | |||
| rd /S /Q %%LCMS%%\Lib | ||||
| rd /S /Q %%LCMS%%\Projects\VC%(vc_version)s\Release | ||||
| %%MSBUILD%% %%LCMS%%\Projects\VC%(vc_version)s\lcms2.sln /t:Clean /p:Configuration="Release" /p:Platform=Win32 /m | ||||
| %%MSBUILD%% %%LCMS%%\Projects\VC%(vc_version)s\lcms2.sln /t:lcms2_static /p:Configuration="Release" /p:Platform=Win32 /m | ||||
| %%MSBUILD%% %%LCMS%%\Projects\VC%(vc_version)s\lcms2.sln /t:lcms2_static /p:Configuration="Release" /p:Platform=Win32 /p:PlatformToolset=v90 /m | ||||
| xcopy /Y /E /Q %%LCMS%%\include %%INCLIB%% | ||||
| copy /Y /B %%LCMS%%\Projects\VC%(vc_version)s\Release\*.lib %%INCLIB%% | ||||
| copy /Y /B %%LCMS%%\Lib\MS\*.lib %%INCLIB%% | ||||
| endlocal | ||||
| """ % compiler  # noqa: E501 | ||||
| 
 | ||||
|  | @ -265,7 +258,7 @@ rem Build gs | |||
| setlocal | ||||
| """ + vc_setup(compiler, bit) + r""" | ||||
| set MSVC_VERSION=""" + { | ||||
|         "2008": "9", | ||||
|         "2010": "90", | ||||
|         "2015": "14" | ||||
|     }[compiler['vc_version']] + r""" | ||||
| set RCOMP="C:\Program Files (x86)\Microsoft SDKs\Windows\v7.1A\Bin\RC.Exe" | ||||
|  | @ -289,7 +282,7 @@ def add_compiler(compiler, bit): | |||
| 
 | ||||
|     # script.append(extract_openjpeg(compiler)) | ||||
| 
 | ||||
|     script.append(msbuild_freetype(compiler)) | ||||
|     script.append(msbuild_freetype(compiler, bit)) | ||||
|     script.append(build_lcms2(compiler)) | ||||
|     # script.append(nmake_openjpeg(compiler)) | ||||
|     script.append(build_ghostscript(compiler, bit)) | ||||
|  | @ -308,7 +301,7 @@ if 'PYTHON' in os.environ: | |||
| else: | ||||
|     # for compiler in all_compilers(): | ||||
|     #     add_compiler(compiler) | ||||
|     add_compiler(compilers[7.0][2008][32], 32) | ||||
|     add_compiler(compilers[7.0][2010][32], 32) | ||||
| 
 | ||||
| with open('build_deps.cmd', 'w') as f: | ||||
|     f.write("\n".join(script)) | ||||
|  |  | |||
|  | @ -3,8 +3,8 @@ import os | |||
| SF_MIRROR = 'http://iweb.dl.sourceforge.net' | ||||
| PILLOW_DEPENDS_DIR = 'C:\\pillow-depends\\' | ||||
| 
 | ||||
| pythons = {'27': {'compiler': 7, 'vc': 2008}, | ||||
|            'pypy2': {'compiler': 7, 'vc': 2008}, | ||||
| pythons = {'27': {'compiler': 7, 'vc': 2010}, | ||||
|            'pypy2': {'compiler': 7, 'vc': 2010}, | ||||
|            '35': {'compiler': 7.1, 'vc': 2015}, | ||||
|            '36': {'compiler': 7.1, 'vc': 2015}, | ||||
|            '37': {'compiler': 7.1, 'vc': 2015}} | ||||
|  | @ -43,9 +43,9 @@ libs = { | |||
|         'dir': 'lcms2-2.7', | ||||
|     }, | ||||
|     'ghostscript': { | ||||
|         'url': 'https://github.com/ArtifexSoftware/ghostpdl-downloads/releases/download/gs926/ghostscript-9.26.tar.gz',  # noqa: E501 | ||||
|         'filename': PILLOW_DEPENDS_DIR + 'ghostscript-9.26.tar.gz', | ||||
|         'dir': 'ghostscript-9.26', | ||||
|         'url': 'https://github.com/ArtifexSoftware/ghostpdl-downloads/releases/download/gs927/ghostscript-9.27.tar.gz',  # noqa: E501 | ||||
|         'filename': PILLOW_DEPENDS_DIR + 'ghostscript-9.27.tar.gz', | ||||
|         'dir': 'ghostscript-9.27', | ||||
|     }, | ||||
|     'tcl-8.5': { | ||||
|         'url': SF_MIRROR+'/project/tcl/Tcl/8.5.19/tcl8519-src.zip', | ||||
|  | @ -83,10 +83,10 @@ libs = { | |||
| 
 | ||||
| compilers = { | ||||
|     7: { | ||||
|         2008: { | ||||
|         2010: { | ||||
|             64: { | ||||
|                 'env_version': 'v7.0', | ||||
|                 'vc_version': '2008', | ||||
|                 'vc_version': '2010', | ||||
|                 'env_flags': '/x64 /xp', | ||||
|                 'inc_dir': 'msvcr90-x64', | ||||
|                 'platform': 'x64', | ||||
|  | @ -94,7 +94,7 @@ compilers = { | |||
|             }, | ||||
|             32: { | ||||
|                 'env_version': 'v7.0', | ||||
|                 'vc_version': '2008', | ||||
|                 'vc_version': '2010', | ||||
|                 'env_flags': '/x86 /xp', | ||||
|                 'inc_dir': 'msvcr90-x32', | ||||
|                 'platform': 'Win32', | ||||
|  |  | |||