diff --git a/.appveyor.yml b/.appveyor.yml
index 0cf10597a..2a4e67dc9 100644
--- a/.appveyor.yml
+++ b/.appveyor.yml
@@ -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)
}
diff --git a/.github/CONTRIBUTING.md b/.github/CONTRIBUTING.md
index e3c7379ba..676f4374b 100644
--- a/.github/CONTRIBUTING.md
+++ b/.github/CONTRIBUTING.md
@@ -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
diff --git a/.travis.yml b/.travis.yml
index 9550eb8f2..68d1d840c 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -18,41 +18,27 @@ 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="ubuntu-16.04-xenial-amd64" DOCKER_TAG="master"
+ - env: DOCKER="ubuntu-18.04-bionic-amd64" DOCKER_TAG="master"
- env: DOCKER="debian-stretch-x86" DOCKER_TAG="master"
- env: DOCKER="centos-6-amd64" DOCKER_TAG="master"
- env: DOCKER="centos-7-amd64" DOCKER_TAG="master"
@@ -75,14 +61,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
diff --git a/CHANGES.rst b/CHANGES.rst
index 2b1945012..cb276b0bf 100644
--- a/CHANGES.rst
+++ b/CHANGES.rst
@@ -2,12 +2,102 @@
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]
+
+- Only close original fp in __del__ and __exit__ if original fp is exclusive #3683
+ [radarhere]
+
+- Fix BytesWarning in Tests/test_numpy.py #3725
+ [jdufresne]
+
+- Add missing MIME types and extensions #3520
+ [pirate486743186]
+
+- Add I;16 PNG save #3566
+ [radarhere]
+
+- Add support for BMP RGBA bitfield compression #3705
+ [radarhere]
+
+- Added ability to set language for text rendering #3693
+ [iwsfutcmd]
+
+- Only close exclusive fp on Image __exit__ #3698
+ [radarhere]
+
+- Changed EPS subprocess stdout from devnull to None #3635
+ [radarhere]
+
+- Add reading old-JPEG compressed TIFFs #3489
+ [kkopachev]
+
- Add EXIF support for PNG #3674
[radarhere]
@@ -38,7 +128,7 @@ Changelog (Pillow)
- Make ContainerIO.isatty() return a bool, not int #3568
[jdufresne]
-- Add support for I;16 modes for more transpose operations #3563
+- Add support to all transpose operations for I;16 modes #3563, #3741
[radarhere]
- Deprecate support for PyQt4 and PySide #3655
diff --git a/README.rst b/README.rst
index 381859c18..a5de420e7 100644
--- a/README.rst
+++ b/README.rst
@@ -20,6 +20,30 @@ Pillow is the friendly PIL fork by `Alex Clark and Contributors `_
+
+ - `Installation `_
+ - `Handbook `_
+
+- `Contribute `_
+
+ - `Issues `_
+ - `Pull requests `_
+
+- `Changelog `_
+
+ - `Pre-fork `_
+
+Report a Vulnerability
+----------------------
+
+To report a security vulnerability, please follow the procedure described in the `Tidelift security policy `_.
+
.. |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 `_
-
- - `Installation `_
- - `Handbook `_
-
-- `Contribute `_
-
- - `Issues `_
- - `Pull requests `_
-
-- `Changelog `_
-
- - `Pre-fork `_
diff --git a/RELEASING.md b/RELEASING.md
index f28e5f134..e1f57883c 100644
--- a/RELEASING.md
+++ b/RELEASING.md
@@ -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
diff --git a/Tests/images/1_trns.png b/Tests/images/1_trns.png
new file mode 100644
index 000000000..c9a271b40
Binary files /dev/null and b/Tests/images/1_trns.png differ
diff --git a/Tests/images/drawing_roundDown.emf b/Tests/images/drawing_roundDown.emf
new file mode 100644
index 000000000..6c3e20248
Binary files /dev/null and b/Tests/images/drawing_roundDown.emf differ
diff --git a/Tests/images/fujifilm.mpo b/Tests/images/fujifilm.mpo
new file mode 100644
index 000000000..ff0deb8a6
Binary files /dev/null and b/Tests/images/fujifilm.mpo differ
diff --git a/Tests/images/hopper.pnm b/Tests/images/hopper.pnm
new file mode 100644
index 000000000..52368b2e2
Binary files /dev/null and b/Tests/images/hopper.pnm differ
diff --git a/Tests/images/hopper_orientation_2.jpg b/Tests/images/hopper_orientation_2.jpg
new file mode 100644
index 000000000..02b4f392e
Binary files /dev/null and b/Tests/images/hopper_orientation_2.jpg differ
diff --git a/Tests/images/hopper_orientation_2.webp b/Tests/images/hopper_orientation_2.webp
new file mode 100644
index 000000000..43381d2ba
Binary files /dev/null and b/Tests/images/hopper_orientation_2.webp differ
diff --git a/Tests/images/hopper_orientation_3.jpg b/Tests/images/hopper_orientation_3.jpg
new file mode 100644
index 000000000..01717d980
Binary files /dev/null and b/Tests/images/hopper_orientation_3.jpg differ
diff --git a/Tests/images/hopper_orientation_3.webp b/Tests/images/hopper_orientation_3.webp
new file mode 100644
index 000000000..9537ff68e
Binary files /dev/null and b/Tests/images/hopper_orientation_3.webp differ
diff --git a/Tests/images/hopper_orientation_4.jpg b/Tests/images/hopper_orientation_4.jpg
new file mode 100644
index 000000000..3e0bb4e1a
Binary files /dev/null and b/Tests/images/hopper_orientation_4.jpg differ
diff --git a/Tests/images/hopper_orientation_4.webp b/Tests/images/hopper_orientation_4.webp
new file mode 100644
index 000000000..ca7b8cd30
Binary files /dev/null and b/Tests/images/hopper_orientation_4.webp differ
diff --git a/Tests/images/hopper_orientation_5.jpg b/Tests/images/hopper_orientation_5.jpg
new file mode 100644
index 000000000..fd32afc27
Binary files /dev/null and b/Tests/images/hopper_orientation_5.jpg differ
diff --git a/Tests/images/hopper_orientation_5.webp b/Tests/images/hopper_orientation_5.webp
new file mode 100644
index 000000000..a3164a90d
Binary files /dev/null and b/Tests/images/hopper_orientation_5.webp differ
diff --git a/Tests/images/hopper_orientation_6.jpg b/Tests/images/hopper_orientation_6.jpg
new file mode 100644
index 000000000..22a096198
Binary files /dev/null and b/Tests/images/hopper_orientation_6.jpg differ
diff --git a/Tests/images/hopper_orientation_6.webp b/Tests/images/hopper_orientation_6.webp
new file mode 100644
index 000000000..3e24c5bcb
Binary files /dev/null and b/Tests/images/hopper_orientation_6.webp differ
diff --git a/Tests/images/hopper_orientation_7.jpg b/Tests/images/hopper_orientation_7.jpg
new file mode 100644
index 000000000..a7c45146a
Binary files /dev/null and b/Tests/images/hopper_orientation_7.jpg differ
diff --git a/Tests/images/hopper_orientation_7.webp b/Tests/images/hopper_orientation_7.webp
new file mode 100644
index 000000000..f78163aed
Binary files /dev/null and b/Tests/images/hopper_orientation_7.webp differ
diff --git a/Tests/images/hopper_orientation_8.jpg b/Tests/images/hopper_orientation_8.jpg
new file mode 100644
index 000000000..e6b8c2c1c
Binary files /dev/null and b/Tests/images/hopper_orientation_8.jpg differ
diff --git a/Tests/images/hopper_orientation_8.webp b/Tests/images/hopper_orientation_8.webp
new file mode 100644
index 000000000..3cce80a47
Binary files /dev/null and b/Tests/images/hopper_orientation_8.webp differ
diff --git a/Tests/images/hopper_roundDown.bmp b/Tests/images/hopper_roundDown.bmp
new file mode 100644
index 000000000..62aada050
Binary files /dev/null and b/Tests/images/hopper_roundDown.bmp differ
diff --git a/Tests/images/hopper_roundDown_2.tif b/Tests/images/hopper_roundDown_2.tif
new file mode 100644
index 000000000..ac8cd057d
Binary files /dev/null and b/Tests/images/hopper_roundDown_2.tif differ
diff --git a/Tests/images/hopper_roundDown_3.tif b/Tests/images/hopper_roundDown_3.tif
new file mode 100644
index 000000000..0542fab9a
Binary files /dev/null and b/Tests/images/hopper_roundDown_3.tif differ
diff --git a/Tests/images/hopper_roundDown_None.tif b/Tests/images/hopper_roundDown_None.tif
new file mode 100644
index 000000000..21c40e8fe
Binary files /dev/null and b/Tests/images/hopper_roundDown_None.tif differ
diff --git a/Tests/images/hopper_roundUp_2.tif b/Tests/images/hopper_roundUp_2.tif
new file mode 100644
index 000000000..e38541c5d
Binary files /dev/null and b/Tests/images/hopper_roundUp_2.tif differ
diff --git a/Tests/images/hopper_roundUp_3.tif b/Tests/images/hopper_roundUp_3.tif
new file mode 100644
index 000000000..af6c96bd4
Binary files /dev/null and b/Tests/images/hopper_roundUp_3.tif differ
diff --git a/Tests/images/hopper_roundUp_None.tif b/Tests/images/hopper_roundUp_None.tif
new file mode 100644
index 000000000..b98635108
Binary files /dev/null and b/Tests/images/hopper_roundUp_None.tif differ
diff --git a/Tests/images/i_trns.png b/Tests/images/i_trns.png
new file mode 100644
index 000000000..ef63d33b0
Binary files /dev/null and b/Tests/images/i_trns.png differ
diff --git a/Tests/images/iptc_roundDown.jpg b/Tests/images/iptc_roundDown.jpg
new file mode 100644
index 000000000..f98206f18
Binary files /dev/null and b/Tests/images/iptc_roundDown.jpg differ
diff --git a/Tests/images/iptc_roundUp.jpg b/Tests/images/iptc_roundUp.jpg
new file mode 100644
index 000000000..68ac20b71
Binary files /dev/null and b/Tests/images/iptc_roundUp.jpg differ
diff --git a/Tests/images/old-style-jpeg-compression.png b/Tests/images/old-style-jpeg-compression.png
new file mode 100644
index 000000000..c035542ea
Binary files /dev/null and b/Tests/images/old-style-jpeg-compression.png differ
diff --git a/Tests/images/old-style-jpeg-compression.tif b/Tests/images/old-style-jpeg-compression.tif
new file mode 100644
index 000000000..8d726c404
Binary files /dev/null and b/Tests/images/old-style-jpeg-compression.tif differ
diff --git a/Tests/images/rgb32bf-rgba.bmp b/Tests/images/rgb32bf-rgba.bmp
new file mode 100644
index 000000000..467c2570b
Binary files /dev/null and b/Tests/images/rgb32bf-rgba.bmp differ
diff --git a/Tests/images/sugarshack_frame_size.mpo b/Tests/images/sugarshack_frame_size.mpo
new file mode 100644
index 000000000..81d58e64b
Binary files /dev/null and b/Tests/images/sugarshack_frame_size.mpo differ
diff --git a/Tests/images/test_language.png b/Tests/images/test_language.png
new file mode 100644
index 000000000..8daf007b0
Binary files /dev/null and b/Tests/images/test_language.png differ
diff --git a/Tests/test_file_bmp.py b/Tests/test_file_bmp.py
index 5fc6d9f36..b97a84227 100644
--- a/Tests/test_file_bmp.py
+++ b/Tests/test_file_bmp.py
@@ -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')
@@ -90,3 +111,15 @@ class TestFileBmp(PillowTestCase):
self.assertEqual(reloaded.format, "DIB")
self.assertEqual(reloaded.get_format_mimetype(), "image/bmp")
self.assert_image_equal(im, reloaded)
+
+ def test_rgba_bitfields(self):
+ # This test image has been manually hexedited
+ # to change the bitfield compression in the header from XBGR to RGBA
+ im = Image.open("Tests/images/rgb32bf-rgba.bmp")
+
+ # So before the comparing the image, swap the channels
+ b, g, r = im.split()[1:]
+ im = Image.merge("RGB", (r, g, b))
+
+ target = Image.open("Tests/images/bmp/q/rgb32bf-xbgr.bmp")
+ self.assert_image_equal(im, target)
diff --git a/Tests/test_file_gif.py b/Tests/test_file_gif.py
index 36a2b2648..c2188a0a2 100644
--- a/Tests/test_file_gif.py
+++ b/Tests/test_file_gif.py
@@ -230,6 +230,15 @@ class TestFileGif(PillowTestCase):
self.assertEqual(im.info, info)
+ def test_seek_rewind(self):
+ im = Image.open("Tests/images/iss634.gif")
+ im.seek(2)
+ im.seek(1)
+
+ expected = Image.open("Tests/images/iss634.gif")
+ expected.seek(1)
+ self.assert_image_equal(im, expected)
+
def test_n_frames(self):
for path, n_frames in [
[TEST_GIF, 1],
diff --git a/Tests/test_file_ico.py b/Tests/test_file_ico.py
index b0d33e33f..f6244e086 100644
--- a/Tests/test_file_ico.py
+++ b/Tests/test_file_ico.py
@@ -14,6 +14,7 @@ class TestFileIco(PillowTestCase):
self.assertEqual(im.mode, "RGBA")
self.assertEqual(im.size, (16, 16))
self.assertEqual(im.format, "ICO")
+ self.assertEqual(im.get_format_mimetype(), "image/x-icon")
def test_invalid_file(self):
with open("Tests/images/flower.jpg", "rb") as fp:
diff --git a/Tests/test_file_jpeg.py b/Tests/test_file_jpeg.py
index cbe894f34..25c3f754a 100644
--- a/Tests/test_file_jpeg.py
+++ b/Tests/test_file_jpeg.py
@@ -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):
diff --git a/Tests/test_file_libtiff.py b/Tests/test_file_libtiff.py
index e395708c3..0292b7733 100644
--- a/Tests/test_file_libtiff.py
+++ b/Tests/test_file_libtiff.py
@@ -716,3 +716,10 @@ class TestFileLibTiff(LibTiffTestCase):
im = Image.open(infile)
self.assert_image_similar_tofile(im, "Tests/images/flower.jpg", 0.5)
+
+ def test_old_style_jpeg(self):
+ infile = "Tests/images/old-style-jpeg-compression.tif"
+ im = Image.open(infile)
+
+ self.assert_image_equal_tofile(im,
+ "Tests/images/old-style-jpeg-compression.png")
diff --git a/Tests/test_file_mpo.py b/Tests/test_file_mpo.py
index 45172472a..ce1ca96b6 100644
--- a/Tests/test_file_mpo.py
+++ b/Tests/test_file_mpo.py
@@ -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)
diff --git a/Tests/test_file_pcx.py b/Tests/test_file_pcx.py
index 9e42a86f7..7608db47c 100644
--- a/Tests/test_file_pcx.py
+++ b/Tests/test_file_pcx.py
@@ -13,6 +13,7 @@ class TestFilePcx(PillowTestCase):
self.assertEqual(im2.mode, im.mode)
self.assertEqual(im2.size, im.size)
self.assertEqual(im2.format, "PCX")
+ self.assertEqual(im2.get_format_mimetype(), "image/x-pcx")
self.assert_image_equal(im2, im)
def test_sanity(self):
diff --git a/Tests/test_file_png.py b/Tests/test_file_png.py
index c2864d223..6a061fe6a 100644
--- a/Tests/test_file_png.py
+++ b/Tests/test_file_png.py
@@ -88,20 +88,13 @@ class TestFilePng(PillowTestCase):
self.assertEqual(im.format, "PNG")
self.assertEqual(im.get_format_mimetype(), 'image/png')
- hopper("1").save(test_file)
- Image.open(test_file)
-
- hopper("L").save(test_file)
- Image.open(test_file)
-
- hopper("P").save(test_file)
- Image.open(test_file)
-
- hopper("RGB").save(test_file)
- Image.open(test_file)
-
- hopper("I").save(test_file)
- Image.open(test_file)
+ for mode in ["1", "L", "P", "RGB", "I", "I;16"]:
+ im = hopper(mode)
+ im.save(test_file)
+ reloaded = Image.open(test_file)
+ if mode == "I;16":
+ reloaded = reloaded.convert(mode)
+ self.assert_image_equal(reloaded, im)
def test_invalid_file(self):
invalid_file = "Tests/images/flower.jpg"
@@ -298,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"
@@ -394,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
diff --git a/Tests/test_file_ppm.py b/Tests/test_file_ppm.py
index dc239cc4c..fa024f2cd 100644
--- a/Tests/test_file_ppm.py
+++ b/Tests/test_file_ppm.py
@@ -1,4 +1,4 @@
-from .helper import PillowTestCase
+from .helper import PillowTestCase, hopper
from PIL import Image
@@ -36,6 +36,16 @@ class TestFilePpm(PillowTestCase):
reloaded = Image.open(f)
self.assert_image_equal(im, reloaded)
+ def test_pnm(self):
+ im = Image.open('Tests/images/hopper.pnm')
+ self.assert_image_similar(im, hopper(), 0.0001)
+
+ f = self.tempfile('temp.pnm')
+ im.save(f)
+
+ reloaded = Image.open(f)
+ self.assert_image_equal(im, reloaded)
+
def test_truncated_file(self):
path = self.tempfile('temp.pgm')
with open(path, 'w') as f:
diff --git a/Tests/test_file_tga.py b/Tests/test_file_tga.py
index 0dbfb309c..a8ab00d7f 100644
--- a/Tests/test_file_tga.py
+++ b/Tests/test_file_tga.py
@@ -37,6 +37,8 @@ class TestFileTga(PillowTestCase):
path_no_ext, origin, "rle" if rle else "raw")
original_im = Image.open(tga_path)
+ self.assertEqual(original_im.format, "TGA")
+ self.assertEqual(original_im.get_format_mimetype(), "image/x-tga")
if rle:
self.assertEqual(
original_im.info["compression"], "tga_rle")
diff --git a/Tests/test_file_tiff.py b/Tests/test_file_tiff.py
index 9a4104b78..9f2a04bb9 100644
--- a/Tests/test_file_tiff.py
+++ b/Tests/test_file_tiff.py
@@ -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))
diff --git a/Tests/test_file_wmf.py b/Tests/test_file_wmf.py
index 146888491..e3c6063f2 100644
--- a/Tests/test_file_wmf.py
+++ b/Tests/test_file_wmf.py
@@ -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()
diff --git a/Tests/test_image.py b/Tests/test_image.py
index 330048057..5bfcde368 100644
--- a/Tests/test_image.py
+++ b/Tests/test_image.py
@@ -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
@@ -532,6 +554,18 @@ class TestImage(PillowTestCase):
with Image.open(test_file) as im:
self.assert_warning(None, im.save, temp_file)
+ def test_load_on_nonexclusive_multiframe(self):
+ with open("Tests/images/frozenpond.mpo", "rb") as fp:
+ def act(fp):
+ im = Image.open(fp)
+ im.load()
+ act(fp)
+
+ with Image.open(fp) as im:
+ im.load()
+
+ self.assertFalse(fp.closed)
+
class MockEncoder(object):
pass
diff --git a/Tests/test_image_convert.py b/Tests/test_image_convert.py
index 1ba1794eb..970dc8ce1 100644
--- a/Tests/test_image_convert.py
+++ b/Tests/test_image_convert.py
@@ -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)
diff --git a/Tests/test_image_load.py b/Tests/test_image_load.py
index d8f323eb8..9937218cd 100644
--- a/Tests/test_image_load.py
+++ b/Tests/test_image_load.py
@@ -28,3 +28,10 @@ class TestImageLoad(PillowTestCase):
os.fstat(fn)
self.assertRaises(OSError, os.fstat, fn)
+
+ def test_contextmanager_non_exclusive_fp(self):
+ with open("Tests/images/hopper.gif", "rb") as fp:
+ with Image.open(fp):
+ pass
+
+ self.assertFalse(fp.closed)
diff --git a/Tests/test_image_mode.py b/Tests/test_image_mode.py
index 26266611d..345c0dacb 100644
--- a/Tests/test_image_mode.py
+++ b/Tests/test_image_mode.py
@@ -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"))
diff --git a/Tests/test_image_putalpha.py b/Tests/test_image_putalpha.py
index 7b66b8833..702afb995 100644
--- a/Tests/test_image_putalpha.py
+++ b/Tests/test_image_putalpha.py
@@ -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))
diff --git a/Tests/test_image_putpalette.py b/Tests/test_image_putpalette.py
index 34b585f55..4bac448fd 100644
--- a/Tests/test_image_putpalette.py
+++ b/Tests/test_image_putpalette.py
@@ -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")
diff --git a/Tests/test_image_quantize.py b/Tests/test_image_quantize.py
index 762b4bcab..5f2318574 100644
--- a/Tests/test_image_quantize.py
+++ b/Tests/test_image_quantize.py
@@ -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()
diff --git a/Tests/test_image_transpose.py b/Tests/test_image_transpose.py
index ad8c77126..1cce5e986 100644
--- a/Tests/test_image_transpose.py
+++ b/Tests/test_image_transpose.py
@@ -24,7 +24,7 @@ class TestImageTranspose(PillowTestCase):
self.assertEqual(im.getpixel((1, y-2)), out.getpixel((x-2, y-2)))
self.assertEqual(im.getpixel((x-2, y-2)), out.getpixel((1, y-2)))
- for mode in ("L", "RGB", "I;16", "I;16L", "I;16B"):
+ for mode in self.hopper:
transpose(mode)
def test_flip_top_bottom(self):
@@ -40,7 +40,7 @@ class TestImageTranspose(PillowTestCase):
self.assertEqual(im.getpixel((1, y-2)), out.getpixel((1, 1)))
self.assertEqual(im.getpixel((x-2, y-2)), out.getpixel((x-2, 1)))
- for mode in ("L", "RGB", "I;16", "I;16L", "I;16B"):
+ for mode in self.hopper:
transpose(mode)
def test_rotate_90(self):
@@ -56,7 +56,7 @@ class TestImageTranspose(PillowTestCase):
self.assertEqual(im.getpixel((1, y-2)), out.getpixel((y-2, x-2)))
self.assertEqual(im.getpixel((x-2, y-2)), out.getpixel((y-2, 1)))
- for mode in ("L", "RGB"):
+ for mode in self.hopper:
transpose(mode)
def test_rotate_180(self):
@@ -72,7 +72,7 @@ class TestImageTranspose(PillowTestCase):
self.assertEqual(im.getpixel((1, y-2)), out.getpixel((x-2, 1)))
self.assertEqual(im.getpixel((x-2, y-2)), out.getpixel((1, 1)))
- for mode in ("L", "RGB", "I;16", "I;16L", "I;16B"):
+ for mode in self.hopper:
transpose(mode)
def test_rotate_270(self):
@@ -88,7 +88,7 @@ class TestImageTranspose(PillowTestCase):
self.assertEqual(im.getpixel((1, y-2)), out.getpixel((1, 1)))
self.assertEqual(im.getpixel((x-2, y-2)), out.getpixel((1, x-2)))
- for mode in ("L", "RGB"):
+ for mode in self.hopper:
transpose(mode)
def test_transpose(self):
@@ -104,7 +104,7 @@ class TestImageTranspose(PillowTestCase):
self.assertEqual(im.getpixel((1, y-2)), out.getpixel((y-2, 1)))
self.assertEqual(im.getpixel((x-2, y-2)), out.getpixel((y-2, x-2)))
- for mode in ("L", "RGB"):
+ for mode in self.hopper:
transpose(mode)
def test_tranverse(self):
@@ -120,28 +120,29 @@ class TestImageTranspose(PillowTestCase):
self.assertEqual(im.getpixel((1, y-2)), out.getpixel((1, x-2)))
self.assertEqual(im.getpixel((x-2, y-2)), out.getpixel((1, 1)))
- for mode in ("L", "RGB"):
+ for mode in self.hopper:
transpose(mode)
def test_roundtrip(self):
- im = self.hopper['L']
+ for mode in self.hopper:
+ im = self.hopper[mode]
- def transpose(first, second):
- return im.transpose(first).transpose(second)
+ def transpose(first, second):
+ return im.transpose(first).transpose(second)
- self.assert_image_equal(
- im, transpose(FLIP_LEFT_RIGHT, FLIP_LEFT_RIGHT))
- self.assert_image_equal(
- im, transpose(FLIP_TOP_BOTTOM, FLIP_TOP_BOTTOM))
- self.assert_image_equal(im, transpose(ROTATE_90, ROTATE_270))
- self.assert_image_equal(im, transpose(ROTATE_180, ROTATE_180))
- self.assert_image_equal(
- im.transpose(TRANSPOSE), transpose(ROTATE_90, FLIP_TOP_BOTTOM))
- self.assert_image_equal(
- im.transpose(TRANSPOSE), transpose(ROTATE_270, FLIP_LEFT_RIGHT))
- self.assert_image_equal(
- im.transpose(TRANSVERSE), transpose(ROTATE_90, FLIP_LEFT_RIGHT))
- self.assert_image_equal(
- im.transpose(TRANSVERSE), transpose(ROTATE_270, FLIP_TOP_BOTTOM))
- self.assert_image_equal(
- im.transpose(TRANSVERSE), transpose(ROTATE_180, TRANSPOSE))
+ self.assert_image_equal(
+ im, transpose(FLIP_LEFT_RIGHT, FLIP_LEFT_RIGHT))
+ self.assert_image_equal(
+ im, transpose(FLIP_TOP_BOTTOM, FLIP_TOP_BOTTOM))
+ self.assert_image_equal(im, transpose(ROTATE_90, ROTATE_270))
+ self.assert_image_equal(im, transpose(ROTATE_180, ROTATE_180))
+ self.assert_image_equal(
+ im.transpose(TRANSPOSE), transpose(ROTATE_90, FLIP_TOP_BOTTOM))
+ self.assert_image_equal(
+ im.transpose(TRANSPOSE), transpose(ROTATE_270, FLIP_LEFT_RIGHT))
+ self.assert_image_equal(
+ im.transpose(TRANSVERSE), transpose(ROTATE_90, FLIP_LEFT_RIGHT))
+ self.assert_image_equal(
+ im.transpose(TRANSVERSE), transpose(ROTATE_270, FLIP_TOP_BOTTOM))
+ self.assert_image_equal(
+ im.transpose(TRANSVERSE), transpose(ROTATE_180, TRANSPOSE))
diff --git a/Tests/test_imagecms.py b/Tests/test_imagecms.py
index cdd6f00c3..de140710f 100644
--- a/Tests/test_imagecms.py
+++ b/Tests/test_imagecms.py
@@ -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
diff --git a/Tests/test_imagefile.py b/Tests/test_imagefile.py
index 5853fb28f..e2339d76d 100644
--- a/Tests/test_imagefile.py
+++ b/Tests/test_imagefile.py
@@ -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,
+ })
diff --git a/Tests/test_imagefont.py b/Tests/test_imagefont.py
index be8667211..33f37916c 100644
--- a/Tests/test_imagefont.py
+++ b/Tests/test_imagefont.py
@@ -6,6 +6,8 @@ from io import BytesIO
import os
import sys
import copy
+import re
+import distutils.version
FONT_PATH = "Tests/fonts/FreeMono.ttf"
FONT_SIZE = 20
@@ -49,29 +51,40 @@ class TestImageFont(PillowTestCase):
# Freetype has different metrics depending on the version.
# (and, other things, but first things first)
METRICS = {
- ('2', '3'): {'multiline': 30,
- 'textsize': 12,
- 'getters': (13, 16)},
- ('2', '7'): {'multiline': 6.2,
- 'textsize': 2.5,
- 'getters': (12, 16)},
- ('2', '8'): {'multiline': 6.2,
- 'textsize': 2.5,
- 'getters': (12, 16)},
- ('2', '9'): {'multiline': 6.2,
- 'textsize': 2.5,
- 'getters': (12, 16)},
- 'Default': {'multiline': 0.5,
- 'textsize': 0.5,
- 'getters': (12, 16)},
+ ('>=2.3', '<2.4'): {
+ 'multiline': 30,
+ 'textsize': 12,
+ 'getters': (13, 16)},
+ ('>=2.7',): {
+ 'multiline': 6.2,
+ 'textsize': 2.5,
+ 'getters': (12, 16)},
+ 'Default': {
+ 'multiline': 0.5,
+ 'textsize': 0.5,
+ 'getters': (12, 16)},
}
def setUp(self):
- freetype_version = tuple(
- ImageFont.core.freetype2_version.split('.')
- )[:2]
- self.metrics = self.METRICS.get(freetype_version,
- self.METRICS['Default'])
+ freetype = distutils.version.StrictVersion(ImageFont.core.freetype2_version)
+
+ self.metrics = self.METRICS['Default']
+ for conditions, metrics in self.METRICS.items():
+ if not isinstance(conditions, tuple):
+ continue
+
+ for condition in conditions:
+ version = re.sub('[<=>]', '', condition)
+ if (condition.startswith('>=') and freetype >= version) or \
+ (condition.startswith('<') and freetype < version):
+ # Condition was met
+ continue
+
+ # Condition failed
+ break
+ else:
+ # All conditions were met
+ self.metrics = metrics
def get_font(self):
return ImageFont.truetype(FONT_PATH, FONT_SIZE,
@@ -525,6 +538,15 @@ class TestImageFont(PillowTestCase):
self.assertEqual(t.getsize_multiline('ABC\nA'), (36, 36))
self.assertEqual(t.getsize_multiline('ABC\nAaaa'), (48, 36))
+ def test_complex_font_settings(self):
+ # Arrange
+ t = self.get_font()
+ # Act / Assert
+ if t.layout_engine == ImageFont.LAYOUT_BASIC:
+ self.assertRaises(KeyError, t.getmask, 'абвг', direction='rtl')
+ self.assertRaises(KeyError, t.getmask, 'абвг', features=['-kern'])
+ self.assertRaises(KeyError, t.getmask, 'абвг', language='sr')
+
@unittest.skipUnless(HAS_RAQM, "Raqm not Available")
class TestImageFont_RaqmLayout(TestImageFont):
diff --git a/Tests/test_imagefontctl.py b/Tests/test_imagefontctl.py
index d23f6d86f..a00971058 100644
--- a/Tests/test_imagefontctl.py
+++ b/Tests/test_imagefontctl.py
@@ -130,3 +130,16 @@ class TestImagecomplextext(PillowTestCase):
target_img = Image.open(target)
self.assert_image_similar(im, target_img, .5)
+
+ def test_language(self):
+ ttf = ImageFont.truetype(FONT_PATH, FONT_SIZE)
+
+ im = Image.new(mode='RGB', size=(300, 100))
+ draw = ImageDraw.Draw(im)
+ draw.text((0, 0), 'абвг', font=ttf, fill=500,
+ language='sr')
+
+ target = 'Tests/images/test_language.png'
+ target_img = Image.open(target)
+
+ self.assert_image_similar(im, target_img, .5)
diff --git a/Tests/test_imageops.py b/Tests/test_imageops.py
index c5e48c431..eaa8fde3c 100644
--- a/Tests/test_imageops.py
+++ b/Tests/test_imageops.py
@@ -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)
diff --git a/Tests/test_numpy.py b/Tests/test_numpy.py
index c9c3b0f1e..bdb0ed874 100644
--- a/Tests/test_numpy.py
+++ b/Tests/test_numpy.py
@@ -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]], '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.
@@ -143,21 +138,21 @@ class TestNumpy(PillowTestCase):
np_img = numpy.array(img)
self._test_img_equals_nparray(img, np_img)
- self.assertEqual(np_img.dtype, numpy.dtype(dtype))
+ self.assertEqual(np_img.dtype, dtype)
- modes = [("L", 'uint8'),
- ("I", 'int32'),
- ("F", 'float32'),
- ("LA", 'uint8'),
- ("RGB", 'uint8'),
- ("RGBA", 'uint8'),
- ("RGBX", 'uint8'),
- ("CMYK", 'uint8'),
- ("YCbCr", 'uint8'),
+ modes = [("L", numpy.uint8),
+ ("I", numpy.int32),
+ ("F", numpy.float32),
+ ("LA", numpy.uint8),
+ ("RGB", numpy.uint8),
+ ("RGBA", numpy.uint8),
+ ("RGBX", numpy.uint8),
+ ("CMYK", numpy.uint8),
+ ("YCbCr", numpy.uint8),
("I;16", 'u2'),
("I;16L", '`
+ Requires libraqm.
+
+ .. versionadded:: 6.0.0
+
+.. py:method:: PIL.ImageDraw.ImageDraw.multiline_text(xy, text, fill=None, font=None, anchor=None, spacing=0, align="left", direction=None, features=None, language=None)
Draws the string at the given position.
@@ -316,7 +326,17 @@ Methods
.. versionadded:: 4.2.0
-.. py:method:: PIL.ImageDraw.ImageDraw.textsize(text, font=None, spacing=4, direction=None, features=None)
+ :param language: Language of the text. Different languages may use
+ different glyph shapes or ligatures. This parameter tells
+ the font which language the text is in, and to apply the
+ correct substitutions as appropriate, if available.
+ It should be a `BCP47 language code
+ `
+ Requires libraqm.
+
+ .. versionadded:: 6.0.0
+
+.. py:method:: PIL.ImageDraw.ImageDraw.textsize(text, font=None, spacing=4, direction=None, features=None, language=None)
Return the size of the given string, in pixels.
@@ -330,7 +350,6 @@ Methods
Requires libraqm.
.. versionadded:: 4.2.0
-
:param features: A list of OpenType font features to be used during text
layout. This is usually used to turn on optional
font features that are not enabled by default,
@@ -343,8 +362,17 @@ Methods
Requires libraqm.
.. versionadded:: 4.2.0
+ :param language: Language of the text. Different languages may use
+ different glyph shapes or ligatures. This parameter tells
+ the font which language the text is in, and to apply the
+ correct substitutions as appropriate, if available.
+ It should be a `BCP47 language code
+ `
+ Requires libraqm.
-.. py:method:: PIL.ImageDraw.ImageDraw.multiline_textsize(text, font=None, spacing=4, direction=None, features=None)
+ .. versionadded:: 6.0.0
+
+.. py:method:: PIL.ImageDraw.ImageDraw.multiline_textsize(text, font=None, spacing=4, direction=None, features=None, language=None)
Return the size of the given string, in pixels.
@@ -370,6 +398,16 @@ Methods
.. versionadded:: 4.2.0
+ :param language: Language of the text. Different languages may use
+ different glyph shapes or ligatures. This parameter tells
+ the font which language the text is in, and to apply the
+ correct substitutions as appropriate, if available.
+ It should be a `BCP47 language code
+ `
+ Requires libraqm.
+
+ .. versionadded:: 6.0.0
+
.. py:method:: PIL.ImageDraw.getdraw(im=None, hints=None)
.. warning:: This method is experimental.
diff --git a/docs/reference/ImageFont.rst b/docs/reference/ImageFont.rst
index 55ce3d382..b30bdac03 100644
--- a/docs/reference/ImageFont.rst
+++ b/docs/reference/ImageFont.rst
@@ -47,11 +47,45 @@ Functions
Methods
-------
-.. py:method:: PIL.ImageFont.ImageFont.getsize(text)
+.. py:method:: PIL.ImageFont.ImageFont.getsize(text, direction=None, features=[], language=None)
+
+ Returns width and height (in pixels) of given text if rendered in font with
+ provided direction, features, and language.
+
+ :param text: Text to measure.
+
+ :param direction: Direction of the text. It can be 'rtl' (right to
+ left), 'ltr' (left to right) or 'ttb' (top to bottom).
+ Requires libraqm.
+
+ .. versionadded:: 4.2.0
+
+ :param features: A list of OpenType font features to be used during text
+ layout. This is usually used to turn on optional
+ font features that are not enabled by default,
+ for example 'dlig' or 'ss01', but can be also
+ used to turn off default font features for
+ example '-liga' to disable ligatures or '-kern'
+ to disable kerning. To get all supported
+ features, see
+ https://docs.microsoft.com/en-us/typography/opentype/spec/featurelist
+ Requires libraqm.
+
+ .. versionadded:: 4.2.0
+
+ :param language: Language of the text. Different languages may use
+ different glyph shapes or ligatures. This parameter tells
+ the font which language the text is in, and to apply the
+ correct substitutions as appropriate, if available.
+ It should be a `BCP47 language code
+ `
+ Requires libraqm.
+
+ .. versionadded:: 6.0.0
:return: (width, height)
-.. py:method:: PIL.ImageFont.ImageFont.getmask(text, mode='', direction=None, features=[])
+.. py:method:: PIL.ImageFont.ImageFont.getmask(text, mode='', direction=None, features=[], language=None)
Create a bitmap for the text.
@@ -85,5 +119,15 @@ Methods
.. versionadded:: 4.2.0
+ :param language: Language of the text. Different languages may use
+ different glyph shapes or ligatures. This parameter tells
+ the font which language the text is in, and to apply the
+ correct substitutions as appropriate, if available.
+ It should be a `BCP47 language code
+ `
+ Requires libraqm.
+
+ .. versionadded:: 6.0.0
+
:return: An internal PIL storage memory instance as defined by the
:py:mod:`PIL.Image.core` interface module.
diff --git a/docs/reference/open_files.rst b/docs/reference/open_files.rst
index 511eb97bf..e26d9e639 100644
--- a/docs/reference/open_files.rst
+++ b/docs/reference/open_files.rst
@@ -3,10 +3,10 @@
File Handling in Pillow
=======================
-When opening a file as an image, Pillow requires a filename,
-pathlib.Path object, or a file-like object. Pillow uses the filename
-or Path to open a file, so for the rest of this article, they will all
-be treated as a file-like object.
+When opening a file as an image, Pillow requires a filename, ``pathlib.Path``
+object, or a file-like object. Pillow uses the filename or ``Path`` to open a
+file, so for the rest of this article, they will all be treated as a file-like
+object.
The first four of these items are equivalent, the last is dangerous
and may fail::
@@ -48,24 +48,23 @@ Issues
Image Lifecycle
---------------
-* ``Image.open()`` Path-like objects are opened as a file. Metadata is read
- from the open file. The file is left open for further usage.
+* ``Image.open()`` Filenames and ``Path`` objects are opened as a file.
+ Metadata is read from the open file. The file is left open for further usage.
* ``Image.Image.load()`` When the pixel data from the image is
required, ``load()`` is called. The current frame is read into
memory. The image can now be used independently of the underlying
image file.
- If a filename or a path-like object was passed to ``Image.open()``, then
- the file object was opened by Pillow and is considered to be used exclusively
- by Pillow. So if the image is a single-frame image, the file will
- be closed in this method after the frame is read. If the image is a
- multi-frame image, (e.g. multipage TIFF and animated GIF) the image file is
- left open so that ``Image.Image.seek()`` can load the appropriate frame.
+ If a filename or a ``Path`` object was passed to ``Image.open()``, then the
+ file object was opened by Pillow and is considered to be used exclusively by
+ Pillow. So if the image is a single-frame image, the file will be closed in
+ this method after the frame is read. If the image is a multi-frame image,
+ (e.g. multipage TIFF and animated GIF) the image file is left open so that
+ ``Image.Image.seek()`` can load the appropriate frame.
-* ``Image.Image.close()`` Closes the file pointer and destroys the
- core image object. This is used in the Pillow context manager
- support. e.g.::
+* ``Image.Image.close()`` Closes the file and destroys the core image object.
+ This is used in the Pillow context manager support. e.g.::
with Image.open('test.jpg') as img:
... # image operations here.
@@ -84,17 +83,9 @@ data until the caller has explicitly closed the image.
Complications
-------------
-* TiffImagePlugin has some code to pass the underlying file descriptor
- into libtiff (if working on an actual file). Since libtiff closes
- the file descriptor internally, it is duplicated prior to passing it
- into libtiff.
-
-* ``decoder.handles_eof`` This slightly misnamed flag indicates that
- the decoder wants to be called with a 0 length buffer when reads are
- done. Despite the comments in ``ImageFile.load()``, the only decoder
- that actually uses this flag is the Jpeg2K decoder. The use of this
- flag in Jpeg2K predated the change to the decoder that added the
- pulls_fd flag, and is therefore not used.
+* ``TiffImagePlugin`` has some code to pass the underlying file descriptor into
+ libtiff (if working on an actual file). Since libtiff closes the file
+ descriptor internally, it is duplicated prior to passing it into libtiff.
* I don't think that there's any way to make this safe without
changing the lazy loading::
diff --git a/docs/releasenotes/6.0.0.rst b/docs/releasenotes/6.0.0.rst
index 58586f3bb..0145347f2 100644
--- a/docs/releasenotes/6.0.0.rst
+++ b/docs/releasenotes/6.0.0.rst
@@ -99,30 +99,114 @@ 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
+^^^^^^^^^^^^^^^^^^^^^^
+
+Previously, all JPEG2000 images had the MIME type "image/jpx". This has now been
+corrected. After the file format drivers have been loaded, ``Image.MIME["JPEG2000"]``
+will return "image/jp2". ``ImageFile.get_format_mimetype`` will return "image/jpx" if
+a JPX profile is present, or "image/jp2" otherwise.
+
+Previously, all SGI images had the MIME type "image/rgb". This has now been
+corrected. After the file format drivers have been loaded, ``Image.MIME["SGI"]``
+will return "image/sgi". ``ImageFile.get_format_mimetype`` will return "image/rgb" if
+RGB image data is present, or "image/sgi" otherwise.
+
+MIME types have been added to the PPM format. After the file format drivers have been
+loaded, ``Image.MIME["PPM"]`` will now return the generic "image/x-portable-anymap".
+``ImageFile.get_format_mimetype`` will return a MIME type specific to the color type.
+
+The TGA, PCX and ICO formats also now have MIME types: "image/x-tga", "image/x-pcx" and
+"image/x-icon" respectively.
+
API Additions
=============
-DIB File Format
+DIB file format
^^^^^^^^^^^^^^^
-Pillow now supports reading and writing the DIB "Device Independent Bitmap" file format.
+Pillow now supports reading and writing the Device Independent Bitmap file format.
Image.quantize
^^^^^^^^^^^^^^
-The `dither` option is now a customisable parameter (was previously hardcoded to `1`). This parameter takes the same values used in `Image.convert`
+The ``dither`` option is now a customisable parameter (was previously hardcoded to ``1``).
+This parameter takes the same values used in :py:meth:`~PIL.Image.Image.convert`.
-PNG EXIF Data
+New language parameter
+^^^^^^^^^^^^^^^^^^^^^^
+
+These text-rendering functions now accept a ``language`` parameter to request
+language-specific glyphs and ligatures from the font:
+
+* ``ImageDraw.ImageDraw.multiline_text()``
+* ``ImageDraw.ImageDraw.multiline_textsize()``
+* ``ImageDraw.ImageDraw.text()``
+* ``ImageDraw.ImageDraw.textsize()``
+* ``ImageFont.ImageFont.getmask()``
+* ``ImageFont.ImageFont.getsize_multiline()``
+* ``ImageFont.ImageFont.getsize()``
+
+Added EXIF class
+^^^^^^^^^^^^^^^^
+
+:py:meth:`~PIL.Image.Image.getexif` has been added, which returns an
+:py:class:`~PIL.Image.Exif` instance. Values can be retrieved and set like a
+dictionary. When saving JPEG, PNG or WEBP, the instance can be passed as an
+``exif`` argument to include any changes in the output image.
+
+Added ImageOps.exif_transpose
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+:py:meth:`~PIL.ImageOps.exif_transpose` returns a copy of an image, transposed
+according to its EXIF Orientation tag.
+
+PNG EXIF data
^^^^^^^^^^^^^
EXIF data can now be read from and saved to PNG images. However, unlike other image
-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
=============
Reading new DDS image format
-~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^
Pillow can now read uncompressed RGB data from DDS images.
+
+Reading TIFF with old-style JPEG compression
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+Added support reading TIFF files with old-style JPEG compression through LibTIFF. All
+YCbCr TIFF images are now always read as RGB.
+
+TIFF compression codecs
+^^^^^^^^^^^^^^^^^^^^^^^
+
+Support has been added for the LZMA, Zstd and WebP TIFF compression codecs.
+
+Improved support for transposing I;16 images
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+I;16, I;16L and I;16B are now supported image modes for all
+:py:meth:`~PIL.Image.Image.transpose` operations.
diff --git a/setup.py b/setup.py
index 5d8159a12..8eb0b3c6b 100755
--- a/setup.py
+++ b/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 = """
diff --git a/src/PIL/BmpImagePlugin.py b/src/PIL/BmpImagePlugin.py
index 19aa6db29..3ca293a94 100644
--- a/src/PIL/BmpImagePlugin.py
+++ b/src/PIL/BmpImagePlugin.py
@@ -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.
@@ -105,53 +104,51 @@ class BmpImageFile(ImageFile.ImageFile):
# --------------------------------------------- Windows Bitmap v2 to v5
# v3, OS/2 v2, v4, v5
elif file_info['header_size'] in (40, 64, 108, 124):
- if file_info['header_size'] >= 40: # v3 and OS/2
- file_info['y_flip'] = i8(header_data[7]) == 0xff
- file_info['direction'] = 1 if file_info['y_flip'] else -1
- file_info['width'] = i32(header_data[0:4])
- file_info['height'] = (i32(header_data[4:8])
- if not file_info['y_flip']
- else 2**32 - i32(header_data[4:8]))
- file_info['planes'] = i16(header_data[8:10])
- file_info['bits'] = i16(header_data[10:12])
- file_info['compression'] = i32(header_data[12:16])
- # byte size of pixel data
- file_info['data_size'] = i32(header_data[16:20])
- file_info['pixels_per_meter'] = (i32(header_data[20:24]),
- i32(header_data[24:28]))
- 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']))
- if file_info['compression'] == self.BITFIELDS:
- if len(header_data) >= 52:
- for idx, mask in enumerate(['r_mask',
- 'g_mask',
- 'b_mask',
- 'a_mask']):
- file_info[mask] = i32(
- header_data[36 + idx * 4:40 + idx * 4]
- )
- else:
- # 40 byte headers only have the three components in the
- # bitfields masks, ref:
- # https://msdn.microsoft.com/en-us/library/windows/desktop/dd183376(v=vs.85).aspx
- # See also
- # https://github.com/python-pillow/Pillow/issues/1293
- # There is a 4th component in the RGBQuad, in the alpha
- # location, but it is listed as a reserved component,
- # and it is not generally an alpha channel
- file_info['a_mask'] = 0x0
- for mask in ['r_mask', 'g_mask', 'b_mask']:
- file_info[mask] = i32(read(4))
- file_info['rgb_mask'] = (file_info['r_mask'],
- file_info['g_mask'],
- file_info['b_mask'])
- file_info['rgba_mask'] = (file_info['r_mask'],
- file_info['g_mask'],
- file_info['b_mask'],
- file_info['a_mask'])
+ file_info['y_flip'] = i8(header_data[7]) == 0xff
+ file_info['direction'] = 1 if file_info['y_flip'] else -1
+ file_info['width'] = i32(header_data[0:4])
+ file_info['height'] = (i32(header_data[4:8])
+ if not file_info['y_flip']
+ else 2**32 - i32(header_data[4:8]))
+ file_info['planes'] = i16(header_data[8:10])
+ file_info['bits'] = i16(header_data[10:12])
+ file_info['compression'] = i32(header_data[12:16])
+ # byte size of pixel data
+ file_info['data_size'] = i32(header_data[16:20])
+ file_info['pixels_per_meter'] = (i32(header_data[20:24]),
+ i32(header_data[24:28]))
+ file_info['colors'] = i32(header_data[28:32])
+ file_info['palette_padding'] = 4
+ self.info["dpi"] = tuple(
+ 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',
+ 'g_mask',
+ 'b_mask',
+ 'a_mask']):
+ file_info[mask] = i32(
+ header_data[36 + idx * 4:40 + idx * 4]
+ )
+ else:
+ # 40 byte headers only have the three components in the
+ # bitfields masks, ref:
+ # https://msdn.microsoft.com/en-us/library/windows/desktop/dd183376(v=vs.85).aspx
+ # See also
+ # https://github.com/python-pillow/Pillow/issues/1293
+ # There is a 4th component in the RGBQuad, in the alpha
+ # location, but it is listed as a reserved component,
+ # and it is not generally an alpha channel
+ file_info['a_mask'] = 0x0
+ for mask in ['r_mask', 'g_mask', 'b_mask']:
+ file_info[mask] = i32(read(4))
+ file_info['rgb_mask'] = (file_info['r_mask'],
+ file_info['g_mask'],
+ file_info['b_mask'])
+ file_info['rgba_mask'] = (file_info['r_mask'],
+ file_info['g_mask'],
+ file_info['b_mask'],
+ file_info['a_mask'])
else:
raise IOError("Unsupported BMP header type (%d)" %
file_info['header_size'])
@@ -180,6 +177,7 @@ class BmpImageFile(ImageFile.ImageFile):
SUPPORTED = {
32: [(0xff0000, 0xff00, 0xff, 0x0),
(0xff0000, 0xff00, 0xff, 0xff000000),
+ (0xff, 0xff00, 0xff0000, 0xff000000),
(0x0, 0x0, 0x0, 0x0),
(0xff000000, 0xff0000, 0xff00, 0x0)],
24: [(0xff0000, 0xff00, 0xff)],
@@ -188,6 +186,7 @@ class BmpImageFile(ImageFile.ImageFile):
MASK_MODES = {
(32, (0xff0000, 0xff00, 0xff, 0x0)): "BGRX",
(32, (0xff000000, 0xff0000, 0xff00, 0x0)): "XBGR",
+ (32, (0xff, 0xff00, 0xff0000, 0xff000000)): "RGBA",
(32, (0xff0000, 0xff00, 0xff, 0xff000000)): "BGRA",
(32, (0x0, 0x0, 0x0, 0x0)): "BGRA",
(24, (0xff0000, 0xff00, 0xff)): "BGR",
@@ -200,7 +199,7 @@ class BmpImageFile(ImageFile.ImageFile):
raw_mode = MASK_MODES[
(file_info["bits"], file_info["rgba_mask"])
]
- self.mode = "RGBA" if raw_mode in ("BGRA",) else self.mode
+ self.mode = "RGBA" if "A" in raw_mode else self.mode
elif (file_info['bits'] in (24, 16) and
file_info['rgb_mask'] in SUPPORTED[file_info['bits']]):
raw_mode = MASK_MODES[
@@ -310,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
diff --git a/src/PIL/EpsImagePlugin.py b/src/PIL/EpsImagePlugin.py
index 7421189f8..9ec8ebba5 100644
--- a/src/PIL/EpsImagePlugin.py
+++ b/src/PIL/EpsImagePlugin.py
@@ -141,13 +141,11 @@ def Ghostscript(tile, size, fp, scale=1):
# push data through Ghostscript
try:
- with open(os.devnull, 'w+b') as devnull:
- startupinfo = None
- if sys.platform.startswith('win'):
- startupinfo = subprocess.STARTUPINFO()
- startupinfo.dwFlags |= subprocess.STARTF_USESHOWWINDOW
- subprocess.check_call(command, stdout=devnull,
- startupinfo=startupinfo)
+ startupinfo = None
+ if sys.platform.startswith('win'):
+ startupinfo = subprocess.STARTUPINFO()
+ startupinfo.dwFlags |= subprocess.STARTF_USESHOWWINDOW
+ subprocess.check_call(command, startupinfo=startupinfo)
im = Image.open(outfile)
im.load()
finally:
diff --git a/src/PIL/GifImagePlugin.py b/src/PIL/GifImagePlugin.py
index 4f4e62d6d..b328982aa 100644
--- a/src/PIL/GifImagePlugin.py
+++ b/src/PIL/GifImagePlugin.py
@@ -122,6 +122,8 @@ class GifImageFile(ImageFile.ImageFile):
if not self._seek_check(frame):
return
if frame < self.__frame:
+ if frame != 0:
+ self.im = None
self._seek(0)
last_frame = self.__frame
diff --git a/src/PIL/GimpPaletteFile.py b/src/PIL/GimpPaletteFile.py
index 6eef6a2dd..0ee899988 100644
--- a/src/PIL/GimpPaletteFile.py
+++ b/src/PIL/GimpPaletteFile.py
@@ -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)
diff --git a/src/PIL/IcoImagePlugin.py b/src/PIL/IcoImagePlugin.py
index 16683fb8f..c1c0775da 100644
--- a/src/PIL/IcoImagePlugin.py
+++ b/src/PIL/IcoImagePlugin.py
@@ -295,3 +295,5 @@ class IcoImageFile(ImageFile.ImageFile):
Image.register_open(IcoImageFile.format, IcoImageFile, _accept)
Image.register_save(IcoImageFile.format, _save)
Image.register_extension(IcoImageFile.format, ".ico")
+
+Image.register_mime(IcoImageFile.format, "image/x-icon")
diff --git a/src/PIL/Image.py b/src/PIL/Image.py
index 24e0d7fab..18121c6fe 100644
--- a/src/PIL/Image.py
+++ b/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")),
@@ -578,7 +578,12 @@ class Image(object):
return self
def __exit__(self, *args):
- self.close()
+ if hasattr(self, 'fp') and getattr(self, '_exclusive_fp', False):
+ if hasattr(self, "_close__fp"):
+ self._close__fp()
+ if self.fp:
+ self.fp.close()
+ self.fp = None
def close(self):
"""
@@ -610,12 +615,7 @@ class Image(object):
if sys.version_info.major >= 3:
def __del__(self):
- if hasattr(self, "_close__fp"):
- self._close__fp()
- if (hasattr(self, 'fp') and hasattr(self, '_exclusive_fp')
- and self.fp and self._exclusive_fp):
- self.fp.close()
- self.fp = None
+ self.__exit__()
def _copy(self):
self.load()
@@ -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
@@ -2005,9 +2017,6 @@ class Image(object):
**EOFError** exception. When a sequence file is opened, the
library automatically seeks to frame 0.
- Note that in the current version of the library, most sequence
- formats only allow you to seek to the next frame.
-
See :py:meth:`~PIL.Image.Image.tell`.
:param frame: Frame number, starting at 0.
@@ -2374,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):
@@ -2995,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(" 4:
+ offset, = 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))
diff --git a/src/PIL/ImageCms.py b/src/PIL/ImageCms.py
index 2cd6f33b5..c75ad6a66 100644
--- a/src/PIL/ImageCms.py
+++ b/src/PIL/ImageCms.py
@@ -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)
diff --git a/src/PIL/ImageDraw.py b/src/PIL/ImageDraw.py
index ac549790a..86512bb82 100644
--- a/src/PIL/ImageDraw.py
+++ b/src/PIL/ImageDraw.py
@@ -282,13 +282,17 @@ class ImageDraw(object):
self.draw.draw_bitmap(xy, mask, ink)
def multiline_text(self, xy, text, fill=None, font=None, anchor=None,
- spacing=4, align="left", direction=None, features=None):
+ spacing=4, align="left", direction=None, features=None,
+ language=None):
widths = []
max_width = 0
lines = self._multiline_split(text)
line_spacing = self.textsize('A', font=font)[1] + spacing
for line in lines:
- line_width, line_height = self.textsize(line, font)
+ line_width, line_height = self.textsize(line, font,
+ direction=direction,
+ features=features,
+ language=language)
widths.append(line_width)
max_width = max(max_width, line_width)
left, top = xy
@@ -302,29 +306,30 @@ class ImageDraw(object):
else:
raise ValueError('align must be "left", "center" or "right"')
self.text((left, top), line, fill, font, anchor,
- direction=direction, features=features)
+ direction=direction, features=features, language=language)
top += line_spacing
left = xy[0]
def textsize(self, text, font=None, spacing=4, direction=None,
- features=None):
+ features=None, language=None):
"""Get the size of a given string, in pixels."""
if self._multiline_check(text):
return self.multiline_textsize(text, font, spacing,
- direction, features)
+ direction, features, language)
if font is None:
font = self.getfont()
- return font.getsize(text, direction, features)
+ return font.getsize(text, direction, features, language)
def multiline_textsize(self, text, font=None, spacing=4, direction=None,
- features=None):
+ features=None, language=None):
max_width = 0
lines = self._multiline_split(text)
line_spacing = self.textsize('A', font=font)[1] + spacing
for line in lines:
line_width, line_height = self.textsize(line, font, spacing,
- direction, features)
+ direction, features,
+ language)
max_width = max(max_width, line_width)
return max_width, len(lines)*line_spacing - spacing
diff --git a/src/PIL/ImageFile.py b/src/PIL/ImageFile.py
index 2e1f28003..06e7b76ba 100644
--- a/src/PIL/ImageFile.py
+++ b/src/PIL/ImageFile.py
@@ -596,8 +596,6 @@ class PyDecoder(object):
Override to perform the decoding process.
:param buffer: A bytes object with the data to be decoded.
- If `handles_eof` is set, then `buffer` will be empty and `self.fd`
- will be set.
:returns: A tuple of (bytes consumed, errcode).
If finished with decoding return <0 for the bytes consumed.
Err codes are from `ERRORS`
diff --git a/src/PIL/ImageFont.py b/src/PIL/ImageFont.py
index 7454b4413..580aa8744 100644
--- a/src/PIL/ImageFont.py
+++ b/src/PIL/ImageFont.py
@@ -158,17 +158,17 @@ class FreeTypeFont(object):
def getmetrics(self):
return self.font.ascent, self.font.descent
- def getsize(self, text, direction=None, features=None):
- size, offset = self.font.getsize(text, direction, features)
+ def getsize(self, text, direction=None, features=None, language=None):
+ size, offset = self.font.getsize(text, direction, features, language)
return (size[0] + offset[0], size[1] + offset[1])
- def getsize_multiline(self, text, direction=None,
- spacing=4, features=None):
+ def getsize_multiline(self, text, direction=None, spacing=4,
+ features=None, language=None):
max_width = 0
lines = self._multiline_split(text)
line_spacing = self.getsize('A')[1] + spacing
for line in lines:
- line_width, line_height = self.getsize(line, direction, features)
+ line_width, line_height = self.getsize(line, direction, features, language)
max_width = max(max_width, line_width)
return max_width, len(lines)*line_spacing - spacing
@@ -176,15 +176,15 @@ class FreeTypeFont(object):
def getoffset(self, text):
return self.font.getsize(text)[1]
- def getmask(self, text, mode="", direction=None, features=None):
- return self.getmask2(text, mode, direction=direction,
- features=features)[0]
+ def getmask(self, text, mode="", direction=None, features=None, language=None):
+ return self.getmask2(text, mode, direction=direction, features=features,
+ language=language)[0]
def getmask2(self, text, mode="", fill=Image.core.fill, direction=None,
- features=None, *args, **kwargs):
- size, offset = self.font.getsize(text, direction, features)
+ features=None, language=None, *args, **kwargs):
+ size, offset = self.font.getsize(text, direction, features, language)
im = fill("L", size, 0)
- self.font.render(text, im.id, mode == "1", direction, features)
+ self.font.render(text, im.id, mode == "1", direction, features, language)
return im, offset
def font_variant(self, font=None, size=None, index=None, encoding=None,
diff --git a/src/PIL/ImageOps.py b/src/PIL/ImageOps.py
index b93f6f35d..ab6a3d2c4 100644
--- a/src/PIL/ImageOps.py
+++ b/src/PIL/ImageOps.py
@@ -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()
diff --git a/src/PIL/IptcImagePlugin.py b/src/PIL/IptcImagePlugin.py
index 5055d2a00..371bb3acf 100644
--- a/src/PIL/IptcImagePlugin.py
+++ b/src/PIL/IptcImagePlugin.py
@@ -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
diff --git a/src/PIL/JpegImagePlugin.py b/src/PIL/JpegImagePlugin.py
index 2f76e9675..3caedbd92 100644
--- a/src/PIL/JpegImagePlugin.py
+++ b/src/PIL/JpegImagePlugin.py
@@ -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
diff --git a/src/PIL/MpoImagePlugin.py b/src/PIL/MpoImagePlugin.py
index 64c0f57a4..ead2f1fff 100644
--- a/src/PIL/MpoImagePlugin.py
+++ b/src/PIL/MpoImagePlugin.py
@@ -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
diff --git a/src/PIL/PcxImagePlugin.py b/src/PIL/PcxImagePlugin.py
index 8094a2866..02dbe26e0 100644
--- a/src/PIL/PcxImagePlugin.py
+++ b/src/PIL/PcxImagePlugin.py
@@ -180,3 +180,5 @@ Image.register_open(PcxImageFile.format, PcxImageFile, _accept)
Image.register_save(PcxImageFile.format, _save)
Image.register_extension(PcxImageFile.format, ".pcx")
+
+Image.register_mime(PcxImageFile.format, "image/x-pcx")
diff --git a/src/PIL/PngImagePlugin.py b/src/PIL/PngImagePlugin.py
index df632af1d..4e192ecd6 100644
--- a/src/PIL/PngImagePlugin.py
+++ b/src/PIL/PngImagePlugin.py
@@ -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)
# --------------------------------------------------------------------
@@ -707,6 +718,7 @@ _OUTMODES = {
"L": ("L", b'\x08\x00'),
"LA": ("LA", b'\x08\x04'),
"I": ("I;16B", b'\x10\x00'),
+ "I;16": ("I;16B", b'\x10\x00'),
"P;1": ("P;1", b'\x01\x03'),
"P;2": ("P;2", b'\x02\x03'),
"P;4": ("P;4", b'\x04\x03'),
@@ -840,7 +852,7 @@ def _save(im, fp, filename, chunk=putchunk):
transparency = max(0, min(255, transparency))
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":
@@ -874,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)
diff --git a/src/PIL/PpmImagePlugin.py b/src/PIL/PpmImagePlugin.py
index e3e411cad..a1ac8d69e 100644
--- a/src/PIL/PpmImagePlugin.py
+++ b/src/PIL/PpmImagePlugin.py
@@ -164,6 +164,6 @@ def _save(im, fp, filename):
Image.register_open(PpmImageFile.format, PpmImageFile, _accept)
Image.register_save(PpmImageFile.format, _save)
-Image.register_extensions(PpmImageFile.format, [".pbm", ".pgm", ".ppm"])
+Image.register_extensions(PpmImageFile.format, [".pbm", ".pgm", ".ppm", ".pnm"])
Image.register_mime(PpmImageFile.format, "image/x-portable-anymap")
diff --git a/src/PIL/SpiderImagePlugin.py b/src/PIL/SpiderImagePlugin.py
index 4d5aa3f0e..b601847cb 100644
--- a/src/PIL/SpiderImagePlugin.py
+++ b/src/PIL/SpiderImagePlugin.py
@@ -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
diff --git a/src/PIL/TgaImagePlugin.py b/src/PIL/TgaImagePlugin.py
index 26b1e9c60..ae9697b29 100644
--- a/src/PIL/TgaImagePlugin.py
+++ b/src/PIL/TgaImagePlugin.py
@@ -226,4 +226,6 @@ def _save(im, fp, filename):
Image.register_open(TgaImageFile.format, TgaImageFile)
Image.register_save(TgaImageFile.format, _save)
-Image.register_extension(TgaImageFile.format, ".tga")
+Image.register_extensions(TgaImageFile.format, [".tga", ".icb", ".vda", ".vst"])
+
+Image.register_mime(TgaImageFile.format, "image/x-tga")
diff --git a/src/PIL/TiffImagePlugin.py b/src/PIL/TiffImagePlugin.py
index 2e05c9a0b..44dde1995 100644
--- a/src/PIL/TiffImagePlugin.py
+++ b/src/PIL/TiffImagePlugin.py
@@ -263,10 +263,10 @@ OPEN_INFO = {
(II, 5, (1,), 1, (8, 8, 8, 8, 8, 8), (0, 0)): ("CMYK", "CMYKXX"),
(MM, 5, (1,), 1, (8, 8, 8, 8, 8, 8), (0, 0)): ("CMYK", "CMYKXX"),
- # JPEG compressed images handled by LibTiff and auto-converted to RGB
+ # JPEG compressed images handled by LibTiff and auto-converted to RGBX
# Minimal Baseline TIFF requires YCbCr images to have 3 SamplesPerPixel
- (II, 6, (1,), 1, (8, 8, 8), ()): ("RGB", "RGB"),
- (MM, 6, (1,), 1, (8, 8, 8), ()): ("RGB", "RGB"),
+ (II, 6, (1,), 1, (8, 8, 8), ()): ("RGB", "RGBX"),
+ (MM, 6, (1,), 1, (8, 8, 8), ()): ("RGB", "RGBX"),
(II, 8, (1,), 1, (8, 8, 8), ()): ("LAB", "LAB"),
(MM, 8, (1,), 1, (8, 8, 8), ()): ("LAB", "LAB"),
@@ -785,17 +785,12 @@ class ImageFileDirectory_v2(MutableMapping):
warnings.warn(str(msg))
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)
@@ -1191,6 +1183,10 @@ class TiffImageFile(ImageFile.ImageFile):
# the specification
photo = self.tag_v2.get(PHOTOMETRIC_INTERPRETATION, 0)
+ # old style jpeg compression images most certainly are YCbCr
+ if self._compression == "tiff_jpeg":
+ photo = 6
+
fillorder = self.tag_v2.get(FILLORDER, 1)
if DEBUG:
@@ -1257,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
@@ -1472,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
diff --git a/src/PIL/WebPImagePlugin.py b/src/PIL/WebPImagePlugin.py
index 212e6b4c7..f2a99bb9d 100644
--- a/src/PIL/WebPImagePlugin.py
+++ b/src/PIL/WebPImagePlugin.py
@@ -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):
@@ -186,7 +187,7 @@ def _save_all(im, fp, filename):
# will preserve non-alpha modes
total = 0
for ims in [im]+append_images:
- total += 1 if not hasattr(ims, "n_frames") else ims.n_frames
+ total += getattr(ims, "n_frames", 1)
if total == 1:
_save(im, fp, filename)
return
@@ -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
@@ -254,10 +257,7 @@ def _save_all(im, fp, filename):
try:
for ims in [im]+append_images:
# Get # of frames in this image
- if not hasattr(ims, "n_frames"):
- nfr = 1
- else:
- nfr = ims.n_frames
+ nfr = getattr(ims, "n_frames", 1)
for idx in range(nfr):
ims.seek(idx)
@@ -318,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:
diff --git a/src/PIL/WmfImagePlugin.py b/src/PIL/WmfImagePlugin.py
index 06eb851c9..413bd1847 100644
--- a/src/PIL/WmfImagePlugin.py
+++ b/src/PIL/WmfImagePlugin.py
@@ -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
diff --git a/src/PIL/_version.py b/src/PIL/_version.py
index 0b0d90e80..7db210d63 100644
--- a/src/PIL/_version.py
+++ b/src/PIL/_version.py
@@ -1,2 +1,2 @@
# Master version for Pillow
-__version__ = '6.0.0.dev0'
+__version__ = '6.1.0.dev0'
diff --git a/src/_imaging.c b/src/_imaging.c
index 7715a7995..037348baa 100644
--- a/src/_imaging.c
+++ b/src/_imaging.c
@@ -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))
diff --git a/src/_imagingcms.c b/src/_imagingcms.c
index 5e4196cb7..2c9f3aa68 100644
--- a/src/_imagingcms.c
+++ b/src/_imagingcms.c
@@ -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));
}
diff --git a/src/_imagingft.c b/src/_imagingft.c
index f94e55803..6b65e2c33 100644
--- a/src/_imagingft.c
+++ b/src/_imagingft.c
@@ -18,6 +18,7 @@
* Copyright (c) 1998-2007 by Secret Labs AB
*/
+#define PY_SSIZE_T_CLEAN
#include "Python.h"
#include "Imaging.h"
@@ -87,6 +88,10 @@ typedef bool (*t_raqm_set_text_utf8) (raqm_t *rq,
size_t len);
typedef bool (*t_raqm_set_par_direction) (raqm_t *rq,
raqm_direction_t dir);
+typedef bool (*t_raqm_set_language) (raqm_t *rq,
+ const char *lang,
+ size_t start,
+ size_t len);
typedef bool (*t_raqm_add_font_feature) (raqm_t *rq,
const char *feature,
int len);
@@ -106,6 +111,7 @@ typedef struct {
t_raqm_set_text set_text;
t_raqm_set_text_utf8 set_text_utf8;
t_raqm_set_par_direction set_par_direction;
+ t_raqm_set_language set_language;
t_raqm_add_font_feature add_font_feature;
t_raqm_set_freetype_face set_freetype_face;
t_raqm_layout layout;
@@ -160,6 +166,7 @@ setraqm(void)
p_raqm.set_text = (t_raqm_set_text)dlsym(p_raqm.raqm, "raqm_set_text");
p_raqm.set_text_utf8 = (t_raqm_set_text_utf8)dlsym(p_raqm.raqm, "raqm_set_text_utf8");
p_raqm.set_par_direction = (t_raqm_set_par_direction)dlsym(p_raqm.raqm, "raqm_set_par_direction");
+ p_raqm.set_language = (t_raqm_set_language)dlsym(p_raqm.raqm, "raqm_set_language");
p_raqm.add_font_feature = (t_raqm_add_font_feature)dlsym(p_raqm.raqm, "raqm_add_font_feature");
p_raqm.set_freetype_face = (t_raqm_set_freetype_face)dlsym(p_raqm.raqm, "raqm_set_freetype_face");
p_raqm.layout = (t_raqm_layout)dlsym(p_raqm.raqm, "raqm_layout");
@@ -176,6 +183,7 @@ setraqm(void)
p_raqm.set_text &&
p_raqm.set_text_utf8 &&
p_raqm.set_par_direction &&
+ p_raqm.set_language &&
p_raqm.add_font_feature &&
p_raqm.set_freetype_face &&
p_raqm.layout &&
@@ -190,6 +198,7 @@ setraqm(void)
p_raqm.set_text = (t_raqm_set_text)GetProcAddress(p_raqm.raqm, "raqm_set_text");
p_raqm.set_text_utf8 = (t_raqm_set_text_utf8)GetProcAddress(p_raqm.raqm, "raqm_set_text_utf8");
p_raqm.set_par_direction = (t_raqm_set_par_direction)GetProcAddress(p_raqm.raqm, "raqm_set_par_direction");
+ p_raqm.set_language = (t_raqm_set_language)GetProcAddress(p_raqm.raqm, "raqm_set_language");
p_raqm.add_font_feature = (t_raqm_add_font_feature)GetProcAddress(p_raqm.raqm, "raqm_add_font_feature");
p_raqm.set_freetype_face = (t_raqm_set_freetype_face)GetProcAddress(p_raqm.raqm, "raqm_set_freetype_face");
p_raqm.layout = (t_raqm_layout)GetProcAddress(p_raqm.raqm, "raqm_layout");
@@ -205,6 +214,7 @@ setraqm(void)
p_raqm.set_text &&
p_raqm.set_text_utf8 &&
p_raqm.set_par_direction &&
+ p_raqm.set_language &&
p_raqm.add_font_feature &&
p_raqm.set_freetype_face &&
p_raqm.layout &&
@@ -228,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
@@ -332,8 +342,8 @@ font_getchar(PyObject* string, int index, FT_ULong* char_out)
}
static size_t
-text_layout_raqm(PyObject* string, FontObject* self, const char* dir,
- PyObject *features ,GlyphInfo **glyph_info, int mask)
+text_layout_raqm(PyObject* string, FontObject* self, const char* dir, PyObject *features,
+ const char* lang, GlyphInfo **glyph_info, int mask)
{
int i = 0;
raqm_t *rq;
@@ -341,6 +351,7 @@ text_layout_raqm(PyObject* string, FontObject* self, const char* dir,
raqm_glyph_t *glyphs = NULL;
raqm_glyph_t_01 *glyphs_01 = NULL;
raqm_direction_t direction;
+ size_t start = 0;
rq = (*p_raqm.create)();
if (rq == NULL) {
@@ -360,6 +371,13 @@ text_layout_raqm(PyObject* string, FontObject* self, const char* dir,
PyErr_SetString(PyExc_ValueError, "raqm_set_text() failed");
goto failed;
}
+ if (lang) {
+ if (!(*p_raqm.set_language)(rq, lang, start, size)) {
+ PyErr_SetString(PyExc_ValueError, "raqm_set_language() failed");
+ goto failed;
+ }
+ }
+
}
#if PY_VERSION_HEX < 0x03000000
else if (PyString_Check(string)) {
@@ -372,6 +390,12 @@ text_layout_raqm(PyObject* string, FontObject* self, const char* dir,
PyErr_SetString(PyExc_ValueError, "raqm_set_text_utf8() failed");
goto failed;
}
+ if (lang) {
+ if (!(*p_raqm.set_language)(rq, lang, start, size)) {
+ PyErr_SetString(PyExc_ValueError, "raqm_set_language() failed");
+ goto failed;
+ }
+ }
}
#endif
else {
@@ -498,8 +522,8 @@ failed:
}
static size_t
-text_layout_fallback(PyObject* string, FontObject* self, const char* dir,
- PyObject *features ,GlyphInfo **glyph_info, int mask)
+text_layout_fallback(PyObject* string, FontObject* self, const char* dir, PyObject *features,
+ const char* lang, GlyphInfo **glyph_info, int mask)
{
int error, load_flags;
FT_ULong ch;
@@ -509,8 +533,8 @@ text_layout_fallback(PyObject* string, FontObject* self, const char* dir,
FT_UInt last_index = 0;
int i;
- if (features != Py_None || dir != NULL) {
- PyErr_SetString(PyExc_KeyError, "setting text direction or font features is not supported without libraqm");
+ if (features != Py_None || dir != NULL || lang != NULL) {
+ PyErr_SetString(PyExc_KeyError, "setting text direction, language or font features is not supported without libraqm");
}
#if PY_VERSION_HEX >= 0x03000000
if (!PyUnicode_Check(string)) {
@@ -564,15 +588,15 @@ text_layout_fallback(PyObject* string, FontObject* self, const char* dir,
}
static size_t
-text_layout(PyObject* string, FontObject* self, const char* dir,
- PyObject *features, GlyphInfo **glyph_info, int mask)
+text_layout(PyObject* string, FontObject* self, const char* dir, PyObject *features,
+ const char* lang, GlyphInfo **glyph_info, int mask)
{
size_t count;
if (p_raqm.raqm && self->layout_engine == LAYOUT_RAQM) {
- count = text_layout_raqm(string, self, dir, features, glyph_info, mask);
+ count = text_layout_raqm(string, self, dir, features, lang, glyph_info, mask);
} else {
- count = text_layout_fallback(string, self, dir, features, glyph_info, mask);
+ count = text_layout_fallback(string, self, dir, features, lang, glyph_info, mask);
}
return count;
}
@@ -584,6 +608,7 @@ font_getsize(FontObject* self, PyObject* args)
FT_Face face;
int xoffset, yoffset;
const char *dir = NULL;
+ const char *lang = NULL;
size_t count;
GlyphInfo *glyph_info = NULL;
PyObject *features = Py_None;
@@ -591,14 +616,14 @@ font_getsize(FontObject* self, PyObject* args)
/* calculate size and bearing for a given string */
PyObject* string;
- if (!PyArg_ParseTuple(args, "O|zO:getsize", &string, &dir, &features))
+ if (!PyArg_ParseTuple(args, "O|zOz:getsize", &string, &dir, &features, &lang))
return NULL;
face = NULL;
xoffset = yoffset = 0;
y_max = y_min = 0;
- count = text_layout(string, self, dir, features, &glyph_info, 0);
+ count = text_layout(string, self, dir, features, lang, &glyph_info, 0);
if (PyErr_Occurred()) {
return NULL;
}
@@ -691,16 +716,17 @@ font_render(FontObject* self, PyObject* args)
int temp;
int xx, x0, x1;
const char *dir = NULL;
+ const char *lang = NULL;
size_t count;
GlyphInfo *glyph_info;
PyObject *features = NULL;
- if (!PyArg_ParseTuple(args, "On|izO:render", &string, &id, &mask, &dir, &features)) {
+ if (!PyArg_ParseTuple(args, "On|izOz:render", &string, &id, &mask, &dir, &features, &lang)) {
return NULL;
}
glyph_info = NULL;
- count = text_layout(string, self, dir, features, &glyph_info, mask);
+ count = text_layout(string, self, dir, features, lang, &glyph_info, mask);
if (PyErr_Occurred()) {
return NULL;
}
diff --git a/src/decode.c b/src/decode.c
index be1b503e6..562bc7436 100644
--- a/src/decode.c
+++ b/src/decode.c
@@ -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))
diff --git a/src/encode.c b/src/encode.c
index 13da1b999..6832f90c8 100644
--- a/src/encode.c
+++ b/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,
diff --git a/src/libImaging/Convert.c b/src/libImaging/Convert.c
index 39ddf8721..a6fefca32 100644
--- a/src/libImaging/Convert.c
+++ b/src/libImaging/Convert.c
@@ -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;
}
diff --git a/src/libImaging/Geometry.c b/src/libImaging/Geometry.c
index 5358f6eeb..23236a971 100644
--- a/src/libImaging/Geometry.c
+++ b/src/libImaging/Geometry.c
@@ -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,11 @@ 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--) { \
- imOut->image[xr][yyy] = in[xxx]; \
+ INT* out = (INT *)imOut->image[xr]; \
+ out[yyy] = in[xxx]; \
} \
} \
} \
@@ -118,10 +119,15 @@ ImagingRotate90(Imaging imOut, Imaging imIn)
ImagingSectionEnter(&cookie);
- if (imIn->image8)
- ROTATE_90(UINT8, image8)
- else
- ROTATE_90(INT32, image32)
+ if (imIn->image8) {
+ if (strncmp(imIn->mode, "I;16", 4) == 0) {
+ ROTATE_90(UINT16, image8);
+ } else {
+ ROTATE_90(UINT8, image8);
+ }
+ } else {
+ ROTATE_90(INT32, image32);
+ }
ImagingSectionLeave(&cookie);
@@ -155,9 +161,10 @@ 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++) { \
- imOut->image[xxx][yyy] = in[xxx]; \
+ INT* out = (INT *)imOut->image[xxx]; \
+ out[yyy] = in[xxx]; \
} \
} \
} \
@@ -167,10 +174,15 @@ ImagingTranspose(Imaging imOut, Imaging imIn)
ImagingSectionEnter(&cookie);
- if (imIn->image8)
- TRANSPOSE(UINT8, image8)
- else
- TRANSPOSE(INT32, image32)
+ if (imIn->image8) {
+ if (strncmp(imIn->mode, "I;16", 4) == 0) {
+ TRANSPOSE(UINT16, image8);
+ } else {
+ TRANSPOSE(UINT8, image8);
+ }
+ } else {
+ TRANSPOSE(INT32, image32);
+ }
ImagingSectionLeave(&cookie);
@@ -205,10 +217,11 @@ 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--) { \
- imOut->image[xr][yr] = in[xxx]; \
+ INT* out = (INT *)imOut->image[xr]; \
+ out[yr] = in[xxx]; \
} \
} \
} \
@@ -218,10 +231,15 @@ ImagingTransverse(Imaging imOut, Imaging imIn)
ImagingSectionEnter(&cookie);
- if (imIn->image8)
- TRANSVERSE(UINT8, image8)
- else
- TRANSVERSE(INT32, image32)
+ if (imIn->image8) {
+ if (strncmp(imIn->mode, "I;16", 4) == 0) {
+ TRANSVERSE(UINT16, image8);
+ } else {
+ TRANSVERSE(UINT8, image8);
+ }
+ } else {
+ TRANSVERSE(INT32, image32);
+ }
ImagingSectionLeave(&cookie);
@@ -246,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]; \
@@ -299,9 +317,10 @@ 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++) { \
- imOut->image[xxx][yr] = in[xxx]; \
+ INT* out = (INT *)imOut->image[xxx]; \
+ out[yr] = in[xxx]; \
} \
} \
} \
@@ -311,10 +330,15 @@ ImagingRotate270(Imaging imOut, Imaging imIn)
ImagingSectionEnter(&cookie);
- if (imIn->image8)
- ROTATE_270(UINT8, image8)
- else
- ROTATE_270(INT32, image32)
+ if (imIn->image8) {
+ if (strncmp(imIn->mode, "I;16", 4) == 0) {
+ ROTATE_270(UINT16, image8);
+ } else {
+ ROTATE_270(UINT8, image8);
+ }
+ } else {
+ ROTATE_270(INT32, image32);
+ }
ImagingSectionLeave(&cookie);
diff --git a/src/libImaging/Pack.c b/src/libImaging/Pack.c
index 5c298c6c5..56aebf58e 100644
--- a/src/libImaging/Pack.c
+++ b/src/libImaging/Pack.c
@@ -639,6 +639,7 @@ static struct {
/* storage modes */
{"I;16", "I;16", 16, copy2},
+ {"I;16", "I;16B", 16, packI16N_I16B},
{"I;16B", "I;16B", 16, copy2},
{"I;16L", "I;16L", 16, copy2},
{"I;16", "I;16N", 16, packI16N_I16}, // LibTiff native->image endian.
diff --git a/src/libImaging/QuantHash.c b/src/libImaging/QuantHash.c
index 48b7a973e..3fcbf3c02 100644
--- a/src/libImaging/QuantHash.c
+++ b/src/libImaging/QuantHash.c
@@ -35,8 +35,6 @@ struct _HashTable {
uint32_t count;
HashFunc hashFunc;
HashCmpFunc cmpFunc;
- KeyDestroyFunc keyDestroyFunc;
- ValDestroyFunc valDestroyFunc;
void *userData;
};
@@ -51,8 +49,6 @@ HashTable *hashtable_new(HashFunc hf,HashCmpFunc cf) {
if (!h) { return NULL; }
h->hashFunc=hf;
h->cmpFunc=cf;
- h->keyDestroyFunc=NULL;
- h->valDestroyFunc=NULL;
h->length=MIN_LENGTH;
h->count=0;
h->userData=NULL;
@@ -62,15 +58,6 @@ HashTable *hashtable_new(HashFunc hf,HashCmpFunc cf) {
return h;
}
-static void _hashtable_destroy(const HashTable *h,const HashKey_t key,const HashVal_t val,void *u) {
- if (h->keyDestroyFunc) {
- h->keyDestroyFunc(h,key);
- }
- if (h->valDestroyFunc) {
- h->valDestroyFunc(h,val);
- }
-}
-
static uint32_t _findPrime(uint32_t start,int dir) {
static int unit[]={0,1,0,1,0,0,0,1,0,1,0,1,0,1,0,0};
uint32_t t;
@@ -144,12 +131,6 @@ static int _hashtable_insert_node(HashTable *h,HashNode *node,int resize,int upd
free(node);
return 1;
} else {
- if (h->valDestroyFunc) {
- h->valDestroyFunc(h,nv->value);
- }
- if (h->keyDestroyFunc) {
- h->keyDestroyFunc(h,nv->key);
- }
nv->key=node->key;
nv->value=node->value;
free(node);
@@ -180,7 +161,6 @@ static int _hashtable_insert(HashTable *h,HashKey_t key,HashVal_t val,int resize
nv=*n;
i=h->cmpFunc(h,nv->key,key);
if (!i) {
- if (h->valDestroyFunc) { h->valDestroyFunc(h,nv->value); }
nv->value=val;
return 1;
} else if (i>0) {
@@ -202,34 +182,6 @@ static int _hashtable_insert(HashTable *h,HashKey_t key,HashVal_t val,int resize
}
}
-static int _hashtable_lookup_or_insert(HashTable *h,HashKey_t key,HashVal_t *retVal,HashVal_t newVal,int resize) {
- HashNode **n,*nv;
- HashNode *t;
- int i;
- uint32_t hash=h->hashFunc(h,key)%h->length;
-
- for (n=&(h->table[hash]);*n;n=&((*n)->next)) {
- nv=*n;
- i=h->cmpFunc(h,nv->key,key);
- if (!i) {
- *retVal=nv->value;
- return 1;
- } else if (i>0) {
- break;
- }
- }
- t=malloc(sizeof(HashNode));
- if (!t) return 0;
- t->next=*n;
- *n=t;
- t->key=key;
- t->value=newVal;
- *retVal=newVal;
- h->count++;
- if (resize) _hashtable_resize(h);
- return 1;
-}
-
int hashtable_insert_or_update_computed(HashTable *h,
HashKey_t key,
ComputeFunc newFunc,
@@ -243,14 +195,8 @@ int hashtable_insert_or_update_computed(HashTable *h,
nv=*n;
i=h->cmpFunc(h,nv->key,key);
if (!i) {
- HashVal_t old=nv->value;
if (existsFunc) {
existsFunc(h,nv->key,&(nv->value));
- if (nv->value!=old) {
- if (h->valDestroyFunc) {
- h->valDestroyFunc(h,old);
- }
- }
} else {
return 0;
}
@@ -275,10 +221,6 @@ int hashtable_insert_or_update_computed(HashTable *h,
return 1;
}
-int hashtable_update(HashTable *h,HashKey_t key,HashVal_t val) {
- return _hashtable_insert(h,key,val,1,0);
-}
-
int hashtable_insert(HashTable *h,HashKey_t key,HashVal_t val) {
return _hashtable_insert(h,key,val,1,0);
}
@@ -314,9 +256,6 @@ void hashtable_free(HashTable *h) {
uint32_t i;
if (h->table) {
- if (h->keyDestroyFunc || h->keyDestroyFunc) {
- hashtable_foreach(h,_hashtable_destroy,NULL);
- }
for (i=0;ilength;i++) {
for (n=h->table[i];n;n=nn) {
nn=n->next;
@@ -328,84 +267,10 @@ void hashtable_free(HashTable *h) {
free(h);
}
-ValDestroyFunc hashtable_set_value_destroy_func(HashTable *h,ValDestroyFunc d) {
- ValDestroyFunc r=h->valDestroyFunc;
- h->valDestroyFunc=d;
- return r;
-}
-
-KeyDestroyFunc hashtable_set_key_destroy_func(HashTable *h,KeyDestroyFunc d) {
- KeyDestroyFunc r=h->keyDestroyFunc;
- h->keyDestroyFunc=d;
- return r;
-}
-
-static int _hashtable_remove(HashTable *h,
- const HashKey_t key,
- HashKey_t *keyRet,
- HashVal_t *valRet,
- int resize) {
- uint32_t hash=h->hashFunc(h,key)%h->length;
- HashNode *n,*p;
- int i;
-
- for (p=NULL,n=h->table[hash];n;p=n,n=n->next) {
- i=h->cmpFunc(h,n->key,key);
- if (!i) {
- if (p) p=n->next; else h->table[hash]=n->next;
- *keyRet=n->key;
- *valRet=n->value;
- free(n);
- h->count++;
- return 1;
- } else if (i>0) {
- break;
- }
- }
- return 0;
-}
-
-static int _hashtable_delete(HashTable *h,const HashKey_t key,int resize) {
- uint32_t hash=h->hashFunc(h,key)%h->length;
- HashNode *n,*p;
- int i;
-
- for (p=NULL,n=h->table[hash];n;p=n,n=n->next) {
- i=h->cmpFunc(h,n->key,key);
- if (!i) {
- if (p) p=n->next; else h->table[hash]=n->next;
- if (h->valDestroyFunc) { h->valDestroyFunc(h,n->value); }
- if (h->keyDestroyFunc) { h->keyDestroyFunc(h,n->key); }
- free(n);
- h->count++;
- return 1;
- } else if (i>0) {
- break;
- }
- }
- return 0;
-}
-
-int hashtable_remove(HashTable *h,const HashKey_t key,HashKey_t *keyRet,HashVal_t *valRet) {
- return _hashtable_remove(h,key,keyRet,valRet,1);
-}
-
-int hashtable_delete(HashTable *h,const HashKey_t key) {
- return _hashtable_delete(h,key,1);
-}
-
void hashtable_rehash_compute(HashTable *h,CollisionFunc cf) {
_hashtable_rehash(h,cf,h->length);
}
-void hashtable_rehash(HashTable *h) {
- _hashtable_rehash(h,NULL,h->length);
-}
-
-int hashtable_lookup_or_insert(HashTable *h,HashKey_t key,HashVal_t *valp,HashVal_t val) {
- return _hashtable_lookup_or_insert(h,key,valp,val,1);
-}
-
int hashtable_lookup(const HashTable *h,const HashKey_t key,HashVal_t *valp) {
uint32_t hash=h->hashFunc(h,key)%h->length;
HashNode *n;
diff --git a/src/libImaging/QuantHash.h b/src/libImaging/QuantHash.h
index 028b4af89..9874114e5 100644
--- a/src/libImaging/QuantHash.h
+++ b/src/libImaging/QuantHash.h
@@ -22,8 +22,6 @@ typedef uint32_t (*HashFunc)(const HashTable *,const HashKey_t);
typedef int (*HashCmpFunc)(const HashTable *,const HashKey_t,const HashKey_t);
typedef void (*IteratorFunc)(const HashTable *,const HashKey_t,const HashVal_t,void *);
typedef void (*IteratorUpdateFunc)(const HashTable *,const HashKey_t,HashVal_t *,void *);
-typedef void (*KeyDestroyFunc)(const HashTable *,HashKey_t);
-typedef void (*ValDestroyFunc)(const HashTable *,HashVal_t);
typedef void (*ComputeFunc)(const HashTable *,const HashKey_t,HashVal_t *);
typedef void (*CollisionFunc)(const HashTable *,HashKey_t *,HashVal_t *,HashKey_t,HashVal_t);
@@ -32,18 +30,11 @@ void hashtable_free(HashTable *h);
void hashtable_foreach(HashTable *h,IteratorFunc i,void *u);
void hashtable_foreach_update(HashTable *h,IteratorUpdateFunc i,void *u);
int hashtable_insert(HashTable *h,HashKey_t key,HashVal_t val);
-int hashtable_update(HashTable *h,HashKey_t key,HashVal_t val);
int hashtable_lookup(const HashTable *h,const HashKey_t key,HashVal_t *valp);
-int hashtable_lookup_or_insert(HashTable *h,HashKey_t key,HashVal_t *valp,HashVal_t val);
int hashtable_insert_or_update_computed(HashTable *h,HashKey_t key,ComputeFunc newFunc,ComputeFunc existsFunc);
-int hashtable_delete(HashTable *h,const HashKey_t key);
-int hashtable_remove(HashTable *h,const HashKey_t key,HashKey_t *keyRet,HashVal_t *valRet);
void *hashtable_set_user_data(HashTable *h,void *data);
void *hashtable_get_user_data(const HashTable *h);
-KeyDestroyFunc hashtable_set_key_destroy_func(HashTable *,KeyDestroyFunc d);
-ValDestroyFunc hashtable_set_value_destroy_func(HashTable *,ValDestroyFunc d);
uint32_t hashtable_get_count(const HashTable *h);
-void hashtable_rehash(HashTable *h);
void hashtable_rehash_compute(HashTable *h,CollisionFunc cf);
#endif // __QUANTHASH_H__
diff --git a/src/libImaging/TiffDecode.c b/src/libImaging/TiffDecode.c
index 64ac86e6a..381f795e0 100644
--- a/src/libImaging/TiffDecode.c
+++ b/src/libImaging/TiffDecode.c
@@ -124,6 +124,7 @@ toff_t _tiffSizeProc(thandle_t hdata) {
return (toff_t)state->size;
}
+
int _tiffMapProc(thandle_t hdata, tdata_t* pbase, toff_t* psize) {
TIFFSTATE *state = (TIFFSTATE *)hdata;
@@ -168,13 +169,117 @@ int ImagingLibTiffInit(ImagingCodecState state, int fp, int offset) {
return 1;
}
+
+int ReadTile(TIFF* tiff, UINT32 col, UINT32 row, UINT32* buffer) {
+ uint16 photometric;
+
+ TIFFGetField(tiff, TIFFTAG_PHOTOMETRIC, &photometric);
+
+ // To avoid dealing with YCbCr subsampling, let libtiff handle it
+ if (photometric == PHOTOMETRIC_YCBCR) {
+ UINT32 tile_width, tile_height, swap_line_size, i_row;
+ UINT32* swap_line;
+
+ TIFFGetField(tiff, TIFFTAG_TILEWIDTH, &tile_width);
+ TIFFGetField(tiff, TIFFTAG_TILELENGTH, &tile_height);
+
+ swap_line_size = tile_width * sizeof(UINT32);
+ if (tile_width != swap_line_size / sizeof(UINT32)) {
+ return -1;
+ }
+
+ /* Read the tile into an RGBA array */
+ if (!TIFFReadRGBATile(tiff, col, row, buffer)) {
+ return -1;
+ }
+
+ swap_line = (UINT32*)malloc(swap_line_size);
+ /*
+ * For some reason the TIFFReadRGBATile() function chooses the
+ * lower left corner as the origin. Vertically mirror scanlines.
+ */
+ for(i_row = 0; i_row < tile_height / 2; i_row++) {
+ UINT32 *top_line, *bottom_line;
+
+ top_line = buffer + tile_width * i_row;
+ bottom_line = buffer + tile_width * (tile_height - i_row - 1);
+
+ memcpy(swap_line, top_line, 4*tile_width);
+ memcpy(top_line, bottom_line, 4*tile_width);
+ memcpy(bottom_line, swap_line, 4*tile_width);
+ }
+
+ free(swap_line);
+
+ return 0;
+ }
+
+ if (TIFFReadTile(tiff, (tdata_t)buffer, col, row, 0, 0) == -1) {
+ TRACE(("Decode Error, Tile at %dx%d\n", col, row));
+ return -1;
+ }
+
+ TRACE(("Successfully read tile at %dx%d; \n\n", col, row));
+
+ return 0;
+}
+
+int ReadStrip(TIFF* tiff, UINT32 row, UINT32* buffer) {
+ uint16 photometric;
+ TIFFGetField(tiff, TIFFTAG_PHOTOMETRIC, &photometric);
+
+ // To avoid dealing with YCbCr subsampling, let libtiff handle it
+ if (photometric == PHOTOMETRIC_YCBCR) {
+ TIFFRGBAImage img;
+ char emsg[1024] = "";
+ UINT32 rows_per_strip, rows_to_read;
+ int ok;
+
+
+ TIFFGetFieldDefaulted(tiff, TIFFTAG_ROWSPERSTRIP, &rows_per_strip);
+ if ((row % rows_per_strip) != 0) {
+ TRACE(("Row passed to ReadStrip() must be first in a strip."));
+ return -1;
+ }
+
+ if (TIFFRGBAImageOK(tiff, emsg) && TIFFRGBAImageBegin(&img, tiff, 0, emsg)) {
+ TRACE(("Initialized RGBAImage\n"));
+
+ img.req_orientation = ORIENTATION_TOPLEFT;
+ img.row_offset = row;
+ img.col_offset = 0;
+
+ rows_to_read = min(rows_per_strip, img.height - row);
+
+ TRACE(("rows to read: %d\n", rows_to_read));
+ ok = TIFFRGBAImageGet(&img, buffer, img.width, rows_to_read);
+
+ TIFFRGBAImageEnd(&img);
+ } else {
+ ok = 0;
+ }
+
+ if (ok == 0) {
+ TRACE(("Decode Error, row %d; msg: %s\n", row, emsg));
+ return -1;
+ }
+
+ return 0;
+ }
+
+ if (TIFFReadEncodedStrip(tiff, TIFFComputeStrip(tiff, row, 0), (tdata_t)buffer, -1) == -1) {
+ TRACE(("Decode Error, strip %d\n", TIFFComputeStrip(tiff, row, 0)));
+ return -1;
+ }
+
+ return 0;
+}
+
int ImagingLibTiffDecode(Imaging im, ImagingCodecState state, UINT8* buffer, int bytes) {
TIFFSTATE *clientstate = (TIFFSTATE *)state->context;
char *filename = "tempfile.tif";
char *mode = "r";
TIFF *tiff;
- uint16 photometric = 0, compression;
-
/* buffer is the encoded file, bytes is the length of the encoded file */
/* it all ends up in state->buffer, which is a uint8* from Imaging.h */
@@ -235,19 +340,17 @@ int ImagingLibTiffDecode(Imaging im, ImagingCodecState state, UINT8* buffer, int
}
}
- TIFFGetFieldDefaulted(tiff, TIFFTAG_COMPRESSION, &compression);
- TIFFGetField(tiff, TIFFTAG_PHOTOMETRIC, &photometric);
- if (compression == COMPRESSION_JPEG && photometric == PHOTOMETRIC_YCBCR) {
- /* Set pseudo-tag to force automatic YCbCr->RGB conversion */
- TIFFSetField(tiff, TIFFTAG_JPEGCOLORMODE, JPEGCOLORMODE_RGB);
- }
-
if (TIFFIsTiled(tiff)) {
- uint32 x, y, tile_y;
- uint32 tileWidth, tileLength;
+ UINT32 x, y, tile_y, row_byte_size;
+ UINT32 tile_width, tile_length, current_tile_width;
UINT8 *new_data;
- state->bytes = TIFFTileSize(tiff);
+ TIFFGetField(tiff, TIFFTAG_TILEWIDTH, &tile_width);
+ TIFFGetField(tiff, TIFFTAG_TILELENGTH, &tile_length);
+
+ // We could use TIFFTileSize, but for YCbCr data it returns subsampled data size
+ row_byte_size = (tile_width * state->bits + 7) / 8;
+ state->bytes = row_byte_size * tile_length;
/* overflow check for malloc */
if (state->bytes > INT_MAX - 1) {
@@ -268,12 +371,9 @@ int ImagingLibTiffDecode(Imaging im, ImagingCodecState state, UINT8* buffer, int
TRACE(("TIFFTileSize: %d\n", state->bytes));
- TIFFGetField(tiff, TIFFTAG_TILEWIDTH, &tileWidth);
- TIFFGetField(tiff, TIFFTAG_TILELENGTH, &tileLength);
-
- for (y = state->yoff; y < state->ysize; y += tileLength) {
- for (x = state->xoff; x < state->xsize; x += tileWidth) {
- if (TIFFReadTile(tiff, (tdata_t)state->buffer, x, y, 0, 0) == -1) {
+ for (y = state->yoff; y < state->ysize; y += tile_length) {
+ for (x = state->xoff; x < state->xsize; x += tile_width) {
+ if (ReadTile(tiff, x, y, (UINT32*) state->buffer) == -1) {
TRACE(("Decode Error, Tile at %dx%d\n", x, y));
state->errcode = IMAGING_CODEC_BROKEN;
TIFFClose(tiff);
@@ -282,53 +382,68 @@ int ImagingLibTiffDecode(Imaging im, ImagingCodecState state, UINT8* buffer, int
TRACE(("Read tile at %dx%d; \n\n", x, y));
+ current_tile_width = min(tile_width, state->xsize - x);
+
// iterate over each line in the tile and stuff data into image
- for (tile_y = 0; tile_y < min(tileLength, state->ysize - y); tile_y++) {
+ for (tile_y = 0; tile_y < min(tile_length, state->ysize - y); tile_y++) {
+ TRACE(("Writing tile data at %dx%d using tile_width: %d; \n", tile_y + y, x, current_tile_width));
- TRACE(("Writing tile data at %dx%d using tilwWidth: %d; \n", tile_y + y, x, min(tileWidth, state->xsize - x)));
-
- // UINT8 * bbb = state->buffer + tile_y * (state->bytes / tileLength);
+ // UINT8 * bbb = state->buffer + tile_y * row_byte_size;
// TRACE(("chars: %x%x%x%x\n", ((UINT8 *)bbb)[0], ((UINT8 *)bbb)[1], ((UINT8 *)bbb)[2], ((UINT8 *)bbb)[3]));
state->shuffle((UINT8*) im->image[tile_y + y] + x * im->pixelsize,
- state->buffer + tile_y * (state->bytes / tileLength),
- min(tileWidth, state->xsize - x)
+ state->buffer + tile_y * row_byte_size,
+ current_tile_width
);
}
}
}
} else {
- tsize_t size;
+ UINT32 strip_row, row_byte_size;
+ UINT8 *new_data;
+ UINT32 rows_per_strip;
- size = TIFFScanlineSize(tiff);
- TRACE(("ScanlineSize: %lu \n", size));
- if (size > state->bytes) {
- TRACE(("Error, scanline size > buffer size\n"));
- state->errcode = IMAGING_CODEC_BROKEN;
+ TIFFGetField(tiff, TIFFTAG_ROWSPERSTRIP, &rows_per_strip);
+ TRACE(("RowsPerStrip: %u \n", rows_per_strip));
+
+ // We could use TIFFStripSize, but for YCbCr data it returns subsampled data size
+ row_byte_size = (state->xsize * state->bits + 7) / 8;
+ state->bytes = rows_per_strip * row_byte_size;
+
+ TRACE(("StripSize: %d \n", state->bytes));
+
+ /* realloc to fit whole strip */
+ new_data = realloc (state->buffer, state->bytes);
+ if (!new_data) {
+ state->errcode = IMAGING_CODEC_MEMORY;
TIFFClose(tiff);
return -1;
}
- // Have to do this row by row and shove stuff into the buffer that way,
- // with shuffle. (or, just alloc a buffer myself, then figure out how to get it
- // back in. Can't use read encoded stripe.
+ state->buffer = new_data;
- // This thing pretty much requires that I have the whole image in one shot.
- // Perhaps a stub version would work better???
- while(state->y < state->ysize){
- if (TIFFReadScanline(tiff, (tdata_t)state->buffer, (uint32)state->y, 0) == -1) {
- TRACE(("Decode Error, row %d\n", state->y));
+ for (; state->y < state->ysize; state->y += rows_per_strip) {
+ if (ReadStrip(tiff, state->y, (UINT32 *)state->buffer) == -1) {
+ TRACE(("Decode Error, strip %d\n", TIFFComputeStrip(tiff, state->y, 0)));
state->errcode = IMAGING_CODEC_BROKEN;
TIFFClose(tiff);
return -1;
}
- /* TRACE(("Decoded row %d \n", state->y)); */
- state->shuffle((UINT8*) im->image[state->y + state->yoff] +
- state->xoff * im->pixelsize,
- state->buffer,
- state->xsize);
- state->y++;
+ TRACE(("Decoded strip for row %d \n", state->y));
+
+ // iterate over each row in the strip and stuff data into image
+ for (strip_row = 0; strip_row < min(rows_per_strip, state->ysize - state->y); strip_row++) {
+ TRACE(("Writing data into line %d ; \n", state->y + strip_row));
+
+ // UINT8 * bbb = state->buffer + strip_row * (state->bytes / rows_per_strip);
+ // TRACE(("chars: %x %x %x %x\n", ((UINT8 *)bbb)[0], ((UINT8 *)bbb)[1], ((UINT8 *)bbb)[2], ((UINT8 *)bbb)[3]));
+
+ state->shuffle((UINT8*) im->image[state->y + state->yoff + strip_row] +
+ state->xoff * im->pixelsize,
+ state->buffer + strip_row * row_byte_size,
+ state->xsize);
+ }
}
}
diff --git a/src/libImaging/Unpack.c b/src/libImaging/Unpack.c
index e9921d2ca..ccfeabaf9 100644
--- a/src/libImaging/Unpack.c
+++ b/src/libImaging/Unpack.c
@@ -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},
diff --git a/winbuild/appveyor_install_pypy.cmd b/winbuild/appveyor_install_pypy.cmd
index 3092fc617..8c36268f0 100644
--- a/winbuild/appveyor_install_pypy.cmd
+++ b/winbuild/appveyor_install_pypy.cmd
@@ -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
diff --git a/winbuild/build.py b/winbuild/build.py
index 313a14ff4..01c847bae 100755
--- a/winbuild/build.py
+++ b/winbuild/build.py
@@ -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
diff --git a/winbuild/build_dep.py b/winbuild/build_dep.py
index 13296dc5b..1bf8b51a8 100644
--- a/winbuild/build_dep.py
+++ b/winbuild/build_dep.py
@@ -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))
diff --git a/winbuild/config.py b/winbuild/config.py
index da6f7855b..3b13951f1 100644
--- a/winbuild/config.py
+++ b/winbuild/config.py
@@ -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}}
@@ -33,9 +33,9 @@ libs = {
'dir': 'tiff-4.0.10',
},
'freetype': {
- 'url': 'https://download.savannah.gnu.org/releases/freetype/freetype-2.9.1.tar.gz', # noqa: E501
- 'filename': PILLOW_DEPENDS_DIR + 'freetype-2.9.1.tar.gz',
- 'dir': 'freetype-2.9.1',
+ 'url': 'https://download.savannah.gnu.org/releases/freetype/freetype-2.10.0.tar.gz', # noqa: E501
+ 'filename': PILLOW_DEPENDS_DIR + 'freetype-2.10.0.tar.gz',
+ 'dir': 'freetype-2.10.0',
},
'lcms': {
'url': SF_MIRROR+'/project/lcms/lcms/2.7/lcms2-2.7.zip',
@@ -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',
diff --git a/winbuild/fetch.py b/winbuild/fetch.py
index b7acb63ac..830a64ee5 100644
--- a/winbuild/fetch.py
+++ b/winbuild/fetch.py
@@ -9,7 +9,11 @@ def fetch(url):
if not os.path.exists(name):
print("Fetching", url)
- content = urllib.request.urlopen(url).read()
+ try:
+ r = urllib.request.urlopen(url)
+ except urllib.error.URLError:
+ r = urllib.request.urlopen(url)
+ content = r.read()
with open(name, 'wb') as fd:
fd.write(content)
return name