Merge pull request #1 from python-pillow/master

Update local base
This commit is contained in:
ABHISHAKE KUMAR BOJJA 2019-04-06 13:39:17 -07:00 committed by GitHub
commit 2f13422537
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
86 changed files with 1344 additions and 520 deletions

View File

@ -52,7 +52,7 @@ install:
} }
else else
{ {
c:\python34\python.exe c:\pillow\winbuild\build_dep.py c:\python37\python.exe c:\pillow\winbuild\build_dep.py
c:\pillow\winbuild\build_deps.cmd c:\pillow\winbuild\build_deps.cmd
$host.SetShouldExit(0) $host.SetShouldExit(0)
} }

View File

@ -9,7 +9,7 @@ Please send a pull request to the master branch. Please include [documentation](
- Fork the Pillow repository. - Fork the Pillow repository.
- Create a branch from master. - Create a branch from master.
- Develop bug fixes, features, tests, etc. - Develop bug fixes, features, tests, etc.
- Run the test suite on Python 2.7 and 3.x. You can enable [Travis CI](https://travis-ci.org/profile/) and [AppVeyor](https://ci.appveyor.com/projects/new) on your repo to catch test failures prior to the pull request, and [Coveralls](https://coveralls.io/repos/new) to see if the changed code is covered by tests. - Run the test suite on Python 2.7 and 3.x. You can enable [Travis CI](https://travis-ci.org/profile/) and [AppVeyor](https://ci.appveyor.com/projects/new) on your repo to catch test failures prior to the pull request, and [Codecov](https://codecov.io/gh) to see if the changed code is covered by tests.
- Create a pull request to pull the changes from your branch to the Pillow master. - Create a pull request to pull the changes from your branch to the Pillow master.
### Guidelines ### Guidelines

View File

@ -18,40 +18,25 @@ matrix:
env: LINT="true" env: LINT="true"
- python: "pypy2.7-6.0" - python: "pypy2.7-6.0"
name: "PyPy2 Xenial" name: "PyPy2 Xenial"
dist: xenial
- python: "pypy3.5-6.0" - python: "pypy3.5-6.0"
name: "PyPy3 Xenial" name: "PyPy3 Xenial"
dist: xenial
- python: '3.7' - python: '3.7'
name: "3.7 Xenial" name: "3.7 Xenial"
- python: '2.7' - python: '2.7'
name: "2.7 Xenial" name: "2.7 Xenial"
- python: '2.7'
name: "2.7 Trusty"
dist: trusty
- python: "2.7_with_system_site_packages" # For PyQt4 - python: "2.7_with_system_site_packages" # For PyQt4
name: "2.7_with_system_site_packages Xenial" name: "2.7_with_system_site_packages Xenial"
services: xvfb services: xvfb
- python: "2.7_with_system_site_packages" # For PyQt4
name: "2.7_with_system_site_packages Trusty"
dist: trusty
- python: '3.6' - python: '3.6'
name: "3.6 Xenial" name: "3.6 Xenial PYTHONOPTIMIZE=1"
- python: '3.6'
name: "3.6 Trusty PYTHONOPTIMIZE=1"
dist: trusty
env: PYTHONOPTIMIZE=1 env: PYTHONOPTIMIZE=1
- python: '3.5' - python: '3.5'
name: "3.5 Xenial" name: "3.5 Xenial PYTHONOPTIMIZE=2"
- python: '3.5'
name: "3.5 Trusty PYTHONOPTIMIZE=2"
dist: trusty
env: PYTHONOPTIMIZE=2 env: PYTHONOPTIMIZE=2
- python: "3.8-dev" - python: "3.8-dev"
name: "3.8-dev Xenial" name: "3.8-dev Xenial"
- env: DOCKER="alpine" DOCKER_TAG="master" - env: DOCKER="alpine" DOCKER_TAG="master"
- env: DOCKER="arch" DOCKER_TAG="master" # contains PyQt5 - env: DOCKER="arch" DOCKER_TAG="master" # contains PyQt5
- env: DOCKER="ubuntu-trusty-x86" DOCKER_TAG="master"
- env: DOCKER="ubuntu-xenial-amd64" DOCKER_TAG="master" - env: DOCKER="ubuntu-xenial-amd64" DOCKER_TAG="master"
- env: DOCKER="debian-stretch-x86" DOCKER_TAG="master" - env: DOCKER="debian-stretch-x86" DOCKER_TAG="master"
- env: DOCKER="centos-6-amd64" DOCKER_TAG="master" - env: DOCKER="centos-6-amd64" DOCKER_TAG="master"
@ -75,14 +60,6 @@ install:
.travis/install.sh; .travis/install.sh;
fi fi
before_script:
# Qt needs a display for some of the tests, and it's only run on the system site packages install
- |
if [ "$TRAVIS_JOB_NAME" == "2.7_with_system_site_packages Trusty" ]; then
export DISPLAY=:99.0
sh -e /etc/init.d/xvfb start
fi
script: script:
- | - |
if [ "$LINT" == "true" ]; then if [ "$LINT" == "true" ]; then

View File

@ -2,12 +2,72 @@
Changelog (Pillow) Changelog (Pillow)
================== ==================
6.0.0 (unreleased) 6.0.0 (2019-04-01)
------------------ ------------------
- Python 2.7 support will be removed in Pillow 7.0.0 #3682 - Python 2.7 support will be removed in Pillow 7.0.0 #3682
[hugovk] [hugovk]
- Add EXIF class #3625
[radarhere]
- Add ImageOps exif_transpose method #3687
[radarhere]
- Added warnings to deprecated CMSProfile attributes #3615
[hugovk]
- Documented reading TIFF multiframe images #3720
[akuchling]
- Improved speed of opening an MPO file #3658
[Glandos]
- Update palette in quantize #3721
[radarhere]
- Improvements to TIFF is_animated and n_frames #3714
[radarhere]
- Fixed incompatible pointer type warnings #3754
[radarhere]
- Improvements to PA and LA conversion and palette operations #3728
[radarhere]
- Consistent DPI rounding #3709
[radarhere]
- Change size of MPO image to match frame #3588
[radarhere]
- Read Photoshop resolution data #3701
[radarhere]
- Ensure image is mutable before saving #3724
[radarhere]
- Correct remap_palette documentation #3740
[radarhere]
- Promote P images to PA in putalpha #3726
[radarhere]
- Allow RGB and RGBA values for new P images #3719
[radarhere]
- Fixed TIFF bug when seeking backwards and then forwards #3713
[radarhere]
- Cache EXIF information #3498
[Glandos]
- Added transparency for all PNG greyscale modes #3744
[radarhere]
- Fix deprecation warnings in Python 3.8 #3749
[radarhere]
- Fixed GIF bug when rewinding to a non-zero frame #3716 - Fixed GIF bug when rewinding to a non-zero frame #3716
[radarhere] [radarhere]

View File

@ -20,6 +20,30 @@ Pillow is the friendly PIL fork by `Alex Clark and Contributors <https://github.
* - social * - social
- |gitter| |twitter| - |gitter| |twitter|
.. end-badges
More Information
----------------
- `Documentation <https://pillow.readthedocs.io/>`_
- `Installation <https://pillow.readthedocs.io/en/latest/installation.html>`_
- `Handbook <https://pillow.readthedocs.io/en/latest/handbook/index.html>`_
- `Contribute <https://github.com/python-pillow/Pillow/blob/master/.github/CONTRIBUTING.md>`_
- `Issues <https://github.com/python-pillow/Pillow/issues>`_
- `Pull requests <https://github.com/python-pillow/Pillow/pulls>`_
- `Changelog <https://github.com/python-pillow/Pillow/blob/master/CHANGES.rst>`_
- `Pre-fork <https://github.com/python-pillow/Pillow/blob/master/CHANGES.rst#pre-fork>`_
Report a Vulnerability
----------------------
To report a security vulnerability, please follow the procedure described in the `Tidelift security policy <https://tidelift.com/docs/security>`_.
.. |docs| image:: https://readthedocs.org/projects/pillow/badge/?version=latest .. |docs| image:: https://readthedocs.org/projects/pillow/badge/?version=latest
:target: https://pillow.readthedocs.io/?badge=latest :target: https://pillow.readthedocs.io/?badge=latest
:alt: Documentation Status :alt: Documentation Status
@ -36,8 +60,8 @@ Pillow is the friendly PIL fork by `Alex Clark and Contributors <https://github.
:target: https://ci.appveyor.com/project/python-pillow/Pillow :target: https://ci.appveyor.com/project/python-pillow/Pillow
:alt: AppVeyor CI build status (Windows) :alt: AppVeyor CI build status (Windows)
.. |coverage| image:: https://coveralls.io/repos/python-pillow/Pillow/badge.svg?branch=master&service=github .. |coverage| image:: https://codecov.io/gh/python-pillow/Pillow/branch/master/graph/badge.svg
:target: https://coveralls.io/github/python-pillow/Pillow?branch=master :target: https://codecov.io/gh/python-pillow/Pillow
:alt: Code coverage :alt: Code coverage
.. |zenodo| image:: https://zenodo.org/badge/17549/python-pillow/Pillow.svg .. |zenodo| image:: https://zenodo.org/badge/17549/python-pillow/Pillow.svg
@ -61,24 +85,3 @@ Pillow is the friendly PIL fork by `Alex Clark and Contributors <https://github.
.. |twitter| image:: https://img.shields.io/badge/tweet-on%20Twitter-00aced.svg .. |twitter| image:: https://img.shields.io/badge/tweet-on%20Twitter-00aced.svg
:target: https://twitter.com/PythonPillow :target: https://twitter.com/PythonPillow
:alt: Follow on https://twitter.com/PythonPillow :alt: Follow on https://twitter.com/PythonPillow
.. end-badges
More Information
----------------
- `Documentation <https://pillow.readthedocs.io/>`_
- `Installation <https://pillow.readthedocs.io/en/latest/installation.html>`_
- `Handbook <https://pillow.readthedocs.io/en/latest/handbook/index.html>`_
- `Contribute <https://github.com/python-pillow/Pillow/blob/master/.github/CONTRIBUTING.md>`_
- `Issues <https://github.com/python-pillow/Pillow/issues>`_
- `Pull requests <https://github.com/python-pillow/Pillow/pulls>`_
- `Changelog <https://github.com/python-pillow/Pillow/blob/master/CHANGES.rst>`_
- `Pre-fork <https://github.com/python-pillow/Pillow/blob/master/CHANGES.rst#pre-fork>`_

View File

@ -102,4 +102,4 @@ Released as needed privately to individual vendors for critical security-related
## Documentation ## Documentation
* [ ] Make sure the default version for Read the Docs is the latest tagged release e.g. `d2d43879` (5.4.0) * [ ] Make sure the [default version for Read the Docs](https://pillow.readthedocs.io/en/stable/) is up-to-date with the release changes

BIN
Tests/images/1_trns.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 612 B

Binary file not shown.

BIN
Tests/images/fujifilm.mpo Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.5 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 48 KiB

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

BIN
Tests/images/i_trns.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 117 KiB

View File

@ -71,6 +71,27 @@ class TestFileBmp(PillowTestCase):
self.assertEqual(im.size, reloaded.size) self.assertEqual(im.size, reloaded.size)
self.assertEqual(reloaded.format, "JPEG") self.assertEqual(reloaded.format, "JPEG")
def test_load_dpi_rounding(self):
# Round up
im = Image.open('Tests/images/hopper.bmp')
self.assertEqual(im.info["dpi"], (96, 96))
# Round down
im = Image.open('Tests/images/hopper_roundDown.bmp')
self.assertEqual(im.info["dpi"], (72, 72))
def test_save_dpi_rounding(self):
outfile = self.tempfile("temp.bmp")
im = Image.open('Tests/images/hopper.bmp')
im.save(outfile, dpi=(72.2, 72.2))
reloaded = Image.open(outfile)
self.assertEqual(reloaded.info["dpi"], (72, 72))
im.save(outfile, dpi=(72.8, 72.8))
reloaded = Image.open(outfile)
self.assertEqual(reloaded.info["dpi"], (73, 73))
def test_load_dib(self): def test_load_dib(self):
# test for #1293, Imagegrab returning Unsupported Bitfields Format # test for #1293, Imagegrab returning Unsupported Bitfields Format
im = Image.open('Tests/images/clipboard.dib') im = Image.open('Tests/images/clipboard.dib')

View File

@ -524,6 +524,27 @@ class TestFileJpeg(PillowTestCase):
reloaded.load() reloaded.load()
self.assertEqual(im.info['dpi'], reloaded.info['dpi']) self.assertEqual(im.info['dpi'], reloaded.info['dpi'])
def test_load_dpi_rounding(self):
# Round up
im = Image.open('Tests/images/iptc_roundUp.jpg')
self.assertEqual(im.info["dpi"], (44, 44))
# Round down
im = Image.open('Tests/images/iptc_roundDown.jpg')
self.assertEqual(im.info["dpi"], (2, 2))
def test_save_dpi_rounding(self):
outfile = self.tempfile("temp.jpg")
im = Image.open('Tests/images/hopper.jpg')
im.save(outfile, dpi=(72.2, 72.2))
reloaded = Image.open(outfile)
self.assertEqual(reloaded.info["dpi"], (72, 72))
im.save(outfile, dpi=(72.8, 72.8))
reloaded = Image.open(outfile)
self.assertEqual(reloaded.info["dpi"], (73, 73))
def test_dpi_tuple_from_exif(self): def test_dpi_tuple_from_exif(self):
# Arrange # Arrange
# This Photoshop CC 2017 image has DPI in EXIF not metadata # This Photoshop CC 2017 image has DPI in EXIF not metadata
@ -590,6 +611,15 @@ class TestFileJpeg(PillowTestCase):
# Act / Assert # Act / Assert
self.assertEqual(im._getexif()[306], '2017:03:13 23:03:09') self.assertEqual(im._getexif()[306], '2017:03:13 23:03:09')
def test_photoshop(self):
im = Image.open("Tests/images/photoshop-200dpi.jpg")
self.assertEqual(im.info["photoshop"][0x03ed], {
'XResolution': 200.0,
'DisplayedUnitsX': 1,
'YResolution': 200.0,
'DisplayedUnitsY': 1,
})
@unittest.skipUnless(sys.platform.startswith('win32'), "Windows only") @unittest.skipUnless(sys.platform.startswith('win32'), "Windows only")
class TestFileCloseW32(PillowTestCase): class TestFileCloseW32(PillowTestCase):

View File

@ -55,6 +55,27 @@ class TestFileMpo(PillowTestCase):
self.assertEqual(info[296], 2) self.assertEqual(info[296], 2)
self.assertEqual(info[34665], 188) self.assertEqual(info[34665], 188)
def test_frame_size(self):
# This image has been hexedited to contain a different size
# in the EXIF data of the second frame
im = Image.open("Tests/images/sugarshack_frame_size.mpo")
self.assertEqual(im.size, (640, 480))
im.seek(1)
self.assertEqual(im.size, (680, 480))
def test_parallax(self):
# Nintendo
im = Image.open("Tests/images/sugarshack.mpo")
exif = im.getexif()
self.assertEqual(exif.get_ifd(0x927c)[0x1101]["Parallax"], -44.798187255859375)
# Fujifilm
im = Image.open("Tests/images/fujifilm.mpo")
im.seek(1)
exif = im.getexif()
self.assertEqual(exif.get_ifd(0x927c)[0xb211], -3.125)
def test_mp(self): def test_mp(self):
for test_file in test_files: for test_file in test_files:
im = Image.open(test_file) im = Image.open(test_file)

View File

@ -291,13 +291,15 @@ class TestFilePng(PillowTestCase):
self.assert_image(im, "RGBA", (10, 10)) self.assert_image(im, "RGBA", (10, 10))
self.assertEqual(im.getcolors(), [(100, (0, 0, 0, 0))]) self.assertEqual(im.getcolors(), [(100, (0, 0, 0, 0))])
def test_save_l_transparency(self): def test_save_greyscale_transparency(self):
# There are 559 transparent pixels in l_trns.png. for mode, num_transparent in {
num_transparent = 559 "1": 1994,
"L": 559,
in_file = "Tests/images/l_trns.png" "I": 559,
}.items():
in_file = "Tests/images/"+mode.lower()+"_trns.png"
im = Image.open(in_file) im = Image.open(in_file)
self.assertEqual(im.mode, "L") self.assertEqual(im.mode, mode)
self.assertEqual(im.info["transparency"], 255) self.assertEqual(im.info["transparency"], 255)
im_rgba = im.convert('RGBA') im_rgba = im.convert('RGBA')
@ -308,7 +310,7 @@ class TestFilePng(PillowTestCase):
im.save(test_file) im.save(test_file)
test_im = Image.open(test_file) test_im = Image.open(test_file)
self.assertEqual(test_im.mode, "L") self.assertEqual(test_im.mode, mode)
self.assertEqual(test_im.info["transparency"], 255) self.assertEqual(test_im.info["transparency"], 255)
self.assert_image_equal(im, test_im) self.assert_image_equal(im, test_im)
@ -387,6 +389,24 @@ class TestFilePng(PillowTestCase):
im = roundtrip(im, dpi=(100, 100)) im = roundtrip(im, dpi=(100, 100))
self.assertEqual(im.info["dpi"], (100, 100)) self.assertEqual(im.info["dpi"], (100, 100))
def test_load_dpi_rounding(self):
# Round up
im = Image.open(TEST_PNG_FILE)
self.assertEqual(im.info["dpi"], (96, 96))
# Round down
im = Image.open("Tests/images/icc_profile_none.png")
self.assertEqual(im.info["dpi"], (72, 72))
def test_save_dpi_rounding(self):
im = Image.open(TEST_PNG_FILE)
im = roundtrip(im, dpi=(72.2, 72.2))
self.assertEqual(im.info["dpi"], (72, 72))
im = roundtrip(im, dpi=(72.8, 72.8))
self.assertEqual(im.info["dpi"], (73, 73))
def test_roundtrip_text(self): def test_roundtrip_text(self):
# Check text roundtripping # Check text roundtripping

View File

@ -126,6 +126,30 @@ class TestFileTiff(PillowTestCase):
im._setup() im._setup()
self.assertEqual(im.info['dpi'], (71., 71.)) self.assertEqual(im.info['dpi'], (71., 71.))
def test_load_dpi_rounding(self):
for resolutionUnit, dpi in ((None, (72, 73)),
(2, (72, 73)),
(3, (183, 185))):
im = Image.open(
"Tests/images/hopper_roundDown_"+str(resolutionUnit)+".tif")
self.assertEqual(im.tag_v2.get(RESOLUTION_UNIT), resolutionUnit)
self.assertEqual(im.info['dpi'], (dpi[0], dpi[0]))
im = Image.open("Tests/images/hopper_roundUp_"+str(resolutionUnit)+".tif")
self.assertEqual(im.tag_v2.get(RESOLUTION_UNIT), resolutionUnit)
self.assertEqual(im.info['dpi'], (dpi[1], dpi[1]))
def test_save_dpi_rounding(self):
outfile = self.tempfile("temp.tif")
im = Image.open("Tests/images/hopper.tif")
for dpi in (72.2, 72.8):
im.save(outfile, dpi=(dpi, dpi))
reloaded = Image.open(outfile)
reloaded.load()
self.assertEqual((round(dpi), round(dpi)), reloaded.info['dpi'])
def test_save_setting_missing_resolution(self): def test_save_setting_missing_resolution(self):
b = BytesIO() b = BytesIO()
Image.open("Tests/images/10ct_32bit_128.tiff").save( Image.open("Tests/images/10ct_32bit_128.tiff").save(
@ -229,11 +253,6 @@ class TestFileTiff(PillowTestCase):
['Tests/images/multipage-lastframe.tif', 1], ['Tests/images/multipage-lastframe.tif', 1],
['Tests/images/multipage.tiff', 3] ['Tests/images/multipage.tiff', 3]
]: ]:
# Test is_animated before n_frames
im = Image.open(path)
self.assertEqual(im.is_animated, n_frames != 1)
# Test is_animated after n_frames
im = Image.open(path) im = Image.open(path)
self.assertEqual(im.n_frames, n_frames) self.assertEqual(im.n_frames, n_frames)
self.assertEqual(im.is_animated, n_frames != 1) self.assertEqual(im.is_animated, n_frames != 1)
@ -263,6 +282,11 @@ class TestFileTiff(PillowTestCase):
self.assertEqual(im.size, (10, 10)) self.assertEqual(im.size, (10, 10))
self.assertEqual(im.convert('RGB').getpixel((0, 0)), (255, 0, 0)) self.assertEqual(im.convert('RGB').getpixel((0, 0)), (255, 0, 0))
im.seek(0)
im.load()
self.assertEqual(im.size, (10, 10))
self.assertEqual(im.convert('RGB').getpixel((0, 0)), (0, 128, 0))
im.seek(2) im.seek(2)
im.load() im.load()
self.assertEqual(im.size, (20, 20)) self.assertEqual(im.size, (20, 20))

View File

@ -45,6 +45,15 @@ class TestFileWmf(PillowTestCase):
# Restore the state before this test # Restore the state before this test
WmfImagePlugin.register_handler(None) WmfImagePlugin.register_handler(None)
def test_load_dpi_rounding(self):
# Round up
im = Image.open('Tests/images/drawing.emf')
self.assertEqual(im.info["dpi"], 1424)
# Round down
im = Image.open('Tests/images/drawing_roundDown.emf')
self.assertEqual(im.info["dpi"], 1426)
def test_save(self): def test_save(self):
im = hopper() im = hopper()

View File

@ -3,6 +3,8 @@ from .helper import unittest, PillowTestCase, hopper
from PIL import Image from PIL import Image
from PIL._util import py3 from PIL._util import py3
import os import os
import sys
import shutil
class TestImage(PillowTestCase): class TestImage(PillowTestCase):
@ -121,6 +123,16 @@ class TestImage(PillowTestCase):
im.paste(0, (0, 0, 100, 100)) im.paste(0, (0, 0, 100, 100))
self.assertFalse(im.readonly) self.assertFalse(im.readonly)
@unittest.skipIf(sys.platform.startswith('win32'),
"Test requires opening tempfile twice")
def test_readonly_save(self):
temp_file = self.tempfile("temp.bmp")
shutil.copy("Tests/images/rgb32bf-rgba.bmp", temp_file)
im = Image.open(temp_file)
self.assertTrue(im.readonly)
im.save(temp_file)
def test_dump(self): def test_dump(self):
im = Image.new("L", (10, 10)) im = Image.new("L", (10, 10))
im._dump(self.tempfile("temp_L.ppm")) im._dump(self.tempfile("temp_L.ppm"))
@ -522,6 +534,16 @@ class TestImage(PillowTestCase):
_make_new(im, blank_p, ImagePalette.ImagePalette()) _make_new(im, blank_p, ImagePalette.ImagePalette())
_make_new(im, blank_pa, ImagePalette.ImagePalette()) _make_new(im, blank_pa, ImagePalette.ImagePalette())
def test_p_from_rgb_rgba(self):
for mode, color in [
("RGB", '#DDEEFF'),
("RGB", (221, 238, 255)),
("RGBA", (221, 238, 255, 255))
]:
im = Image.new("P", (100, 100), color)
expected = Image.new(mode, (100, 100), color)
self.assert_image_equal(im.convert(mode), expected)
def test_no_resource_warning_on_save(self): def test_no_resource_warning_on_save(self):
# https://github.com/python-pillow/Pillow/issues/835 # https://github.com/python-pillow/Pillow/issues/835
# Arrange # Arrange

View File

@ -12,7 +12,8 @@ class TestImageConvert(PillowTestCase):
self.assertEqual(out.mode, mode) self.assertEqual(out.mode, mode)
self.assertEqual(out.size, im.size) self.assertEqual(out.size, im.size)
modes = "1", "L", "I", "F", "RGB", "RGBA", "RGBX", "CMYK", "YCbCr" modes = ("1", "L", "LA", "P", "PA", "I", "F",
"RGB", "RGBA", "RGBX", "CMYK", "YCbCr")
for mode in modes: for mode in modes:
im = hopper(mode) im = hopper(mode)

View File

@ -42,7 +42,7 @@ class TestImageMode(PillowTestCase):
self.assertEqual(signature, result) self.assertEqual(signature, result)
check("1", "L", "L", 1, ("1",)) check("1", "L", "L", 1, ("1",))
check("L", "L", "L", 1, ("L",)) check("L", "L", "L", 1, ("L",))
check("P", "RGB", "L", 1, ("P",)) check("P", "P", "L", 1, ("P",))
check("I", "L", "I", 1, ("I",)) check("I", "L", "I", 1, ("I",))
check("F", "L", "F", 1, ("F",)) check("F", "L", "F", 1, ("F",))
check("RGB", "RGB", "L", 3, ("R", "G", "B")) check("RGB", "RGB", "L", 3, ("R", "G", "B"))

View File

@ -28,6 +28,13 @@ class TestImagePutAlpha(PillowTestCase):
self.assertEqual(im.mode, 'LA') self.assertEqual(im.mode, 'LA')
self.assertEqual(im.getpixel((0, 0)), (1, 2)) self.assertEqual(im.getpixel((0, 0)), (1, 2))
im = Image.new("P", (1, 1), 1)
self.assertEqual(im.getpixel((0, 0)), 1)
im.putalpha(2)
self.assertEqual(im.mode, 'PA')
self.assertEqual(im.getpixel((0, 0)), (1, 2))
im = Image.new("RGB", (1, 1), (1, 2, 3)) im = Image.new("RGB", (1, 1), (1, 2, 3))
self.assertEqual(im.getpixel((0, 0)), (1, 2, 3)) self.assertEqual(im.getpixel((0, 0)), (1, 2, 3))

View File

@ -14,8 +14,10 @@ class TestImagePutPalette(PillowTestCase):
return im.mode, p[:10] return im.mode, p[:10]
return im.mode return im.mode
self.assertRaises(ValueError, palette, "1") self.assertRaises(ValueError, palette, "1")
self.assertEqual(palette("L"), ("P", [0, 1, 2, 3, 4, 5, 6, 7, 8, 9])) for mode in ["L", "LA", "P", "PA"]:
self.assertEqual(palette("P"), ("P", [0, 1, 2, 3, 4, 5, 6, 7, 8, 9])) self.assertEqual(palette(mode),
("PA" if "A" in mode else "P",
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]))
self.assertRaises(ValueError, palette, "I") self.assertRaises(ValueError, palette, "I")
self.assertRaises(ValueError, palette, "F") self.assertRaises(ValueError, palette, "F")
self.assertRaises(ValueError, palette, "RGB") self.assertRaises(ValueError, palette, "RGB")

View File

@ -38,9 +38,10 @@ class TestImageQuantize(PillowTestCase):
def test_rgba_quantize(self): def test_rgba_quantize(self):
image = hopper('RGBA') image = hopper('RGBA')
image.quantize()
self.assertRaises(ValueError, image.quantize, method=0) self.assertRaises(ValueError, image.quantize, method=0)
self.assertEqual(image.quantize().convert().mode, "RGBA")
def test_quantize(self): def test_quantize(self):
image = Image.open('Tests/images/caption_6_33_22.png').convert('RGB') image = Image.open('Tests/images/caption_6_33_22.png').convert('RGB')
converted = image.quantize() converted = image.quantize()

View File

@ -309,7 +309,7 @@ class TestImageCms(PillowTestCase):
2: (False, False, True), 2: (False, False, True),
3: (False, False, True) 3: (False, False, True)
}) })
self.assertEqual(p.color_space, 'RGB')
self.assertIsNone(p.colorant_table) self.assertIsNone(p.colorant_table)
self.assertIsNone(p.colorant_table_out) self.assertIsNone(p.colorant_table_out)
self.assertIsNone(p.colorimetric_intent) self.assertIsNone(p.colorimetric_intent)
@ -361,16 +361,9 @@ class TestImageCms(PillowTestCase):
(5000.722328847392,)) (5000.722328847392,))
self.assertEqual(p.model, self.assertEqual(p.model,
'IEC 61966-2-1 Default RGB Colour Space - sRGB') 'IEC 61966-2-1 Default RGB Colour Space - sRGB')
self.assertEqual(p.pcs, 'XYZ')
self.assertIsNone(p.perceptual_rendering_intent_gamut) self.assertIsNone(p.perceptual_rendering_intent_gamut)
self.assertEqual(p.product_copyright,
'Copyright International Color Consortium, 2009')
self.assertEqual(p.product_desc, 'sRGB IEC61966-2-1 black scaled')
self.assertEqual(p.product_description,
'sRGB IEC61966-2-1 black scaled')
self.assertEqual(p.product_manufacturer, '')
self.assertEqual(
p.product_model, 'IEC 61966-2-1 Default RGB Colour Space - sRGB')
self.assertEqual( self.assertEqual(
p.profile_description, 'sRGB IEC61966-2-1 black scaled') p.profile_description, 'sRGB IEC61966-2-1 black scaled')
self.assertEqual( self.assertEqual(
@ -393,6 +386,40 @@ class TestImageCms(PillowTestCase):
'Reference Viewing Condition in IEC 61966-2-1') 'Reference Viewing Condition in IEC 61966-2-1')
self.assertEqual(p.xcolor_space, 'RGB ') self.assertEqual(p.xcolor_space, 'RGB ')
def test_deprecations(self):
self.skip_missing()
o = ImageCms.getOpenProfile(SRGB)
p = o.profile
def helper_deprecated(attr, expected):
result = self.assert_warning(DeprecationWarning, getattr, p, attr)
self.assertEqual(result, expected)
# p.color_space
helper_deprecated("color_space", "RGB")
# p.pcs
helper_deprecated("pcs", "XYZ")
# p.product_copyright
helper_deprecated(
"product_copyright", "Copyright International Color Consortium, 2009"
)
# p.product_desc
helper_deprecated("product_desc", "sRGB IEC61966-2-1 black scaled")
# p.product_description
helper_deprecated("product_description", "sRGB IEC61966-2-1 black scaled")
# p.product_manufacturer
helper_deprecated("product_manufacturer", "")
# p.product_model
helper_deprecated(
"product_model", "IEC 61966-2-1 Default RGB Colour Space - sRGB"
)
def test_profile_typesafety(self): def test_profile_typesafety(self):
""" Profile init type safety """ Profile init type safety

View File

@ -1,4 +1,4 @@
from .helper import PillowTestCase, hopper, fromstring, tostring from .helper import unittest, PillowTestCase, hopper, fromstring, tostring
from io import BytesIO from io import BytesIO
@ -6,6 +6,12 @@ from PIL import Image
from PIL import ImageFile from PIL import ImageFile
from PIL import EpsImagePlugin from PIL import EpsImagePlugin
try:
from PIL import _webp
HAVE_WEBP = True
except ImportError:
HAVE_WEBP = False
codecs = dir(Image.core) codecs = dir(Image.core)
@ -233,3 +239,97 @@ class TestPyDecoder(PillowTestCase):
im = MockImageFile(buf) im = MockImageFile(buf)
self.assertIsNone(im.format) self.assertIsNone(im.format)
self.assertIsNone(im.get_format_mimetype()) self.assertIsNone(im.get_format_mimetype())
def test_exif_jpeg(self):
im = Image.open("Tests/images/exif-72dpi-int.jpg") # Little endian
exif = im.getexif()
self.assertNotIn(258, exif)
self.assertIn(40960, exif)
self.assertEqual(exif[40963], 450)
self.assertEqual(exif[11], "gThumb 3.0.1")
out = self.tempfile('temp.jpg')
exif[258] = 8
del exif[40960]
exif[40963] = 455
exif[11] = "Pillow test"
im.save(out, exif=exif)
reloaded = Image.open(out)
reloaded_exif = reloaded.getexif()
self.assertEqual(reloaded_exif[258], 8)
self.assertNotIn(40960, exif)
self.assertEqual(reloaded_exif[40963], 455)
self.assertEqual(exif[11], "Pillow test")
im = Image.open("Tests/images/no-dpi-in-exif.jpg") # Big endian
exif = im.getexif()
self.assertNotIn(258, exif)
self.assertIn(40962, exif)
self.assertEqual(exif[40963], 200)
self.assertEqual(exif[305], "Adobe Photoshop CC 2017 (Macintosh)")
out = self.tempfile('temp.jpg')
exif[258] = 8
del exif[34665]
exif[40963] = 455
exif[305] = "Pillow test"
im.save(out, exif=exif)
reloaded = Image.open(out)
reloaded_exif = reloaded.getexif()
self.assertEqual(reloaded_exif[258], 8)
self.assertNotIn(40960, exif)
self.assertEqual(reloaded_exif[40963], 455)
self.assertEqual(exif[305], "Pillow test")
@unittest.skipIf(not HAVE_WEBP or not _webp.HAVE_WEBPANIM,
"WebP support not installed with animation")
def test_exif_webp(self):
im = Image.open("Tests/images/hopper.webp")
exif = im.getexif()
self.assertEqual(exif, {})
out = self.tempfile('temp.webp')
exif[258] = 8
exif[40963] = 455
exif[305] = "Pillow test"
def check_exif():
reloaded = Image.open(out)
reloaded_exif = reloaded.getexif()
self.assertEqual(reloaded_exif[258], 8)
self.assertEqual(reloaded_exif[40963], 455)
self.assertEqual(exif[305], "Pillow test")
im.save(out, exif=exif)
check_exif()
im.save(out, exif=exif, save_all=True)
check_exif()
def test_exif_png(self):
im = Image.open("Tests/images/exif.png")
exif = im.getexif()
self.assertEqual(exif, {274: 1})
out = self.tempfile('temp.png')
exif[258] = 8
del exif[274]
exif[40963] = 455
exif[305] = "Pillow test"
im.save(out, exif=exif)
reloaded = Image.open(out)
reloaded_exif = reloaded.getexif()
self.assertEqual(reloaded_exif, {
258: 8,
40963: 455,
305: 'Pillow test',
})
def test_exif_interop(self):
im = Image.open("Tests/images/flower.jpg")
exif = im.getexif()
self.assertEqual(exif.get_ifd(0xa005), {
1: 'R98',
2: b'0100',
4097: 2272,
4098: 1704,
})

View File

@ -1,7 +1,13 @@
from .helper import PillowTestCase, hopper from .helper import PillowTestCase, hopper
from PIL import ImageOps
from PIL import Image from PIL import Image
from PIL import ImageOps
try:
from PIL import _webp
HAVE_WEBP = True
except ImportError:
HAVE_WEBP = False
class TestImageOps(PillowTestCase): class TestImageOps(PillowTestCase):
@ -62,6 +68,9 @@ class TestImageOps(PillowTestCase):
ImageOps.solarize(hopper("L")) ImageOps.solarize(hopper("L"))
ImageOps.solarize(hopper("RGB")) ImageOps.solarize(hopper("RGB"))
ImageOps.exif_transpose(hopper("L"))
ImageOps.exif_transpose(hopper("RGB"))
def test_1pxfit(self): def test_1pxfit(self):
# Division by zero in equalize if image is 1 pixel high # Division by zero in equalize if image is 1 pixel high
newimg = ImageOps.fit(hopper("RGB").resize((1, 1)), (35, 35)) newimg = ImageOps.fit(hopper("RGB").resize((1, 1)), (35, 35))
@ -218,3 +227,36 @@ class TestImageOps(PillowTestCase):
(0, 127, 0), (0, 127, 0),
threshold=1, threshold=1,
msg='white test pixel incorrect') msg='white test pixel incorrect')
def test_exif_transpose(self):
exts = [".jpg"]
if HAVE_WEBP and _webp.HAVE_WEBPANIM:
exts.append(".webp")
for ext in exts:
base_im = Image.open("Tests/images/hopper"+ext)
orientations = [base_im]
for i in range(2, 9):
im = Image.open("Tests/images/hopper_orientation_"+str(i)+ext)
orientations.append(im)
for i, orientation_im in enumerate(orientations):
for im in [
orientation_im, # ImageFile
orientation_im.copy() # Image
]:
if i == 0:
self.assertNotIn("exif", im.info)
else:
original_exif = im.info["exif"]
transposed_im = ImageOps.exif_transpose(im)
self.assert_image_similar(base_im, transposed_im, 17)
if i == 0:
self.assertNotIn("exif", im.info)
else:
self.assertNotEqual(transposed_im.info["exif"], original_exif)
self.assertNotIn(0x0112, transposed_im.getexif())
# Repeat the operation, to test that it does not keep transposing
transposed_im2 = ImageOps.exif_transpose(transposed_im)
self.assert_image_equal(transposed_im2, transposed_im)

View File

@ -113,11 +113,6 @@ class TestNumpy(PillowTestCase):
img = Image.fromarray(arr * 255).convert('1') img = Image.fromarray(arr * 255).convert('1')
self.assertEqual(img.mode, '1') self.assertEqual(img.mode, '1')
arr_back = numpy.array(img) arr_back = numpy.array(img)
# numpy 1.8 and earlier return this as a boolean. (trusty/precise)
if arr_back.dtype == numpy.bool:
arr_bool = numpy.array([[1, 0, 0, 1, 0], [0, 1, 0, 0, 0]], numpy.bool)
numpy.testing.assert_array_equal(arr_bool, arr_back)
else:
numpy.testing.assert_array_equal(arr, arr_back) numpy.testing.assert_array_equal(arr, arr_back)
def test_save_tiff_uint16(self): def test_save_tiff_uint16(self):

View File

@ -10,7 +10,6 @@ except ImportError:
@unittest.skipIf(pyroma is None, "Pyroma is not installed") @unittest.skipIf(pyroma is None, "Pyroma is not installed")
class TestPyroma(PillowTestCase): class TestPyroma(PillowTestCase):
def test_pyroma(self): def test_pyroma(self):
# Arrange # Arrange
data = pyroma.projectdata.get_data(".") data = pyroma.projectdata.get_data(".")
@ -19,12 +18,13 @@ class TestPyroma(PillowTestCase):
rating = pyroma.ratings.rate(data) rating = pyroma.ratings.rate(data)
# Assert # Assert
if 'rc' in __version__: if "rc" in __version__:
# Pyroma needs to chill about RC versions # Pyroma needs to chill about RC versions and not kill all our tests.
# and not kill all our tests. self.assertEqual(
self.assertEqual(rating, (9, [ rating,
"The package's version number does not comply with PEP-386."])) (9, ["The package's version number does not comply with PEP-386."]),
)
else: else:
# Should have a perfect score # Should have a near-perfect score
self.assertEqual(rating, (10, [])) self.assertEqual(rating, (9, ["Your package does not have license data."]))

View File

@ -11,17 +11,6 @@ class TestFromQPixmap(PillowQPixmapTestCase, PillowTestCase):
# Qt saves all pixmaps as rgb # Qt saves all pixmaps as rgb
self.assert_image_equal(result, expected.convert('RGB')) self.assert_image_equal(result, expected.convert('RGB'))
def test_sanity_1(self): def test_sanity(self):
self.roundtrip(hopper('1')) for mode in ('1', 'RGB', 'RGBA', 'L', 'P'):
self.roundtrip(hopper(mode))
def test_sanity_rgb(self):
self.roundtrip(hopper('RGB'))
def test_sanity_rgba(self):
self.roundtrip(hopper('RGBA'))
def test_sanity_l(self):
self.roundtrip(hopper('L'))
def test_sanity_p(self):
self.roundtrip(hopper('P'))

View File

@ -21,11 +21,6 @@ jobs:
docker: 'arch' docker: 'arch'
name: 'arch' name: 'arch'
- template: .azure-pipelines/jobs/test-docker.yml
parameters:
docker: 'ubuntu-trusty-x86'
name: 'ubuntu_trusty_x86'
- template: .azure-pipelines/jobs/test-docker.yml - template: .azure-pipelines/jobs/test-docker.yml
parameters: parameters:
docker: 'ubuntu-xenial-amd64' docker: 'ubuntu-xenial-amd64'

View File

@ -79,6 +79,26 @@ PILLOW_VERSION constant
``PILLOW_VERSION`` has been deprecated and will be removed in the next ``PILLOW_VERSION`` has been deprecated and will be removed in the next
major release. Use ``__version__`` instead. major release. Use ``__version__`` instead.
ImageCms.CmsProfile attributes
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
.. deprecated:: 3.2.0
Some attributes in ``ImageCms.CmsProfile`` are deprecated. From 6.0.0, they issue a
``DeprecationWarning``:
======================== ===============================
Deprecated Use instead
======================== ===============================
``color_space`` Padded ``xcolor_space``
``pcs`` Padded ``connection_space``
``product_copyright`` Unicode ``copyright``
``product_desc`` Unicode ``profile_description``
``product_description`` Unicode ``profile_description``
``product_manufacturer`` Unicode ``manufacturer``
``product_model`` Unicode ``model``
======================== ===============================
Removed features Removed features
---------------- ----------------

View File

@ -104,11 +104,11 @@ The :py:meth:`~PIL.Image.Image.open` method sets the following
Reading sequences Reading sequences
~~~~~~~~~~~~~~~~~ ~~~~~~~~~~~~~~~~~
The GIF loader supports the :py:meth:`~file.seek` and :py:meth:`~file.tell` The GIF loader supports the :py:meth:`~PIL.Image.Image.seek` and
methods. You can combine these methods to seek to the next frame :py:meth:`~PIL.Image.Image.tell` methods. You can combine these methods
(``im.seek(im.tell() + 1)``). to seek to the next frame (``im.seek(im.tell() + 1)``).
``im.seek()`` raises an ``EOFError`` if you try to seek after the last frame. ``im.seek()`` raises an :py:exc:`EOFError` if you try to seek after the last frame.
Saving Saving
~~~~~~ ~~~~~~
@ -464,8 +464,9 @@ Pillow identifies, reads, and writes PNG files containing ``1``, ``L``, ``LA``,
v1.1.7. v1.1.7.
As of Pillow 6.0, EXIF data can be read from PNG images. However, unlike other As of Pillow 6.0, EXIF data can be read from PNG images. However, unlike other
image formats, EXIF data is not guaranteed to have been read until image formats, EXIF data is not guaranteed to be present in
:py:meth:`~PIL.Image.Image.load` has been called. :py:attr:`~PIL.Image.Image.info` until :py:meth:`~PIL.Image.Image.load` has been
called.
The :py:meth:`~PIL.Image.Image.open` method sets the following The :py:meth:`~PIL.Image.Image.open` method sets the following
:py:attr:`~PIL.Image.Image.info` properties, when appropriate: :py:attr:`~PIL.Image.Image.info` properties, when appropriate:
@ -490,12 +491,12 @@ The :py:meth:`~PIL.Image.Image.open` method sets the following
For ``P`` images: Either the palette index for full transparent pixels, For ``P`` images: Either the palette index for full transparent pixels,
or a byte string with alpha values for each palette entry. or a byte string with alpha values for each palette entry.
For ``L`` and ``RGB`` images, the color that represents full transparent For ``1``, ``L``, ``I`` and ``RGB`` images, the color that represents
pixels in this image. full transparent pixels in this image.
This key is omitted if the image is not a transparent palette image. This key is omitted if the image is not a transparent palette image.
``Open`` also sets ``Image.text`` to a dictionary of the values of the ``open`` also sets ``Image.text`` to a dictionary of the values of the
``tEXt``, ``zTXt``, and ``iTXt`` chunks of the PNG image. Individual ``tEXt``, ``zTXt``, and ``iTXt`` chunks of the PNG image. Individual
compressed chunks are limited to a decompressed size of compressed chunks are limited to a decompressed size of
``PngImagePlugin.MAX_TEXT_CHUNK``, by default 1MB, to prevent ``PngImagePlugin.MAX_TEXT_CHUNK``, by default 1MB, to prevent
@ -511,8 +512,8 @@ The :py:meth:`~PIL.Image.Image.save` method supports the following options:
encoder settings. encoder settings.
**transparency** **transparency**
For ``P``, ``L``, and ``RGB`` images, this option controls what For ``P``, ``1``, ``L``, ``I``, and ``RGB`` images, this option controls
color image to mark as transparent. what color from the image to mark as transparent.
For ``P`` images, this can be a either the palette index, For ``P`` images, this can be a either the palette index,
or a byte string with alpha values for each palette entry. or a byte string with alpha values for each palette entry.
@ -569,7 +570,7 @@ Pillow reads and writes SPIDER image files of 32-bit floating point data
("F;32F"). ("F;32F").
Pillow also reads SPIDER stack files containing sequences of SPIDER images. The Pillow also reads SPIDER stack files containing sequences of SPIDER images. The
:py:meth:`~file.seek` and :py:meth:`~file.tell` methods are supported, and :py:meth:`~PIL.Image.Image.seek` and :py:meth:`~PIL.Image.Image.tell` methods are supported, and
random access is allowed. random access is allowed.
The :py:meth:`~PIL.Image.Image.open` method sets the following attributes: The :py:meth:`~PIL.Image.Image.open` method sets the following attributes:
@ -580,7 +581,7 @@ The :py:meth:`~PIL.Image.Image.open` method sets the following attributes:
**istack** **istack**
Set to 1 if the file is an image stack, else 0. Set to 1 if the file is an image stack, else 0.
**nimages** **n_frames**
Set to the number of images in the stack. Set to the number of images in the stack.
A convenience method, :py:meth:`~PIL.Image.Image.convert2byte`, is provided for A convenience method, :py:meth:`~PIL.Image.Image.convert2byte`, is provided for
@ -664,6 +665,17 @@ numbers are returned as a tuple of ``(numerator, denominator)``.
.. deprecated:: 3.0.0 .. deprecated:: 3.0.0
Reading Multi-frame TIFF Images
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
The TIFF loader supports the :py:meth:`~PIL.Image.Image.seek` and
:py:meth:`~PIL.Image.Image.tell` methods, taking and returning frame numbers
within the image file. You can combine these methods to seek to the next frame
(``im.seek(im.tell() + 1)``). Frames are numbered from 0 to ``im.num_frames - 1``,
and can be accessed in any order.
``im.seek()`` raises an :py:exc:`EOFError` if you try to seek after the
last frame.
Saving Tiff Images Saving Tiff Images
~~~~~~~~~~~~~~~~~~ ~~~~~~~~~~~~~~~~~~
@ -851,7 +863,7 @@ is commonly used in fax applications. The DCX decoder can read files containing
``1``, ``L``, ``P``, or ``RGB`` data. ``1``, ``L``, ``P``, or ``RGB`` data.
When the file is opened, only the first image is read. You can use When the file is opened, only the first image is read. You can use
:py:meth:`~file.seek` or :py:mod:`~PIL.ImageSequence` to read other images. :py:meth:`~PIL.Image.Image.seek` or :py:mod:`~PIL.ImageSequence` to read other images.
DDS DDS
@ -943,8 +955,8 @@ MIC
^^^ ^^^
Pillow identifies and reads Microsoft Image Composer (MIC) files. When opened, Pillow identifies and reads Microsoft Image Composer (MIC) files. When opened,
the first sprite in the file is loaded. You can use :py:meth:`~file.seek` and the first sprite in the file is loaded. You can use :py:meth:`~PIL.Image.Image.seek` and
:py:meth:`~file.tell` to read other sprites from the file. :py:meth:`~PIL.Image.Image.tell` to read other sprites from the file.
Note that there may be an embedded gamma of 2.2 in MIC files. Note that there may be an embedded gamma of 2.2 in MIC files.
@ -952,7 +964,7 @@ MPO
^^^ ^^^
Pillow identifies and reads Multi Picture Object (MPO) files, loading the primary Pillow identifies and reads Multi Picture Object (MPO) files, loading the primary
image when first opened. The :py:meth:`~file.seek` and :py:meth:`~file.tell` image when first opened. The :py:meth:`~PIL.Image.Image.seek` and :py:meth:`~PIL.Image.Image.tell`
methods may be used to read other pictures from the file. The pictures are methods may be used to read other pictures from the file. The pictures are
zero-indexed and random access is supported. zero-indexed and random access is supported.

View File

@ -30,8 +30,8 @@ Pillow is the friendly PIL fork by `Alex Clark and Contributors <https://github.
:target: https://pypi.org/project/Pillow/ :target: https://pypi.org/project/Pillow/
:alt: Number of PyPI downloads :alt: Number of PyPI downloads
.. image:: https://coveralls.io/repos/python-pillow/Pillow/badge.svg?branch=master&service=github .. image:: https://codecov.io/gh/python-pillow/Pillow/branch/master/graph/badge.svg
:target: https://coveralls.io/github/python-pillow/Pillow?branch=master :target: https://codecov.io/gh/python-pillow/Pillow
:alt: Code coverage :alt: Code coverage
.. toctree:: .. toctree::

View File

@ -151,7 +151,7 @@ Many of Pillow's features require external libraries:
* **littlecms** provides color management * **littlecms** provides color management
* Pillow version 2.2.1 and below uses liblcms1, Pillow 2.3.0 and * Pillow version 2.2.1 and below uses liblcms1, Pillow 2.3.0 and
above uses liblcms2. Tested with **1.19** and **2.7**. above uses liblcms2. Tested with **1.19** and **2.7-2.9**.
* **libwebp** provides the WebP format. * **libwebp** provides the WebP format.
@ -165,7 +165,7 @@ Many of Pillow's features require external libraries:
* Pillow has been tested with openjpeg **2.0.0** and **2.1.0**. * Pillow has been tested with openjpeg **2.0.0** and **2.1.0**.
* Pillow does **not** support the earlier **1.5** series which ships * Pillow does **not** support the earlier **1.5** series which ships
with Ubuntu <= 14.04 and Debian Jessie. with Debian Jessie.
* **libimagequant** provides improved color quantization * **libimagequant** provides improved color quantization
@ -403,10 +403,6 @@ These platforms are built and tested for every change.
| Ubuntu Linux 16.04 LTS | 2.7, 3.5, 3.6, 3.7, |x86-64 | | Ubuntu Linux 16.04 LTS | 2.7, 3.5, 3.6, 3.7, |x86-64 |
| | PyPy, PyPy3 | | | | PyPy, PyPy3 | |
+----------------------------------+-------------------------------+-----------------------+ +----------------------------------+-------------------------------+-----------------------+
| Ubuntu Linux 14.04 LTS | 2.7, 3.5, 3.6 |x86-64 |
| +-------------------------------+-----------------------+
| | 2.7 |x86 |
+----------------------------------+-------------------------------+-----------------------+
| Windows Server 2012 R2 | 2.7, 3.5, 3.6, 3.7 |x86, x86-64 | | Windows Server 2012 R2 | 2.7, 3.5, 3.6, 3.7 |x86, x86-64 |
| +-------------------------------+-----------------------+ | +-------------------------------+-----------------------+
| | PyPy, 3.7/MinGW |x86 | | | PyPy, 3.7/MinGW |x86 |

View File

@ -132,21 +132,21 @@ can be easily displayed in a chromaticity diagram, for example).
.. py:attribute:: manufacturer .. py:attribute:: manufacturer
The (english) display string for the device manufacturer (see The (English) display string for the device manufacturer (see
9.2.22 of ICC.1:2010). 9.2.22 of ICC.1:2010).
:type: :py:class:`unicode` or ``None`` :type: :py:class:`unicode` or ``None``
.. py:attribute:: model .. py:attribute:: model
The (english) display string for the device model of the device The (English) display string for the device model of the device
for which this profile is created (see 9.2.23 of ICC.1:2010). for which this profile is created (see 9.2.23 of ICC.1:2010).
:type: :py:class:`unicode` or ``None`` :type: :py:class:`unicode` or ``None``
.. py:attribute:: profile_description .. py:attribute:: profile_description
The (english) display string for the profile description (see The (English) display string for the profile description (see
9.2.41 of ICC.1:2010). 9.2.41 of ICC.1:2010).
:type: :py:class:`unicode` or ``None`` :type: :py:class:`unicode` or ``None``
@ -269,14 +269,14 @@ can be easily displayed in a chromaticity diagram, for example).
.. py:attribute:: viewing_condition .. py:attribute:: viewing_condition
The (english) display string for the viewing conditions (see The (English) display string for the viewing conditions (see
9.2.48 of ICC.1:2010). 9.2.48 of ICC.1:2010).
:type: :py:class:`unicode` or ``None`` :type: :py:class:`unicode` or ``None``
.. py:attribute:: screening_description .. py:attribute:: screening_description
The (english) display string for the screening conditions. The (English) display string for the screening conditions.
This tag was available in ICC 3.2, but it is removed from This tag was available in ICC 3.2, but it is removed from
version 4. version 4.

View File

@ -99,6 +99,24 @@ version.
Use ``PIL.__version__`` instead. Use ``PIL.__version__`` instead.
ImageCms.CmsProfile attributes
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Some attributes in ``ImageCms.CmsProfile`` have been deprecated since Pillow 3.2.0. From
6.0.0, they issue a ``DeprecationWarning``:
======================== ===============================
Deprecated Use instead
======================== ===============================
``color_space`` Padded ``xcolor_space``
``pcs`` Padded ``connection_space``
``product_copyright`` Unicode ``copyright``
``product_desc`` Unicode ``profile_description``
``product_description`` Unicode ``profile_description``
``product_manufacturer`` Unicode ``manufacturer``
``product_model`` Unicode ``model``
======================== ===============================
MIME type improvements MIME type improvements
^^^^^^^^^^^^^^^^^^^^^^ ^^^^^^^^^^^^^^^^^^^^^^
@ -131,7 +149,7 @@ Image.quantize
^^^^^^^^^^^^^^ ^^^^^^^^^^^^^^
The ``dither`` option is now a customisable parameter (was previously hardcoded to ``1``). The ``dither`` option is now a customisable parameter (was previously hardcoded to ``1``).
This parameter takes the same values used in ``Image.convert``. This parameter takes the same values used in :py:meth:`~PIL.Image.Image.convert`.
New language parameter New language parameter
^^^^^^^^^^^^^^^^^^^^^^ ^^^^^^^^^^^^^^^^^^^^^^
@ -147,12 +165,26 @@ language-specific glyphs and ligatures from the font:
* ``ImageFont.ImageFont.getsize_multiline()`` * ``ImageFont.ImageFont.getsize_multiline()``
* ``ImageFont.ImageFont.getsize()`` * ``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 PNG EXIF data
^^^^^^^^^^^^^ ^^^^^^^^^^^^^
EXIF data can now be read from and saved to PNG images. However, unlike other image EXIF data can now be read from and saved to PNG images. However, unlike other image
formats, EXIF data is not guaranteed to have been read until formats, EXIF data is not guaranteed to be present in :py:attr:`~PIL.Image.Image.info`
:py:meth:`~PIL.Image.Image.load` has been called. until :py:meth:`~PIL.Image.Image.load` has been called.
Other Changes Other Changes
============= =============
@ -172,3 +204,9 @@ TIFF compression codecs
^^^^^^^^^^^^^^^^^^^^^^^ ^^^^^^^^^^^^^^^^^^^^^^^
Support has been added for the LZMA, Zstd and WebP 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.

View File

@ -765,12 +765,7 @@ try:
url='http://python-pillow.org', url='http://python-pillow.org',
classifiers=[ classifiers=[
"Development Status :: 6 - Mature", "Development Status :: 6 - Mature",
"Topic :: Multimedia :: Graphics", "License :: OSI Approved :: Historical Permission Notice and Disclaimer (HPND)", # noqa: E501
"Topic :: Multimedia :: Graphics :: Capture :: Digital Camera",
"Topic :: Multimedia :: Graphics :: Capture :: Screen Capture",
"Topic :: Multimedia :: Graphics :: Graphics Conversion",
"Topic :: Multimedia :: Graphics :: Viewers",
"License :: Other/Proprietary License",
"Programming Language :: Python :: 2", "Programming Language :: Python :: 2",
"Programming Language :: Python :: 2.7", "Programming Language :: Python :: 2.7",
"Programming Language :: Python :: 3", "Programming Language :: Python :: 3",
@ -779,6 +774,11 @@ try:
"Programming Language :: Python :: 3.7", "Programming Language :: Python :: 3.7",
"Programming Language :: Python :: Implementation :: CPython", "Programming Language :: Python :: Implementation :: CPython",
"Programming Language :: Python :: Implementation :: PyPy", "Programming Language :: Python :: Implementation :: PyPy",
"Topic :: Multimedia :: Graphics",
"Topic :: Multimedia :: Graphics :: Capture :: Digital Camera",
"Topic :: Multimedia :: Graphics :: Capture :: Screen Capture",
"Topic :: Multimedia :: Graphics :: Graphics Conversion",
"Topic :: Multimedia :: Graphics :: Viewers",
], ],
python_requires=">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*", python_requires=">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*",
cmdclass={"build_ext": pil_build_ext}, cmdclass={"build_ext": pil_build_ext},
@ -789,7 +789,6 @@ try:
packages=["PIL"], packages=["PIL"],
package_dir={'': 'src'}, package_dir={'': 'src'},
keywords=["Imaging", ], keywords=["Imaging", ],
license='Standard PIL License',
zip_safe=not (debug_build() or PLATFORM_MINGW), ) zip_safe=not (debug_build() or PLATFORM_MINGW), )
except RequiredDependencyException as err: except RequiredDependencyException as err:
msg = """ msg = """

View File

@ -27,7 +27,6 @@
from . import Image, ImageFile, ImagePalette from . import Image, ImageFile, ImagePalette
from ._binary import i8, i16le as i16, i32le as i32, \ from ._binary import i8, i16le as i16, i32le as i32, \
o8, o16le as o16, o32le as o32 o8, o16le as o16, o32le as o32
import math
# __version__ is deprecated and will be removed in a future version. Use # __version__ is deprecated and will be removed in a future version. Use
# PIL.__version__ instead. # PIL.__version__ instead.
@ -121,8 +120,7 @@ class BmpImageFile(ImageFile.ImageFile):
file_info['colors'] = i32(header_data[28:32]) file_info['colors'] = i32(header_data[28:32])
file_info['palette_padding'] = 4 file_info['palette_padding'] = 4
self.info["dpi"] = tuple( self.info["dpi"] = tuple(
map(lambda x: int(math.ceil(x / 39.3701)), int(x / 39.3701 + 0.5) for x in file_info['pixels_per_meter'])
file_info['pixels_per_meter']))
if file_info['compression'] == self.BITFIELDS: if file_info['compression'] == self.BITFIELDS:
if len(header_data) >= 52: if len(header_data) >= 52:
for idx, mask in enumerate(['r_mask', for idx, mask in enumerate(['r_mask',
@ -311,7 +309,7 @@ def _save(im, fp, filename, bitmap_header=True):
dpi = info.get("dpi", (96, 96)) dpi = info.get("dpi", (96, 96))
# 1 meter == 39.3701 inches # 1 meter == 39.3701 inches
ppm = tuple(map(lambda x: int(x * 39.3701), dpi)) ppm = tuple(map(lambda x: int(x * 39.3701 + 0.5), dpi))
stride = ((im.size[0]*bits+7)//8+3) & (~3) stride = ((im.size[0]*bits+7)//8+3) & (~3)
header = 40 # or 64 for OS/2 version 2 header = 40 # or 64 for OS/2 version 2

View File

@ -32,14 +32,12 @@ class GimpPaletteFile(object):
if fp.readline()[:12] != b"GIMP Palette": if fp.readline()[:12] != b"GIMP Palette":
raise SyntaxError("not a GIMP palette file") raise SyntaxError("not a GIMP palette file")
i = 0 for i in range(256):
while i <= 255:
s = fp.readline() s = fp.readline()
if not s: if not s:
break break
# skip fields and comment lines # skip fields and comment lines
if re.match(br"\w+:|#", s): if re.match(br"\w+:|#", s):
continue continue
@ -50,11 +48,8 @@ class GimpPaletteFile(object):
if len(v) != 3: if len(v) != 3:
raise ValueError("bad palette entry") raise ValueError("bad palette entry")
if 0 <= i <= 255:
self.palette[i] = o8(v[0]) + o8(v[1]) + o8(v[2]) self.palette[i] = o8(v[0]) + o8(v[1]) + o8(v[2])
i += 1
self.palette = b"".join(self.palette) self.palette = b"".join(self.palette)
def getpalette(self): def getpalette(self):

View File

@ -40,8 +40,8 @@ except ImportError:
import __builtin__ import __builtin__
builtins = __builtin__ builtins = __builtin__
from . import ImageMode from . import ImageMode, TiffTags
from ._binary import i8 from ._binary import i8, i32le
from ._util import isPath, isStringType, deferred_error from ._util import isPath, isStringType, deferred_error
import os import os
@ -54,10 +54,10 @@ import atexit
import numbers import numbers
try: try:
# Python 3 # Python 3
from collections.abc import Callable from collections.abc import Callable, MutableMapping
except ImportError: except ImportError:
# Python 2.7 # Python 2.7
from collections import Callable from collections import Callable, MutableMapping
# Silence warning # Silence warning
@ -247,7 +247,7 @@ _MODEINFO = {
"L": ("L", "L", ("L",)), "L": ("L", "L", ("L",)),
"I": ("L", "I", ("I",)), "I": ("L", "I", ("I",)),
"F": ("L", "F", ("F",)), "F": ("L", "F", ("F",)),
"P": ("RGB", "L", ("P",)), "P": ("P", "L", ("P",)),
"RGB": ("RGB", "L", ("R", "G", "B")), "RGB": ("RGB", "L", ("R", "G", "B")),
"RGBX": ("RGB", "L", ("R", "G", "B", "X")), "RGBX": ("RGB", "L", ("R", "G", "B", "X")),
"RGBA": ("RGB", "L", ("R", "G", "B", "A")), "RGBA": ("RGB", "L", ("R", "G", "B", "A")),
@ -950,7 +950,7 @@ class Image(object):
delete_trns = False delete_trns = False
# transparency handling # transparency handling
if has_transparency: if has_transparency:
if self.mode in ('L', 'RGB') and mode == 'RGBA': if self.mode in ('1', 'L', 'I', 'RGB') and mode == 'RGBA':
# Use transparent conversion to promote from transparent # Use transparent conversion to promote from transparent
# color to an alpha channel. # color to an alpha channel.
new_im = self._new(self.im.convert_transparent( new_im = self._new(self.im.convert_transparent(
@ -1096,7 +1096,13 @@ class Image(object):
im = self.im.convert("P", dither, palette.im) im = self.im.convert("P", dither, palette.im)
return self._new(im) return self._new(im)
return self._new(self.im.quantize(colors, method, kmeans)) im = self._new(self.im.quantize(colors, method, kmeans))
from . import ImagePalette
mode = im.im.getpalettemode()
im.palette = ImagePalette.ImagePalette(mode, im.im.getpalette(mode, mode))
return im
def copy(self): def copy(self):
""" """
@ -1291,6 +1297,12 @@ class Image(object):
return tuple(extrema) return tuple(extrema)
return self.im.getextrema() return self.im.getextrema()
def getexif(self):
exif = Exif()
if "exif" in self.info:
exif.load(self.info["exif"])
return exif
def getim(self): def getim(self):
""" """
Returns a capsule that points to the internal image memory. Returns a capsule that points to the internal image memory.
@ -1559,7 +1571,7 @@ class Image(object):
self._ensure_mutable() self._ensure_mutable()
if self.mode not in ("LA", "RGBA"): if self.mode not in ("LA", "PA", "RGBA"):
# attempt to promote self to a matching alpha mode # attempt to promote self to a matching alpha mode
try: try:
mode = getmodebase(self.mode) + "A" mode = getmodebase(self.mode) + "A"
@ -1568,7 +1580,7 @@ class Image(object):
except (AttributeError, ValueError): except (AttributeError, ValueError):
# do things the hard way # do things the hard way
im = self.im.convert(mode) im = self.im.convert(mode)
if im.mode not in ("LA", "RGBA"): if im.mode not in ("LA", "PA", "RGBA"):
raise ValueError # sanity check raise ValueError # sanity check
self.im = im self.im = im
self.pyaccess = None self.pyaccess = None
@ -1576,7 +1588,7 @@ class Image(object):
except (KeyError, ValueError): except (KeyError, ValueError):
raise ValueError("illegal image mode") raise ValueError("illegal image mode")
if self.mode == "LA": if self.mode in ("LA", "PA"):
band = 1 band = 1
else: else:
band = 3 band = 3
@ -1619,10 +1631,10 @@ class Image(object):
def putpalette(self, data, rawmode="RGB"): def putpalette(self, data, rawmode="RGB"):
""" """
Attaches a palette to this image. The image must be a "P" or Attaches a palette to this image. The image must be a "P",
"L" image, and the palette sequence must contain 768 integer "PA", "L" or "LA" image, and the palette sequence must contain
values, where each group of three values represent the red, 768 integer values, where each group of three values represent
green, and blue values for the corresponding pixel the red, green, and blue values for the corresponding pixel
index. Instead of an integer sequence, you can use an 8-bit index. Instead of an integer sequence, you can use an 8-bit
string. string.
@ -1631,7 +1643,7 @@ class Image(object):
""" """
from . import ImagePalette from . import ImagePalette
if self.mode not in ("L", "P"): if self.mode not in ("L", "LA", "P", "PA"):
raise ValueError("illegal image mode") raise ValueError("illegal image mode")
self.load() self.load()
if isinstance(data, ImagePalette.ImagePalette): if isinstance(data, ImagePalette.ImagePalette):
@ -1643,7 +1655,7 @@ class Image(object):
else: else:
data = "".join(chr(x) for x in data) data = "".join(chr(x) for x in data)
palette = ImagePalette.raw(rawmode, data) palette = ImagePalette.raw(rawmode, data)
self.mode = "P" self.mode = "PA" if "A" in self.mode else "P"
self.palette = palette self.palette = palette
self.palette.mode = "RGB" self.palette.mode = "RGB"
self.load() # install new palette self.load() # install new palette
@ -1688,7 +1700,7 @@ class Image(object):
Rewrites the image to reorder the palette. Rewrites the image to reorder the palette.
:param dest_map: A list of indexes into the original palette. :param dest_map: A list of indexes into the original palette.
e.g. [1,0] would swap a two item palette, and list(range(255)) e.g. [1,0] would swap a two item palette, and list(range(256))
is the identity transform. is the identity transform.
:param source_palette: Bytes or None. :param source_palette: Bytes or None.
:returns: An :py:class:`~PIL.Image.Image` object. :returns: An :py:class:`~PIL.Image.Image` object.
@ -1958,7 +1970,7 @@ class Image(object):
filename = fp.name filename = fp.name
# may mutate self! # may mutate self!
self.load() self._ensure_mutable()
save_all = params.pop('save_all', False) save_all = params.pop('save_all', False)
self.encoderinfo = params self.encoderinfo = params
@ -2371,7 +2383,14 @@ def new(mode, size, color=0):
from . import ImageColor from . import ImageColor
color = ImageColor.getcolor(color, mode) color = ImageColor.getcolor(color, mode)
return Image()._new(core.fill(mode, size, color)) im = Image()
if mode == "P" and \
isinstance(color, (list, tuple)) and len(color) in [3, 4]:
# RGB or RGBA value for a P image
from . import ImagePalette
im.palette = ImagePalette.ImagePalette()
color = im.palette.getcolor(color)
return im._new(core.fill(mode, size, color))
def frombytes(mode, size, data, decoder_name="raw", *args): def frombytes(mode, size, data, decoder_name="raw", *args):
@ -2992,3 +3011,182 @@ def _apply_env_variables(env=None):
_apply_env_variables() _apply_env_variables()
atexit.register(core.clear_cache) atexit.register(core.clear_cache)
class Exif(MutableMapping):
endian = "<"
def __init__(self):
self._data = {}
self._ifds = {}
def _fixup_dict(self, src_dict):
# Helper function for _getexif()
# returns a dict with any single item tuples/lists as individual values
def _fixup(value):
try:
if len(value) == 1 and not isinstance(value, dict):
return value[0]
except Exception:
pass
return value
return {k: _fixup(v) for k, v in src_dict.items()}
def _get_ifd_dict(self, tag):
try:
# an offset pointer to the location of the nested embedded IFD.
# It should be a long, but may be corrupted.
self.fp.seek(self._data[tag])
except (KeyError, TypeError):
pass
else:
from . import TiffImagePlugin
info = TiffImagePlugin.ImageFileDirectory_v1(self.head)
info.load(self.fp)
return self._fixup_dict(info)
def load(self, data):
# Extract EXIF information. This is highly experimental,
# and is likely to be replaced with something better in a future
# version.
# The EXIF record consists of a TIFF file embedded in a JPEG
# application marker (!).
self.fp = io.BytesIO(data[6:])
self.head = self.fp.read(8)
# process dictionary
from . import TiffImagePlugin
info = TiffImagePlugin.ImageFileDirectory_v1(self.head)
self.endian = info._endian
self.fp.seek(info.next)
info.load(self.fp)
self._data = dict(self._fixup_dict(info))
# get EXIF extension
ifd = self._get_ifd_dict(0x8769)
if ifd:
self._data.update(ifd)
self._ifds[0x8769] = ifd
# get gpsinfo extension
ifd = self._get_ifd_dict(0x8825)
if ifd:
self._data[0x8825] = ifd
self._ifds[0x8825] = ifd
def tobytes(self, offset=0):
from . import TiffImagePlugin
if self.endian == "<":
head = b"II\x2A\x00\x08\x00\x00\x00"
else:
head = b"MM\x00\x2A\x00\x00\x00\x08"
ifd = TiffImagePlugin.ImageFileDirectory_v2(ifh=head)
for tag, value in self._data.items():
ifd[tag] = value
return b"Exif\x00\x00"+head+ifd.tobytes(offset)
def get_ifd(self, tag):
if tag not in self._ifds and tag in self._data:
if tag == 0xa005: # interop
self._ifds[tag] = self._get_ifd_dict(tag)
elif tag == 0x927c: # makernote
from . import TiffImagePlugin
if self._data[0x927c][:8] == b"FUJIFILM":
exif_data = self._data[0x927c]
ifd_offset = i32le(exif_data[8:12])
ifd_data = exif_data[ifd_offset:]
makernote = {}
for i in range(0, struct.unpack("<H", ifd_data[:2])[0]):
ifd_tag, typ, count, data = struct.unpack(
"<HHL4s", ifd_data[i*12 + 2:(i+1)*12 + 2])
try:
unit_size, handler =\
TiffImagePlugin.ImageFileDirectory_v2._load_dispatch[
typ
]
except KeyError:
continue
size = count * unit_size
if size > 4:
offset, = struct.unpack("<L", data)
data = ifd_data[offset-12:offset+size-12]
else:
data = data[:size]
if len(data) != size:
warnings.warn("Possibly corrupt EXIF MakerNote data. "
"Expecting to read %d bytes but only got %d."
" Skipping tag %s"
% (size, len(data), ifd_tag))
continue
if not data:
continue
makernote[ifd_tag] = handler(
TiffImagePlugin.ImageFileDirectory_v2(), data, False)
self._ifds[0x927c] = dict(self._fixup_dict(makernote))
elif self._data.get(0x010f) == "Nintendo":
ifd_data = self._data[0x927c]
makernote = {}
for i in range(0, struct.unpack(">H", ifd_data[:2])[0]):
ifd_tag, typ, count, data = struct.unpack(
">HHL4s", ifd_data[i*12 + 2:(i+1)*12 + 2])
if ifd_tag == 0x1101:
# CameraInfo
offset, = struct.unpack(">L", data)
self.fp.seek(offset)
camerainfo = {'ModelID': self.fp.read(4)}
self.fp.read(4)
# Seconds since 2000
camerainfo['TimeStamp'] = i32le(self.fp.read(12))
self.fp.read(4)
camerainfo['InternalSerialNumber'] = self.fp.read(4)
self.fp.read(12)
parallax = self.fp.read(4)
handler =\
TiffImagePlugin.ImageFileDirectory_v2._load_dispatch[
TiffTags.FLOAT
][1]
camerainfo['Parallax'] = handler(
TiffImagePlugin.ImageFileDirectory_v2(),
parallax, False)
self.fp.read(4)
camerainfo['Category'] = self.fp.read(2)
makernote = {0x1101: dict(self._fixup_dict(camerainfo))}
self._ifds[0x927c] = makernote
return self._ifds.get(tag, {})
def __str__(self):
return str(self._data)
def __len__(self):
return len(self._data)
def __getitem__(self, tag):
return self._data[tag]
def __contains__(self, tag):
return tag in self._data
if not py3:
def has_key(self, tag):
return tag in self
def __setitem__(self, tag, value):
self._data[tag] = value
def __delitem__(self, tag):
del self._data[tag]
def __iter__(self):
return iter(set(self._data))

View File

@ -686,11 +686,11 @@ def getProfileName(profile):
# // name was "%s - %s" (model, manufacturer) || Description , # // name was "%s - %s" (model, manufacturer) || Description ,
# // but if the Model and Manufacturer were the same or the model # // but if the Model and Manufacturer were the same or the model
# // was long, Just the model, in 1.x # // was long, Just the model, in 1.x
model = profile.profile.product_model model = profile.profile.model
manufacturer = profile.profile.product_manufacturer manufacturer = profile.profile.manufacturer
if not (model or manufacturer): if not (model or manufacturer):
return profile.profile.product_description + "\n" return (profile.profile.profile_description or "") + "\n"
if not manufacturer or len(model) > 30: if not manufacturer or len(model) > 30:
return model + "\n" return model + "\n"
return "%s - %s\n" % (model, manufacturer) return "%s - %s\n" % (model, manufacturer)
@ -727,8 +727,8 @@ def getProfileInfo(profile):
# Python, not C. the white point bits weren't working well, # Python, not C. the white point bits weren't working well,
# so skipping. # so skipping.
# info was description \r\n\r\n copyright \r\n\r\n K007 tag \r\n\r\n whitepoint # info was description \r\n\r\n copyright \r\n\r\n K007 tag \r\n\r\n whitepoint
description = profile.profile.product_description description = profile.profile.profile_description
cpright = profile.profile.product_copyright cpright = profile.profile.copyright
arr = [] arr = []
for elt in (description, cpright): for elt in (description, cpright):
if elt: if elt:
@ -762,7 +762,7 @@ def getProfileCopyright(profile):
# add an extra newline to preserve pyCMS compatibility # add an extra newline to preserve pyCMS compatibility
if not isinstance(profile, ImageCmsProfile): if not isinstance(profile, ImageCmsProfile):
profile = ImageCmsProfile(profile) profile = ImageCmsProfile(profile)
return profile.profile.product_copyright + "\n" return (profile.profile.copyright or "") + "\n"
except (AttributeError, IOError, TypeError, ValueError) as v: except (AttributeError, IOError, TypeError, ValueError) as v:
raise PyCMSError(v) raise PyCMSError(v)
@ -790,7 +790,7 @@ def getProfileManufacturer(profile):
# add an extra newline to preserve pyCMS compatibility # add an extra newline to preserve pyCMS compatibility
if not isinstance(profile, ImageCmsProfile): if not isinstance(profile, ImageCmsProfile):
profile = ImageCmsProfile(profile) profile = ImageCmsProfile(profile)
return profile.profile.product_manufacturer + "\n" return (profile.profile.manufacturer or "") + "\n"
except (AttributeError, IOError, TypeError, ValueError) as v: except (AttributeError, IOError, TypeError, ValueError) as v:
raise PyCMSError(v) raise PyCMSError(v)
@ -819,7 +819,7 @@ def getProfileModel(profile):
# add an extra newline to preserve pyCMS compatibility # add an extra newline to preserve pyCMS compatibility
if not isinstance(profile, ImageCmsProfile): if not isinstance(profile, ImageCmsProfile):
profile = ImageCmsProfile(profile) profile = ImageCmsProfile(profile)
return profile.profile.product_model + "\n" return (profile.profile.model or "") + "\n"
except (AttributeError, IOError, TypeError, ValueError) as v: except (AttributeError, IOError, TypeError, ValueError) as v:
raise PyCMSError(v) raise PyCMSError(v)
@ -848,7 +848,7 @@ def getProfileDescription(profile):
# add an extra newline to preserve pyCMS compatibility # add an extra newline to preserve pyCMS compatibility
if not isinstance(profile, ImageCmsProfile): if not isinstance(profile, ImageCmsProfile):
profile = ImageCmsProfile(profile) profile = ImageCmsProfile(profile)
return profile.profile.product_description + "\n" return (profile.profile.profile_description or "") + "\n"
except (AttributeError, IOError, TypeError, ValueError) as v: except (AttributeError, IOError, TypeError, ValueError) as v:
raise PyCMSError(v) raise PyCMSError(v)

View File

@ -522,3 +522,30 @@ def solarize(image, threshold=128):
else: else:
lut.append(255-i) lut.append(255-i)
return _lut(image, lut) return _lut(image, lut)
def exif_transpose(image):
"""
If an image has an EXIF Orientation tag, return a new image that is
transposed accordingly. Otherwise, return a copy of the image.
:param image: The image to transpose.
:return: An image.
"""
exif = image.getexif()
orientation = exif.get(0x0112)
method = {
2: Image.FLIP_LEFT_RIGHT,
3: Image.ROTATE_180,
4: Image.FLIP_TOP_BOTTOM,
5: Image.TRANSPOSE,
6: Image.ROTATE_270,
7: Image.TRANSVERSE,
8: Image.ROTATE_90
}.get(orientation)
if method is not None:
transposed_image = image.transpose(method)
del exif[0x0112]
transposed_image.info["exif"] = exif.tobytes()
return transposed_image
return image.copy()

View File

@ -198,35 +198,9 @@ def getiptcinfo(im):
elif isinstance(im, JpegImagePlugin.JpegImageFile): elif isinstance(im, JpegImagePlugin.JpegImageFile):
# extract the IPTC/NAA resource # extract the IPTC/NAA resource
try: photoshop = im.info.get("photoshop")
app = im.app["APP13"] if photoshop:
if app[:14] == b"Photoshop 3.0\x00": data = photoshop.get(0x0404)
app = app[14:]
# parse the image resource block
offset = 0
while app[offset:offset+4] == b"8BIM":
offset += 4
# resource code
code = i16(app, offset)
offset += 2
# resource name (usually empty)
name_len = i8(app[offset])
# name = app[offset+1:offset+1+name_len]
offset = 1 + offset + name_len
if offset & 1:
offset += 1
# resource data block
size = i32(app, offset)
offset += 4
if code == 0x0404:
# 0x0404 contains IPTC/NAA data
data = app[offset:offset+size]
break
offset = offset + size
if offset & 1:
offset += 1
except (AttributeError, KeyError):
pass
elif isinstance(im, TiffImagePlugin.TiffImageFile): elif isinstance(im, TiffImagePlugin.TiffImageFile):
# get raw data from the IPTC/NAA tag (PhotoShop tags the data # get raw data from the IPTC/NAA tag (PhotoShop tags the data

View File

@ -39,7 +39,7 @@ import struct
import io import io
import warnings import warnings
from . import Image, ImageFile, TiffImagePlugin from . import Image, ImageFile, TiffImagePlugin
from ._binary import i8, o8, i16be as i16 from ._binary import i8, o8, i16be as i16, i32be as i32
from .JpegPresets import presets from .JpegPresets import presets
from ._util import isStringType from ._util import isStringType
@ -86,7 +86,7 @@ def APP(self, marker):
self.info["jfif_density"] = jfif_density self.info["jfif_density"] = jfif_density
elif marker == 0xFFE1 and s[:5] == b"Exif\0": elif marker == 0xFFE1 and s[:5] == b"Exif\0":
if "exif" not in self.info: if "exif" not in self.info:
# extract Exif information (incomplete) # extract EXIF information (incomplete)
self.info["exif"] = s # FIXME: value will change self.info["exif"] = s # FIXME: value will change
elif marker == 0xFFE2 and s[:5] == b"FPXR\0": elif marker == 0xFFE2 and s[:5] == b"FPXR\0":
# extract FlashPix information (incomplete) # extract FlashPix information (incomplete)
@ -104,6 +104,39 @@ def APP(self, marker):
# reassemble the profile, rather than assuming that the APP2 # reassemble the profile, rather than assuming that the APP2
# markers appear in the correct sequence. # markers appear in the correct sequence.
self.icclist.append(s) self.icclist.append(s)
elif marker == 0xFFED:
if s[:14] == b"Photoshop 3.0\x00":
blocks = s[14:]
# parse the image resource block
offset = 0
photoshop = {}
while blocks[offset:offset+4] == b"8BIM":
offset += 4
# resource code
code = i16(blocks, offset)
offset += 2
# resource name (usually empty)
name_len = i8(blocks[offset])
# name = blocks[offset+1:offset+1+name_len]
offset = 1 + offset + name_len
if offset & 1:
offset += 1
# resource data block
size = i32(blocks, offset)
offset += 4
data = blocks[offset:offset+size]
if code == 0x03ED: # ResolutionInfo
data = {
'XResolution': i32(data[:4]) / 65536,
'DisplayedUnitsX': i16(data[4:8]),
'YResolution': i32(data[8:12]) / 65536,
'DisplayedUnitsY': i16(data[12:]),
}
photoshop[code] = data
offset = offset + size
if offset & 1:
offset += 1
self.info["photoshop"] = photoshop
elif marker == 0xFFEE and s[:5] == b"Adobe": elif marker == 0xFFEE and s[:5] == b"Adobe":
self.info["adobe"] = i16(s, 5) self.info["adobe"] = i16(s, 5)
# extract Adobe custom properties # extract Adobe custom properties
@ -127,15 +160,15 @@ def APP(self, marker):
resolution_unit = exif[0x0128] resolution_unit = exif[0x0128]
x_resolution = exif[0x011A] x_resolution = exif[0x011A]
try: try:
dpi = x_resolution[0] / x_resolution[1] dpi = float(x_resolution[0]) / x_resolution[1]
except TypeError: except TypeError:
dpi = x_resolution dpi = x_resolution
if resolution_unit == 3: # cm if resolution_unit == 3: # cm
# 1 dpcm = 2.54 dpi # 1 dpcm = 2.54 dpi
dpi *= 2.54 dpi *= 2.54
self.info["dpi"] = dpi, dpi self.info["dpi"] = int(dpi + 0.5), int(dpi + 0.5)
except (KeyError, SyntaxError, ZeroDivisionError): except (KeyError, SyntaxError, ZeroDivisionError):
# SyntaxError for invalid/unreadable exif # SyntaxError for invalid/unreadable EXIF
# KeyError for dpi not included # KeyError for dpi not included
# ZeroDivisionError for invalid dpi rational value # ZeroDivisionError for invalid dpi rational value
self.info["dpi"] = 72, 72 self.info["dpi"] = 72, 72
@ -439,60 +472,23 @@ class JpegImageFile(ImageFile.ImageFile):
def _fixup_dict(src_dict): def _fixup_dict(src_dict):
# Helper function for _getexif() # Helper function for _getexif()
# returns a dict with any single item tuples/lists as individual values # returns a dict with any single item tuples/lists as individual values
def _fixup(value): exif = Image.Exif()
try: return exif._fixup_dict(src_dict)
if len(value) == 1 and not isinstance(value, dict):
return value[0]
except Exception:
pass
return value
return {k: _fixup(v) for k, v in src_dict.items()}
def _getexif(self): def _getexif(self):
# Extract EXIF information. This method is highly experimental, # Use the cached version if possible
# and is likely to be replaced with something better in a future
# version.
# The EXIF record consists of a TIFF file embedded in a JPEG
# application marker (!).
try: try:
data = self.info["exif"] return self.info["parsed_exif"]
except KeyError: except KeyError:
return None
fp = io.BytesIO(data[6:])
head = fp.read(8)
# process dictionary
info = TiffImagePlugin.ImageFileDirectory_v1(head)
fp.seek(info.next)
info.load(fp)
exif = dict(_fixup_dict(info))
# get exif extension
try:
# exif field 0x8769 is an offset pointer to the location
# of the nested embedded exif ifd.
# It should be a long, but may be corrupted.
fp.seek(exif[0x8769])
except (KeyError, TypeError):
pass pass
else:
info = TiffImagePlugin.ImageFileDirectory_v1(head)
info.load(fp)
exif.update(_fixup_dict(info))
# get gpsinfo extension
try:
# exif field 0x8825 is an offset pointer to the location
# of the nested embedded gps exif ifd.
# It should be a long, but may be corrupted.
fp.seek(exif[0x8825])
except (KeyError, TypeError):
pass
else:
info = TiffImagePlugin.ImageFileDirectory_v1(head)
info.load(fp)
exif[0x8825] = _fixup_dict(info)
if "exif" not in self.info:
return None
exif = dict(self.getexif())
# Cache the result for future use
self.info["parsed_exif"] = exif
return exif return exif
@ -728,6 +724,10 @@ def _save(im, fp, filename):
optimize = info.get("optimize", False) optimize = info.get("optimize", False)
exif = info.get("exif", b"")
if isinstance(exif, Image.Exif):
exif = exif.tobytes()
# get keyword arguments # get keyword arguments
im.encoderconfig = ( im.encoderconfig = (
quality, quality,
@ -739,7 +739,7 @@ def _save(im, fp, filename):
subsampling, subsampling,
qtables, qtables,
extra, extra,
info.get("exif", b"") exif
) )
# if we optimize, libjpeg needs a buffer big enough to hold the whole image # if we optimize, libjpeg needs a buffer big enough to hold the whole image
@ -757,9 +757,9 @@ def _save(im, fp, filename):
else: else:
bufsize = im.size[0] * im.size[1] bufsize = im.size[0] * im.size[1]
# The exif info needs to be written as one block, + APP1, + one spare byte. # The EXIF info needs to be written as one block, + APP1, + one spare byte.
# Ensure that our buffer is big enough. Same with the icc_profile block. # Ensure that our buffer is big enough. Same with the icc_profile block.
bufsize = max(ImageFile.MAXBLOCK, bufsize, len(info.get("exif", b"")) + 5, bufsize = max(ImageFile.MAXBLOCK, bufsize, len(exif) + 5,
len(extra) + 1) len(extra) + 1)
ImageFile._save(im, fp, [("jpeg", (0, 0)+im.size, 0, rawmode)], bufsize) ImageFile._save(im, fp, [("jpeg", (0, 0)+im.size, 0, rawmode)], bufsize)
@ -786,7 +786,8 @@ def jpeg_factory(fp=None, filename=None):
if mpheader[45057] > 1: if mpheader[45057] > 1:
# It's actually an MPO # It's actually an MPO
from .MpoImagePlugin import MpoImageFile from .MpoImagePlugin import MpoImageFile
im = MpoImageFile(fp, filename) # Don't reload everything, just convert it.
im = MpoImageFile.adopt(im, mpheader)
except (TypeError, IndexError): except (TypeError, IndexError):
# It is really a JPEG # It is really a JPEG
pass pass

View File

@ -18,7 +18,8 @@
# See the README file for information on usage and redistribution. # See the README file for information on usage and redistribution.
# #
from . import Image, JpegImagePlugin from . import Image, ImageFile, JpegImagePlugin
from ._binary import i16be as i16
# __version__ is deprecated and will be removed in a future version. Use # __version__ is deprecated and will be removed in a future version. Use
# PIL.__version__ instead. # PIL.__version__ instead.
@ -46,7 +47,10 @@ class MpoImageFile(JpegImagePlugin.JpegImageFile):
def _open(self): def _open(self):
self.fp.seek(0) # prep the fp in order to pass the JPEG test self.fp.seek(0) # prep the fp in order to pass the JPEG test
JpegImagePlugin.JpegImageFile._open(self) JpegImagePlugin.JpegImageFile._open(self)
self.mpinfo = self._getmp() self._after_jpeg_open()
def _after_jpeg_open(self, mpheader=None):
self.mpinfo = mpheader if mpheader is not None else self._getmp()
self.__framecount = self.mpinfo[0xB001] self.__framecount = self.mpinfo[0xB001]
self.__mpoffsets = [mpent['DataOffset'] + self.info['mpoffset'] self.__mpoffsets = [mpent['DataOffset'] + self.info['mpoffset']
for mpent in self.mpinfo[0xB002]] for mpent in self.mpinfo[0xB002]]
@ -78,6 +82,20 @@ class MpoImageFile(JpegImagePlugin.JpegImageFile):
return return
self.fp = self.__fp self.fp = self.__fp
self.offset = self.__mpoffsets[frame] self.offset = self.__mpoffsets[frame]
self.fp.seek(self.offset + 2) # skip SOI marker
if "parsed_exif" in self.info:
del self.info["parsed_exif"]
if i16(self.fp.read(2)) == 0xFFE1: # APP1
n = i16(self.fp.read(2))-2
self.info["exif"] = ImageFile._safe_read(self.fp, n)
exif = self._getexif()
if 40962 in exif and 40963 in exif:
self._size = (exif[40962], exif[40963])
elif "exif" in self.info:
del self.info["exif"]
self.tile = [ self.tile = [
("jpeg", (0, 0) + self.size, self.offset, (self.mode, "")) ("jpeg", (0, 0) + self.size, self.offset, (self.mode, ""))
] ]
@ -95,6 +113,22 @@ class MpoImageFile(JpegImagePlugin.JpegImageFile):
finally: finally:
self.__fp = None self.__fp = None
@staticmethod
def adopt(jpeg_instance, mpheader=None):
"""
Transform the instance of JpegImageFile into
an instance of MpoImageFile.
After the call, the JpegImageFile is extended
to be an MpoImageFile.
This is essentially useful when opening a JPEG
file that reveals itself as an MPO, to avoid
double call to _open.
"""
jpeg_instance.__class__ = MpoImageFile
jpeg_instance._after_jpeg_open(mpheader)
return jpeg_instance
# --------------------------------------------------------------------- # ---------------------------------------------------------------------
# Registry stuff # Registry stuff

View File

@ -54,19 +54,24 @@ _MAGIC = b"\211PNG\r\n\032\n"
_MODES = { _MODES = {
# supported bits/color combinations, and corresponding modes/rawmodes # supported bits/color combinations, and corresponding modes/rawmodes
# Greyscale
(1, 0): ("1", "1"), (1, 0): ("1", "1"),
(2, 0): ("L", "L;2"), (2, 0): ("L", "L;2"),
(4, 0): ("L", "L;4"), (4, 0): ("L", "L;4"),
(8, 0): ("L", "L"), (8, 0): ("L", "L"),
(16, 0): ("I", "I;16B"), (16, 0): ("I", "I;16B"),
# Truecolour
(8, 2): ("RGB", "RGB"), (8, 2): ("RGB", "RGB"),
(16, 2): ("RGB", "RGB;16B"), (16, 2): ("RGB", "RGB;16B"),
# Indexed-colour
(1, 3): ("P", "P;1"), (1, 3): ("P", "P;1"),
(2, 3): ("P", "P;2"), (2, 3): ("P", "P;2"),
(4, 3): ("P", "P;4"), (4, 3): ("P", "P;4"),
(8, 3): ("P", "P"), (8, 3): ("P", "P"),
# Greyscale with alpha
(8, 4): ("LA", "LA"), (8, 4): ("LA", "LA"),
(16, 4): ("RGBA", "LA;16B"), # LA;16B->LA not yet available (16, 4): ("RGBA", "LA;16B"), # LA;16B->LA not yet available
# Truecolour with alpha
(8, 6): ("RGBA", "RGBA"), (8, 6): ("RGBA", "RGBA"),
(16, 6): ("RGBA", "RGBA;16B"), (16, 6): ("RGBA", "RGBA;16B"),
} }
@ -386,7 +391,7 @@ class PngStream(ChunkStream):
# otherwise, we have a byte string with one alpha value # otherwise, we have a byte string with one alpha value
# for each palette entry # for each palette entry
self.im_info["transparency"] = s self.im_info["transparency"] = s
elif self.im_mode == "L": elif self.im_mode in ("1", "L", "I"):
self.im_info["transparency"] = i16(s) self.im_info["transparency"] = i16(s)
elif self.im_mode == "RGB": elif self.im_mode == "RGB":
self.im_info["transparency"] = i16(s), i16(s[2:]), i16(s[4:]) self.im_info["transparency"] = i16(s), i16(s[2:]), i16(s[4:])
@ -691,8 +696,14 @@ class PngImageFile(ImageFile.ImageFile):
def _getexif(self): def _getexif(self):
if "exif" not in self.info: if "exif" not in self.info:
self.load() self.load()
from .JpegImagePlugin import _getexif if "exif" not in self.info:
return _getexif(self) return None
return dict(self.getexif())
def getexif(self):
if "exif" not in self.info:
self.load()
return ImageFile.ImageFile.getexif(self)
# -------------------------------------------------------------------- # --------------------------------------------------------------------
@ -841,7 +852,7 @@ def _save(im, fp, filename, chunk=putchunk):
transparency = max(0, min(255, transparency)) transparency = max(0, min(255, transparency))
alpha = b'\xFF' * transparency + b'\0' alpha = b'\xFF' * transparency + b'\0'
chunk(fp, b"tRNS", alpha[:alpha_bytes]) chunk(fp, b"tRNS", alpha[:alpha_bytes])
elif im.mode == "L": elif im.mode in ("1", "L", "I"):
transparency = max(0, min(65535, transparency)) transparency = max(0, min(65535, transparency))
chunk(fp, b"tRNS", o16(transparency)) chunk(fp, b"tRNS", o16(transparency))
elif im.mode == "RGB": elif im.mode == "RGB":
@ -875,6 +886,8 @@ def _save(im, fp, filename, chunk=putchunk):
exif = im.encoderinfo.get("exif", im.info.get("exif")) exif = im.encoderinfo.get("exif", im.info.get("exif"))
if exif: if exif:
if isinstance(exif, Image.Exif):
exif = exif.tobytes(8)
if exif.startswith(b"Exif\x00\x00"): if exif.startswith(b"Exif\x00\x00"):
exif = exif[6:] exif = exif[6:]
chunk(fp, b"eXIf", exif) chunk(fp, b"eXIf", exif)

View File

@ -208,7 +208,7 @@ class SpiderImageFile(ImageFile.ImageFile):
# given a list of filenames, return a list of images # given a list of filenames, return a list of images
def loadImageSeries(filelist=None): def loadImageSeries(filelist=None):
"""create a list of Image.images for use in montage""" """create a list of :py:class:`~PIL.Image.Image` objects for use in a montage"""
if filelist is None or len(filelist) < 1: if filelist is None or len(filelist) < 1:
return return

View File

@ -785,17 +785,12 @@ class ImageFileDirectory_v2(MutableMapping):
warnings.warn(str(msg)) warnings.warn(str(msg))
return return
def save(self, fp): def tobytes(self, offset=0):
if fp.tell() == 0: # skip TIFF header on subsequent pages
# tiff header -- PIL always starts the first IFD at offset 8
fp.write(self._prefix + self._pack("HL", 42, 8))
# FIXME What about tagdata? # FIXME What about tagdata?
fp.write(self._pack("H", len(self._tags_v2))) result = self._pack("H", len(self._tags_v2))
entries = [] entries = []
offset = fp.tell() + len(self._tags_v2) * 12 + 4 offset = offset + len(result) + len(self._tags_v2) * 12 + 4
stripoffsets = None stripoffsets = None
# pass 1: convert tags to binary format # pass 1: convert tags to binary format
@ -844,18 +839,29 @@ class ImageFileDirectory_v2(MutableMapping):
for tag, typ, count, value, data in entries: for tag, typ, count, value, data in entries:
if DEBUG > 1: if DEBUG > 1:
print(tag, typ, count, repr(value), repr(data)) print(tag, typ, count, repr(value), repr(data))
fp.write(self._pack("HHL4s", tag, typ, count, value)) result += self._pack("HHL4s", tag, typ, count, value)
# -- overwrite here for multi-page -- # -- overwrite here for multi-page --
fp.write(b"\0\0\0\0") # end of entries result += b"\0\0\0\0" # end of entries
# pass 3: write auxiliary data to file # pass 3: write auxiliary data to file
for tag, typ, count, value, data in entries: for tag, typ, count, value, data in entries:
fp.write(data) result += data
if len(data) & 1: if len(data) & 1:
fp.write(b"\0") result += b"\0"
return offset return result
def save(self, fp):
if fp.tell() == 0: # skip TIFF header on subsequent pages
# tiff header -- PIL always starts the first IFD at offset 8
fp.write(self._prefix + self._pack("HL", 42, 8))
offset = fp.tell()
result = self.tobytes(offset)
fp.write(result)
return offset + len(result)
ImageFileDirectory_v2._load_dispatch = _load_dispatch ImageFileDirectory_v2._load_dispatch = _load_dispatch
@ -985,7 +991,6 @@ class TiffImageFile(ImageFile.ImageFile):
self.__fp = self.fp self.__fp = self.fp
self._frame_pos = [] self._frame_pos = []
self._n_frames = None self._n_frames = None
self._is_animated = None
if DEBUG: if DEBUG:
print("*** TiffImageFile._open ***") print("*** TiffImageFile._open ***")
@ -999,29 +1004,14 @@ class TiffImageFile(ImageFile.ImageFile):
def n_frames(self): def n_frames(self):
if self._n_frames is None: if self._n_frames is None:
current = self.tell() current = self.tell()
try: self._seek(len(self._frame_pos))
while True: while self._n_frames is None:
self._seek(self.tell() + 1) self._seek(self.tell() + 1)
except EOFError:
self._n_frames = self.tell() + 1
self.seek(current) self.seek(current)
return self._n_frames return self._n_frames
@property @property
def is_animated(self): def is_animated(self):
if self._is_animated is None:
if self._n_frames is not None:
self._is_animated = self._n_frames != 1
else:
current = self.tell()
try:
self.seek(1)
self._is_animated = True
except EOFError:
self._is_animated = False
self.seek(current)
return self._is_animated return self._is_animated
def seek(self, frame): def seek(self, frame):
@ -1053,10 +1043,13 @@ class TiffImageFile(ImageFile.ImageFile):
print("Loading tags, location: %s" % self.fp.tell()) print("Loading tags, location: %s" % self.fp.tell())
self.tag_v2.load(self.fp) self.tag_v2.load(self.fp)
self.__next = self.tag_v2.next self.__next = self.tag_v2.next
if self.__next == 0:
self._n_frames = frame + 1
if len(self._frame_pos) == 1:
self._is_animated = self.__next != 0
self.__frame += 1 self.__frame += 1
self.fp.seek(self._frame_pos[frame]) self.fp.seek(self._frame_pos[frame])
self.tag_v2.load(self.fp) self.tag_v2.load(self.fp)
self.__next = self.tag_v2.next
# fill the legacy tag/ifd entries # fill the legacy tag/ifd entries
self.tag = self.ifd = ImageFileDirectory_v1.from_v2(self.tag_v2) self.tag = self.ifd = ImageFileDirectory_v1.from_v2(self.tag_v2)
self.__frame = frame self.__frame = frame
@ -1087,7 +1080,7 @@ class TiffImageFile(ImageFile.ImageFile):
def load_end(self): def load_end(self):
# allow closing if we're on the first frame, there's no next # allow closing if we're on the first frame, there's no next
# This is the ImageFile.load path only, libtiff specific below. # This is the ImageFile.load path only, libtiff specific below.
if self.__frame == 0 and not self.__next: if not self._is_animated:
self._close_exclusive_fp_after_loading = True self._close_exclusive_fp_after_loading = True
def _load_libtiff(self): def _load_libtiff(self):
@ -1167,8 +1160,7 @@ class TiffImageFile(ImageFile.ImageFile):
self.tile = [] self.tile = []
self.readonly = 0 self.readonly = 0
# libtiff closed the fp in a, we need to close self.fp, if possible # libtiff closed the fp in a, we need to close self.fp, if possible
if self._exclusive_fp: if self._exclusive_fp and not self._is_animated:
if self.__frame == 0 and not self.__next:
self.fp.close() self.fp.close()
self.fp = None # might be shared self.fp = None # might be shared
@ -1261,11 +1253,11 @@ class TiffImageFile(ImageFile.ImageFile):
if xres and yres: if xres and yres:
resunit = self.tag_v2.get(RESOLUTION_UNIT) resunit = self.tag_v2.get(RESOLUTION_UNIT)
if resunit == 2: # dots per inch if resunit == 2: # dots per inch
self.info["dpi"] = xres, yres self.info["dpi"] = int(xres + 0.5), int(yres + 0.5)
elif resunit == 3: # dots per centimeter. convert to dpi elif resunit == 3: # dots per centimeter. convert to dpi
self.info["dpi"] = xres * 2.54, yres * 2.54 self.info["dpi"] = int(xres * 2.54 + 0.5), int(yres * 2.54 + 0.5)
elif resunit is None: # used to default to 1, but now 2) elif resunit is None: # used to default to 1, but now 2)
self.info["dpi"] = xres, yres self.info["dpi"] = int(xres + 0.5), int(yres + 0.5)
# For backward compatibility, # For backward compatibility,
# we also preserve the old behavior # we also preserve the old behavior
self.info["resolution"] = xres, yres self.info["resolution"] = xres, yres
@ -1476,8 +1468,8 @@ def _save(im, fp, filename):
dpi = im.encoderinfo.get("dpi") dpi = im.encoderinfo.get("dpi")
if dpi: if dpi:
ifd[RESOLUTION_UNIT] = 2 ifd[RESOLUTION_UNIT] = 2
ifd[X_RESOLUTION] = dpi[0] ifd[X_RESOLUTION] = int(dpi[0] + 0.5)
ifd[Y_RESOLUTION] = dpi[1] ifd[Y_RESOLUTION] = int(dpi[1] + 0.5)
if bits != (1,): if bits != (1,):
ifd[BITSPERSAMPLE] = bits ifd[BITSPERSAMPLE] = bits

View File

@ -93,8 +93,9 @@ class WebPImageFile(ImageFile.ImageFile):
self.seek(0) self.seek(0)
def _getexif(self): def _getexif(self):
from .JpegImagePlugin import _getexif if "exif" not in self.info:
return _getexif(self) return None
return dict(self.getexif())
@property @property
def n_frames(self): def n_frames(self):
@ -216,6 +217,8 @@ def _save_all(im, fp, filename):
method = im.encoderinfo.get("method", 0) method = im.encoderinfo.get("method", 0)
icc_profile = im.encoderinfo.get("icc_profile", "") icc_profile = im.encoderinfo.get("icc_profile", "")
exif = im.encoderinfo.get("exif", "") exif = im.encoderinfo.get("exif", "")
if isinstance(exif, Image.Exif):
exif = exif.tobytes()
xmp = im.encoderinfo.get("xmp", "") xmp = im.encoderinfo.get("xmp", "")
if allow_mixed: if allow_mixed:
lossless = False lossless = False
@ -315,6 +318,8 @@ def _save(im, fp, filename):
quality = im.encoderinfo.get("quality", 80) quality = im.encoderinfo.get("quality", 80)
icc_profile = im.encoderinfo.get("icc_profile", "") icc_profile = im.encoderinfo.get("icc_profile", "")
exif = im.encoderinfo.get("exif", "") exif = im.encoderinfo.get("exif", "")
if isinstance(exif, Image.Exif):
exif = exif.tobytes()
xmp = im.encoderinfo.get("xmp", "") xmp = im.encoderinfo.get("xmp", "")
if im.mode not in _VALID_WEBP_LEGACY_MODES: if im.mode not in _VALID_WEBP_LEGACY_MODES:

View File

@ -131,8 +131,8 @@ class WmfStubImageFile(ImageFile.StubImageFile):
size = x1 - x0, y1 - y0 size = x1 - x0, y1 - y0
# calculate dots per inch from bbox and frame # calculate dots per inch from bbox and frame
xdpi = 2540 * (x1 - y0) // (frame[2] - frame[0]) xdpi = int(2540.0 * (x1 - y0) / (frame[2] - frame[0]) + 0.5)
ydpi = 2540 * (y1 - y0) // (frame[3] - frame[1]) ydpi = int(2540.0 * (y1 - y0) / (frame[3] - frame[1]) + 0.5)
self.info["wmf_bbox"] = x0, y0, x1, y1 self.info["wmf_bbox"] = x0, y0, x1, y1

View File

@ -1,2 +1,2 @@
# Master version for Pillow # Master version for Pillow
__version__ = '6.0.0.dev0' __version__ = '6.1.0.dev0'

View File

@ -71,6 +71,7 @@
* See the README file for information on usage and redistribution. * See the README file for information on usage and redistribution.
*/ */
#define PY_SSIZE_T_CLEAN
#include "Python.h" #include "Python.h"
#ifdef HAVE_LIBJPEG #ifdef HAVE_LIBJPEG
@ -1564,11 +1565,12 @@ _putpalette(ImagingObject* self, PyObject* args)
char* rawmode; char* rawmode;
UINT8* palette; UINT8* palette;
int palettesize; Py_ssize_t palettesize;
if (!PyArg_ParseTuple(args, "s"PY_ARG_BYTES_LENGTH, &rawmode, &palette, &palettesize)) if (!PyArg_ParseTuple(args, "s"PY_ARG_BYTES_LENGTH, &rawmode, &palette, &palettesize))
return NULL; 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); PyErr_SetString(PyExc_ValueError, wrong_mode);
return NULL; return NULL;
} }
@ -1626,7 +1628,7 @@ _putpalettealphas(ImagingObject* self, PyObject* args)
{ {
int i; int i;
UINT8 *values; UINT8 *values;
int length; Py_ssize_t length;
if (!PyArg_ParseTuple(args, PY_ARG_BYTES_LENGTH, &values, &length)) if (!PyArg_ParseTuple(args, PY_ARG_BYTES_LENGTH, &values, &length))
return NULL; return NULL;
@ -1770,7 +1772,7 @@ im_setmode(ImagingObject* self, PyObject* args)
Imaging im; Imaging im;
char* mode; char* mode;
int modelen; Py_ssize_t modelen;
if (!PyArg_ParseTuple(args, "s#:setmode", &mode, &modelen)) if (!PyArg_ParseTuple(args, "s#:setmode", &mode, &modelen))
return NULL; return NULL;
@ -2066,8 +2068,8 @@ _getprojection(ImagingObject* self, PyObject* args)
ImagingGetProjection(self->image, (unsigned char *)xprofile, (unsigned char *)yprofile); ImagingGetProjection(self->image, (unsigned char *)xprofile, (unsigned char *)yprofile);
result = Py_BuildValue(PY_ARG_BYTES_LENGTH PY_ARG_BYTES_LENGTH, result = Py_BuildValue(PY_ARG_BYTES_LENGTH PY_ARG_BYTES_LENGTH,
xprofile, self->image->xsize, xprofile, (Py_ssize_t)self->image->xsize,
yprofile, self->image->ysize); yprofile, (Py_ssize_t)self->image->ysize);
free(xprofile); free(xprofile);
free(yprofile); free(yprofile);
@ -2342,7 +2344,7 @@ _font_new(PyObject* self_, PyObject* args)
ImagingObject* imagep; ImagingObject* imagep;
unsigned char* glyphdata; unsigned char* glyphdata;
int glyphdata_length; Py_ssize_t glyphdata_length;
if (!PyArg_ParseTuple(args, "O!"PY_ARG_BYTES_LENGTH, if (!PyArg_ParseTuple(args, "O!"PY_ARG_BYTES_LENGTH,
&Imaging_Type, &imagep, &Imaging_Type, &imagep,
&glyphdata, &glyphdata_length)) &glyphdata, &glyphdata_length))

View File

@ -25,6 +25,7 @@ kevin@cazabon.com\n\
http://www.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 "Python.h" // Include before wchar.h so _GNU_SOURCE is set
#include "wchar.h" #include "wchar.h"
#include "datetime.h" #include "datetime.h"
@ -120,7 +121,7 @@ cms_profile_fromstring(PyObject* self, PyObject* args)
cmsHPROFILE hProfile; cmsHPROFILE hProfile;
char* pProfile; char* pProfile;
int nProfile; Py_ssize_t nProfile;
#if PY_VERSION_HEX >= 0x03000000 #if PY_VERSION_HEX >= 0x03000000
if (!PyArg_ParseTuple(args, "y#:profile_frombytes", &pProfile, &nProfile)) if (!PyArg_ParseTuple(args, "y#:profile_frombytes", &pProfile, &nProfile))
return NULL; return NULL;
@ -966,6 +967,8 @@ _profile_getattr(CmsProfileObject* self, cmsInfoType field)
static PyObject* static PyObject*
cms_profile_getattr_product_desc(CmsProfileObject* self, void* closure) 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 // description was Description != 'Copyright' || or "%s - %s" (manufacturer, model) in 1.x
return _profile_getattr(self, cmsInfoDescription); return _profile_getattr(self, cmsInfoDescription);
} }
@ -975,24 +978,32 @@ cms_profile_getattr_product_desc(CmsProfileObject* self, void* closure)
static PyObject* static PyObject*
cms_profile_getattr_product_description(CmsProfileObject* self, void* closure) 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); return _profile_getattr(self, cmsInfoDescription);
} }
static PyObject* static PyObject*
cms_profile_getattr_product_model(CmsProfileObject* self, void* closure) 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); return _profile_getattr(self, cmsInfoModel);
} }
static PyObject* static PyObject*
cms_profile_getattr_product_manufacturer(CmsProfileObject* self, void* closure) 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); return _profile_getattr(self, cmsInfoManufacturer);
} }
static PyObject* static PyObject*
cms_profile_getattr_product_copyright(CmsProfileObject* self, void* closure) 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); return _profile_getattr(self, cmsInfoCopyright);
} }
@ -1005,12 +1016,16 @@ cms_profile_getattr_rendering_intent(CmsProfileObject* self, void* closure)
static PyObject* static PyObject*
cms_profile_getattr_pcs(CmsProfileObject* self, void* closure) 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))); return PyUnicode_DecodeFSDefault(findICmode(cmsGetPCS(self->profile)));
} }
static PyObject* static PyObject*
cms_profile_getattr_color_space(CmsProfileObject* self, void* closure) 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))); return PyUnicode_DecodeFSDefault(findICmode(cmsGetColorSpace(self->profile)));
} }

View File

@ -18,6 +18,7 @@
* Copyright (c) 1998-2007 by Secret Labs AB * Copyright (c) 1998-2007 by Secret Labs AB
*/ */
#define PY_SSIZE_T_CLEAN
#include "Python.h" #include "Python.h"
#include "Imaging.h" #include "Imaging.h"
@ -237,12 +238,12 @@ getfont(PyObject* self_, PyObject* args, PyObject* kw)
int error = 0; int error = 0;
char* filename = NULL; char* filename = NULL;
int size; Py_ssize_t size;
int index = 0; Py_ssize_t index = 0;
int layout_engine = 0; Py_ssize_t layout_engine = 0;
unsigned char* encoding; unsigned char* encoding;
unsigned char* font_bytes; unsigned char* font_bytes;
int font_bytes_size = 0; Py_ssize_t font_bytes_size = 0;
static char* kwlist[] = { static char* kwlist[] = {
"filename", "size", "index", "encoding", "font_bytes", "filename", "size", "index", "encoding", "font_bytes",
"layout_engine", NULL "layout_engine", NULL

View File

@ -29,6 +29,7 @@
/* FIXME: make these pluggable! */ /* FIXME: make these pluggable! */
#define PY_SSIZE_T_CLEAN
#include "Python.h" #include "Python.h"
#include "Imaging.h" #include "Imaging.h"
@ -117,7 +118,8 @@ static PyObject*
_decode(ImagingDecoderObject* decoder, PyObject* args) _decode(ImagingDecoderObject* decoder, PyObject* args)
{ {
UINT8* buffer; UINT8* buffer;
int bufsize, status; Py_ssize_t bufsize;
int status;
ImagingSectionCookie cookie; ImagingSectionCookie cookie;
if (!PyArg_ParseTuple(args, PY_ARG_BYTES_LENGTH, &buffer, &bufsize)) if (!PyArg_ParseTuple(args, PY_ARG_BYTES_LENGTH, &buffer, &bufsize))

View File

@ -22,6 +22,7 @@
/* FIXME: make these pluggable! */ /* FIXME: make these pluggable! */
#define PY_SSIZE_T_CLEAN
#include "Python.h" #include "Python.h"
#include "Imaging.h" #include "Imaging.h"
@ -123,7 +124,7 @@ _encode(ImagingEncoderObject* encoder, PyObject* args)
/* Encode to a Python string (allocated by this method) */ /* Encode to a Python string (allocated by this method) */
int bufsize = 16384; Py_ssize_t bufsize = 16384;
if (!PyArg_ParseTuple(args, "|i", &bufsize)) if (!PyArg_ParseTuple(args, "|i", &bufsize))
return NULL; return NULL;
@ -176,8 +177,8 @@ _encode_to_file(ImagingEncoderObject* encoder, PyObject* args)
/* Encode to a file handle */ /* Encode to a file handle */
int fh; Py_ssize_t fh;
int bufsize = 16384; Py_ssize_t bufsize = 16384;
if (!PyArg_ParseTuple(args, "i|i", &fh, &bufsize)) if (!PyArg_ParseTuple(args, "i|i", &fh, &bufsize))
return NULL; return NULL;
@ -221,7 +222,7 @@ _setimage(ImagingEncoderObject* encoder, PyObject* args)
PyObject* op; PyObject* op;
Imaging im; Imaging im;
ImagingCodecState state; ImagingCodecState state;
int x0, y0, x1, y1; Py_ssize_t x0, y0, x1, y1;
/* Define where image data should be stored */ /* Define where image data should be stored */
@ -406,8 +407,8 @@ PyImaging_GifEncoderNew(PyObject* self, PyObject* args)
char *mode; char *mode;
char *rawmode; char *rawmode;
int bits = 8; Py_ssize_t bits = 8;
int interlace = 0; Py_ssize_t interlace = 0;
if (!PyArg_ParseTuple(args, "ss|ii", &mode, &rawmode, &bits, &interlace)) if (!PyArg_ParseTuple(args, "ss|ii", &mode, &rawmode, &bits, &interlace))
return NULL; return NULL;
@ -438,7 +439,7 @@ PyImaging_PcxEncoderNew(PyObject* self, PyObject* args)
char *mode; char *mode;
char *rawmode; char *rawmode;
int bits = 8; Py_ssize_t bits = 8;
if (!PyArg_ParseTuple(args, "ss|ii", &mode, &rawmode, &bits)) { if (!PyArg_ParseTuple(args, "ss|ii", &mode, &rawmode, &bits)) {
return NULL; return NULL;
@ -470,8 +471,8 @@ PyImaging_RawEncoderNew(PyObject* self, PyObject* args)
char *mode; char *mode;
char *rawmode; char *rawmode;
int stride = 0; Py_ssize_t stride = 0;
int ystep = 1; Py_ssize_t ystep = 1;
if (!PyArg_ParseTuple(args, "ss|ii", &mode, &rawmode, &stride, &ystep)) if (!PyArg_ParseTuple(args, "ss|ii", &mode, &rawmode, &stride, &ystep))
return NULL; return NULL;
@ -503,7 +504,7 @@ PyImaging_TgaRleEncoderNew(PyObject* self, PyObject* args)
char *mode; char *mode;
char *rawmode; char *rawmode;
int ystep = 1; Py_ssize_t ystep = 1;
if (!PyArg_ParseTuple(args, "ss|i", &mode, &rawmode, &ystep)) if (!PyArg_ParseTuple(args, "ss|i", &mode, &rawmode, &ystep))
return NULL; return NULL;
@ -561,11 +562,11 @@ PyImaging_ZipEncoderNew(PyObject* self, PyObject* args)
char* mode; char* mode;
char* rawmode; char* rawmode;
int optimize = 0; Py_ssize_t optimize = 0;
int compress_level = -1; Py_ssize_t compress_level = -1;
int compress_type = -1; Py_ssize_t compress_type = -1;
char* dictionary = NULL; 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, if (!PyArg_ParseTuple(args, "ss|iii"PY_ARG_BYTES_LENGTH, &mode, &rawmode,
&optimize, &optimize,
&compress_level, &compress_type, &compress_level, &compress_type,
@ -701,20 +702,20 @@ PyImaging_JpegEncoderNew(PyObject* self, PyObject* args)
char *mode; char *mode;
char *rawmode; char *rawmode;
int quality = 0; Py_ssize_t quality = 0;
int progressive = 0; Py_ssize_t progressive = 0;
int smooth = 0; Py_ssize_t smooth = 0;
int optimize = 0; Py_ssize_t optimize = 0;
int streamtype = 0; /* 0=interchange, 1=tables only, 2=image only */ Py_ssize_t streamtype = 0; /* 0=interchange, 1=tables only, 2=image only */
int xdpi = 0, ydpi = 0; Py_ssize_t xdpi = 0, ydpi = 0;
int subsampling = -1; /* -1=default, 0=none, 1=medium, 2=high */ Py_ssize_t subsampling = -1; /* -1=default, 0=none, 1=medium, 2=high */
PyObject* qtables=NULL; PyObject* qtables=NULL;
unsigned int *qarrays = NULL; unsigned int *qarrays = NULL;
int qtablesLen = 0; int qtablesLen = 0;
char* extra = NULL; char* extra = NULL;
int extra_size; Py_ssize_t extra_size;
char* rawExif = NULL; 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, if (!PyArg_ParseTuple(args, "ss|iiiiiiiiO"PY_ARG_BYTES_LENGTH""PY_ARG_BYTES_LENGTH,
&mode, &rawmode, &quality, &mode, &rawmode, &quality,
@ -805,7 +806,7 @@ PyImaging_LibTiffEncoderNew(PyObject* self, PyObject* args)
char* rawmode; char* rawmode;
char* compname; char* compname;
char* filename; char* filename;
int fp; Py_ssize_t fp;
PyObject *dir; PyObject *dir;
PyObject *key, *value; PyObject *key, *value;
@ -985,14 +986,14 @@ PyImaging_Jpeg2KEncoderNew(PyObject *self, PyObject *args)
PyObject *offset = NULL, *tile_offset = NULL, *tile_size = NULL; PyObject *offset = NULL, *tile_offset = NULL, *tile_size = NULL;
char *quality_mode = "rates"; char *quality_mode = "rates";
PyObject *quality_layers = NULL; PyObject *quality_layers = NULL;
int num_resolutions = 0; Py_ssize_t num_resolutions = 0;
PyObject *cblk_size = NULL, *precinct_size = NULL; PyObject *cblk_size = NULL, *precinct_size = NULL;
PyObject *irreversible = NULL; PyObject *irreversible = NULL;
char *progression = "LRCP"; char *progression = "LRCP";
OPJ_PROG_ORDER prog_order; OPJ_PROG_ORDER prog_order;
char *cinema_mode = "no"; char *cinema_mode = "no";
OPJ_CINEMA_MODE cine_mode; OPJ_CINEMA_MODE cine_mode;
int fd = -1; Py_ssize_t fd = -1;
if (!PyArg_ParseTuple(args, "ss|OOOsOIOOOssi", &mode, &format, if (!PyArg_ParseTuple(args, "ss|OOOsOIOOOssi", &mode, &format,
&offset, &tile_offset, &tile_size, &offset, &tile_offset, &tile_size,

View File

@ -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 static void
rgb2cmyk(UINT8* out, const UINT8* in, int xsize) rgb2cmyk(UINT8* out, const UINT8* in, int xsize)
{ {
@ -592,6 +604,22 @@ i2f(UINT8* out_, const UINT8* in_, int xsize)
*out++ = (FLOAT32) *in++; *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 */ /* 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 static void
ycbcr2l(UINT8* out, const UINT8* in, int xsize) ycbcr2l(UINT8* out, const UINT8* in, int xsize)
{ {
@ -665,6 +705,16 @@ ycbcr2l(UINT8* out, const UINT8* in, int xsize)
*out++ = in[0]; *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 */ /* I;16 (16-bit) conversions */
/* ------------------------- */ /* ------------------------- */
@ -802,13 +852,18 @@ static struct {
{ "LA", "L", la2l }, { "LA", "L", la2l },
{ "LA", "La", lA2la }, { "LA", "La", lA2la },
{ "LA", "RGB", la2rgb }, { "LA", "RGB", la2rgb },
{ "LA", "RGBX", la2rgb },
{ "LA", "RGBA", la2rgb }, { "LA", "RGBA", la2rgb },
{ "LA", "RGBX", la2rgb },
{ "LA", "CMYK", la2cmyk },
{ "LA", "YCbCr", la2ycbcr },
{ "La", "LA", la2lA }, { "La", "LA", la2lA },
{ "I", "L", i2l }, { "I", "L", i2l },
{ "I", "F", i2f }, { "I", "F", i2f },
{ "I", "RGB", i2rgb },
{ "I", "RGBA", i2rgb },
{ "I", "RGBX", i2rgb },
{ "F", "L", f2l }, { "F", "L", f2l },
{ "F", "I", f2i }, { "F", "I", f2i },
@ -842,8 +897,9 @@ static struct {
{ "RGBX", "1", rgb2bit }, { "RGBX", "1", rgb2bit },
{ "RGBX", "L", rgb2l }, { "RGBX", "L", rgb2l },
{ "RGBA", "I", rgb2i }, { "RGBX", "LA", rgb2la },
{ "RGBA", "F", rgb2f }, { "RGBX", "I", rgb2i },
{ "RGBX", "F", rgb2f },
{ "RGBX", "RGB", rgba2rgb }, { "RGBX", "RGB", rgba2rgb },
{ "RGBX", "CMYK", rgb2cmyk }, { "RGBX", "CMYK", rgb2cmyk },
{ "RGBX", "YCbCr", ImagingConvertRGB2YCbCr }, { "RGBX", "YCbCr", ImagingConvertRGB2YCbCr },
@ -853,6 +909,7 @@ static struct {
{ "CMYK", "RGBX", cmyk2rgb }, { "CMYK", "RGBX", cmyk2rgb },
{ "YCbCr", "L", ycbcr2l }, { "YCbCr", "L", ycbcr2l },
{ "YCbCr", "LA", ycbcr2la },
{ "YCbCr", "RGB", ImagingConvertYCbCr2RGB }, { "YCbCr", "RGB", ImagingConvertYCbCr2RGB },
{ "HSV", "RGB", hsv2rgb }, { "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; *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 static void
p2l(UINT8* out, const UINT8* in, int xsize, const UINT8* palette) 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; *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 static void
p2la(UINT8* out, const UINT8* in, int xsize, const UINT8* palette) 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; int x;
/* FIXME: precalculate greyscale palette? */ /* FIXME: precalculate greyscale palette? */
for (x = 0; x < xsize; x++, in += 2) { for (x = 0; x < xsize; x++, in += 4, out += 4) {
*out++ = L(&palette[in[0]*4]) / 1000; out[0] = out[1] = out[2] = L(&palette[in[0]*4]) / 1000;
*out++ = in[1]; 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; *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 static void
p2f(UINT8* out_, const UINT8* in, int xsize, const UINT8* palette) 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; *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 static void
p2rgb(UINT8* out, const UINT8* in, int xsize, const UINT8* palette) 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 static void
p2rgba(UINT8* out, const UINT8* in, int xsize, const UINT8* palette) 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); 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 static void
p2ycbcr(UINT8* out, const UINT8* in, int xsize, const UINT8* palette) 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); 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 static Imaging
frompalette(Imaging imOut, Imaging imIn, const char *mode) 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"); alpha = !strcmp(imIn->mode, "PA");
if (strcmp(mode, "1") == 0) if (strcmp(mode, "1") == 0)
convert = p2bit; convert = alpha ? pa2bit : p2bit;
else if (strcmp(mode, "L") == 0) else if (strcmp(mode, "L") == 0)
convert = p2l; convert = alpha ? pa2l : p2l;
else if (strcmp(mode, "LA") == 0) 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) else if (strcmp(mode, "I") == 0)
convert = p2i; convert = alpha ? pa2i : p2i;
else if (strcmp(mode, "F") == 0) else if (strcmp(mode, "F") == 0)
convert = p2f; convert = alpha ? pa2f : p2f;
else if (strcmp(mode, "RGB") == 0) else if (strcmp(mode, "RGB") == 0)
convert = p2rgb; convert = alpha ? pa2rgb : p2rgb;
else if (strcmp(mode, "RGBA") == 0) else if (strcmp(mode, "RGBA") == 0)
convert = (alpha) ? pa2rgba : p2rgba; convert = alpha ? pa2rgba : p2rgba;
else if (strcmp(mode, "RGBX") == 0) else if (strcmp(mode, "RGBX") == 0)
convert = p2rgba; convert = alpha ? pa2rgba : p2rgba;
else if (strcmp(mode, "CMYK") == 0) else if (strcmp(mode, "CMYK") == 0)
convert = p2cmyk; convert = alpha ? pa2cmyk : p2cmyk;
else if (strcmp(mode, "YCbCr") == 0) else if (strcmp(mode, "YCbCr") == 0)
convert = p2ycbcr; convert = alpha ? pa2ycbcr : p2ycbcr;
else else
return (Imaging) ImagingError_ValueError("conversion not supported"); return (Imaging) ImagingError_ValueError("conversion not supported");
@ -1052,9 +1187,10 @@ frompalette(Imaging imOut, Imaging imIn, const char *mode)
#pragma optimize("", off) #pragma optimize("", off)
#endif #endif
static Imaging 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; ImagingSectionCookie cookie;
int alpha;
int x, y; int x, y;
ImagingPalette palette = inpalette;; 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) if (strcmp(imIn->mode, "L") != 0 && strncmp(imIn->mode, "RGB", 3) != 0)
return (Imaging) ImagingError_ValueError("conversion not supported"); return (Imaging) ImagingError_ValueError("conversion not supported");
alpha = !strcmp(mode, "PA");
if (palette == NULL) { if (palette == NULL) {
/* FIXME: make user configurable */ /* FIXME: make user configurable */
if (imIn->bands == 1) if (imIn->bands == 1)
@ -1073,7 +1211,7 @@ topalette(Imaging imOut, Imaging imIn, ImagingPalette inpalette, int dither)
if (!palette) if (!palette)
return (Imaging) ImagingError_ValueError("no palette"); return (Imaging) ImagingError_ValueError("no palette");
imOut = ImagingNew2Dirty("P", imOut, imIn); imOut = ImagingNew2Dirty(mode, imOut, imIn);
if (!imOut) { if (!imOut) {
if (palette != inpalette) if (palette != inpalette)
ImagingPaletteDelete(palette); ImagingPaletteDelete(palette);
@ -1088,8 +1226,13 @@ topalette(Imaging imOut, Imaging imIn, ImagingPalette inpalette, int dither)
/* Greyscale palette: copy data as is */ /* Greyscale palette: copy data as is */
ImagingSectionEnter(&cookie); ImagingSectionEnter(&cookie);
for (y = 0; y < imIn->ysize; y++) 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); memcpy(imOut->image[y], imIn->image[y], imIn->linesize);
}
}
ImagingSectionLeave(&cookie); ImagingSectionLeave(&cookie);
} else { } else {
@ -1120,7 +1263,7 @@ topalette(Imaging imOut, Imaging imIn, ImagingPalette inpalette, int dither)
int g, g0, g1, g2; int g, g0, g1, g2;
int b, b0, b1, b2; int b, b0, b1, b2;
UINT8* in = (UINT8*) imIn->image[y]; UINT8* in = (UINT8*) imIn->image[y];
UINT8* out = imOut->image8[y]; UINT8* out = alpha ? (UINT8*) imOut->image32[y] : imOut->image8[y];
int* e = errors; int* e = errors;
r = r0 = r1 = 0; r = r0 = r1 = 0;
@ -1139,7 +1282,12 @@ topalette(Imaging imOut, Imaging imIn, ImagingPalette inpalette, int dither)
cache = &ImagingPaletteCache(palette, r, g, b); cache = &ImagingPaletteCache(palette, r, g, b);
if (cache[0] == 0x100) if (cache[0] == 0x100)
ImagingPaletteCacheUpdate(palette, r, g, b); ImagingPaletteCacheUpdate(palette, r, g, b);
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]; out[x] = (UINT8) cache[0];
}
r -= (int) palette->palette[cache[0]*4]; r -= (int) palette->palette[cache[0]*4];
g -= (int) palette->palette[cache[0]*4+1]; 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++) { for (y = 0; y < imIn->ysize; y++) {
int r, g, b; int r, g, b;
UINT8* in = (UINT8*) imIn->image[y]; 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) { for (x = 0; x < imIn->xsize; x++, in += 4) {
INT16* cache; INT16* cache;
@ -1183,8 +1331,12 @@ topalette(Imaging imOut, Imaging imIn, ImagingPalette inpalette, int dither)
cache = &ImagingPaletteCache(palette, r, g, b); cache = &ImagingPaletteCache(palette, r, g, b);
if (cache[0] == 0x100) if (cache[0] == 0x100)
ImagingPaletteCacheUpdate(palette, r, g, b); ImagingPaletteCacheUpdate(palette, r, g, b);
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]; out[x] = (UINT8) cache[0];
}
} }
} }
ImagingSectionLeave(&cookie); 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) if (strcmp(imIn->mode, "P") == 0 || strcmp(imIn->mode, "PA") == 0)
return frompalette(imOut, imIn, mode); return frompalette(imOut, imIn, mode);
if (strcmp(mode, "P") == 0) if (strcmp(mode, "P") == 0 || strcmp(mode, "PA") == 0)
return topalette(imOut, imIn, palette, dither); return topalette(imOut, imIn, mode, palette, dither);
if (dither && strcmp(mode, "1") == 0) if (dither && strcmp(mode, "1") == 0)
return tobilevel(imOut, imIn, dither); return tobilevel(imOut, imIn, dither);
@ -1385,6 +1537,8 @@ ImagingConvertTransparent(Imaging imIn, const char *mode,
} }
if (!((strcmp(imIn->mode, "RGB") == 0 || if (!((strcmp(imIn->mode, "RGB") == 0 ||
strcmp(imIn->mode, "1") == 0 ||
strcmp(imIn->mode, "I") == 0 ||
strcmp(imIn->mode, "L") == 0) strcmp(imIn->mode, "L") == 0)
&& strcmp(mode, "RGBA") == 0)) && strcmp(mode, "RGBA") == 0))
#ifdef notdef #ifdef notdef
@ -1402,8 +1556,14 @@ ImagingConvertTransparent(Imaging imIn, const char *mode,
if (strcmp(imIn->mode, "RGB") == 0) { if (strcmp(imIn->mode, "RGB") == 0) {
convert = rgb2rgba; convert = rgb2rgba;
} else {
if (strcmp(imIn->mode, "1") == 0) {
convert = bit2rgb;
} else if (strcmp(imIn->mode, "I") == 0) {
convert = i2rgb;
} else { } else {
convert = l2rgb; convert = l2rgb;
}
g = b = r; g = b = r;
} }

View File

@ -29,8 +29,8 @@ ImagingFlipLeftRight(Imaging imOut, Imaging imIn)
#define FLIP_LEFT_RIGHT(INT, image) \ #define FLIP_LEFT_RIGHT(INT, image) \
for (y = 0; y < imIn->ysize; y++) { \ for (y = 0; y < imIn->ysize; y++) { \
INT* in = imIn->image[y]; \ INT* in = (INT *)imIn->image[y]; \
INT* out = imOut->image[y]; \ INT* out = (INT *)imOut->image[y]; \
xr = imIn->xsize-1; \ xr = imIn->xsize-1; \
for (x = 0; x < imIn->xsize; x++, xr--) \ for (x = 0; x < imIn->xsize; x++, xr--) \
out[xr] = in[x]; \ out[xr] = in[x]; \
@ -105,10 +105,10 @@ ImagingRotate90(Imaging imOut, Imaging imIn)
yyysize = yy + ROTATE_SMALL_CHUNK < imIn->ysize ? yy + ROTATE_SMALL_CHUNK : imIn->ysize; \ 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; \ xxxsize = xx + ROTATE_SMALL_CHUNK < imIn->xsize ? xx + ROTATE_SMALL_CHUNK : imIn->xsize; \
for (yyy = yy; yyy < yyysize; yyy++) { \ for (yyy = yy; yyy < yyysize; yyy++) { \
INT* in = imIn->image[yyy]; \ INT* in = (INT *)imIn->image[yyy]; \
xr = imIn->xsize - 1 - xx; \ xr = imIn->xsize - 1 - xx; \
for (xxx = xx; xxx < xxxsize; xxx++, xr--) { \ for (xxx = xx; xxx < xxxsize; xxx++, xr--) { \
INT* out = imOut->image[xr]; \ INT* out = (INT *)imOut->image[xr]; \
out[yyy] = in[xxx]; \ out[yyy] = in[xxx]; \
} \ } \
} \ } \
@ -161,9 +161,9 @@ ImagingTranspose(Imaging imOut, Imaging imIn)
yyysize = yy + ROTATE_SMALL_CHUNK < imIn->ysize ? yy + ROTATE_SMALL_CHUNK : imIn->ysize; \ 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; \ xxxsize = xx + ROTATE_SMALL_CHUNK < imIn->xsize ? xx + ROTATE_SMALL_CHUNK : imIn->xsize; \
for (yyy = yy; yyy < yyysize; yyy++) { \ for (yyy = yy; yyy < yyysize; yyy++) { \
INT* in = imIn->image[yyy]; \ INT* in = (INT *)imIn->image[yyy]; \
for (xxx = xx; xxx < xxxsize; xxx++) { \ for (xxx = xx; xxx < xxxsize; xxx++) { \
INT* out = imOut->image[xxx]; \ INT* out = (INT *)imOut->image[xxx]; \
out[yyy] = in[xxx]; \ out[yyy] = in[xxx]; \
} \ } \
} \ } \
@ -217,10 +217,10 @@ ImagingTransverse(Imaging imOut, Imaging imIn)
xxxsize = xx + ROTATE_SMALL_CHUNK < imIn->xsize ? xx + ROTATE_SMALL_CHUNK : imIn->xsize; \ xxxsize = xx + ROTATE_SMALL_CHUNK < imIn->xsize ? xx + ROTATE_SMALL_CHUNK : imIn->xsize; \
yr = imIn->ysize - 1 - yy; \ yr = imIn->ysize - 1 - yy; \
for (yyy = yy; yyy < yyysize; yyy++, yr--) { \ for (yyy = yy; yyy < yyysize; yyy++, yr--) { \
INT* in = imIn->image[yyy]; \ INT* in = (INT *)imIn->image[yyy]; \
xr = imIn->xsize - 1 - xx; \ xr = imIn->xsize - 1 - xx; \
for (xxx = xx; xxx < xxxsize; xxx++, xr--) { \ for (xxx = xx; xxx < xxxsize; xxx++, xr--) { \
INT* out = imOut->image[xr]; \ INT* out = (INT *)imOut->image[xr]; \
out[yr] = in[xxx]; \ out[yr] = in[xxx]; \
} \ } \
} \ } \
@ -264,8 +264,8 @@ ImagingRotate180(Imaging imOut, Imaging imIn)
#define ROTATE_180(INT, image) \ #define ROTATE_180(INT, image) \
for (y = 0; y < imIn->ysize; y++, yr--) { \ for (y = 0; y < imIn->ysize; y++, yr--) { \
INT* in = imIn->image[y]; \ INT* in = (INT *)imIn->image[y]; \
INT* out = imOut->image[yr]; \ INT* out = (INT *)imOut->image[yr]; \
xr = imIn->xsize-1; \ xr = imIn->xsize-1; \
for (x = 0; x < imIn->xsize; x++, xr--) \ for (x = 0; x < imIn->xsize; x++, xr--) \
out[xr] = in[x]; \ out[xr] = in[x]; \
@ -317,9 +317,9 @@ ImagingRotate270(Imaging imOut, Imaging imIn)
xxxsize = xx + ROTATE_SMALL_CHUNK < imIn->xsize ? xx + ROTATE_SMALL_CHUNK : imIn->xsize; \ xxxsize = xx + ROTATE_SMALL_CHUNK < imIn->xsize ? xx + ROTATE_SMALL_CHUNK : imIn->xsize; \
yr = imIn->ysize - 1 - yy; \ yr = imIn->ysize - 1 - yy; \
for (yyy = yy; yyy < yyysize; yyy++, yr--) { \ for (yyy = yy; yyy < yyysize; yyy++, yr--) { \
INT* in = imIn->image[yyy]; \ INT* in = (INT *)imIn->image[yyy]; \
for (xxx = xx; xxx < xxxsize; xxx++) { \ for (xxx = xx; xxx < xxxsize; xxx++) { \
INT* out = imOut->image[xxx]; \ INT* out = (INT *)imOut->image[xxx]; \
out[yr] = in[xxx]; \ out[yr] = in[xxx]; \
} \ } \
} \ } \

View File

@ -1331,6 +1331,7 @@ static struct {
{"RGB", "BGR;5", 16, ImagingUnpackBGR15}, /* compat */ {"RGB", "BGR;5", 16, ImagingUnpackBGR15}, /* compat */
{"RGB", "RGBX", 32, copy4}, {"RGB", "RGBX", 32, copy4},
{"RGB", "RGBX;L", 32, unpackRGBAL}, {"RGB", "RGBX;L", 32, unpackRGBAL},
{"RGB", "RGBA;L", 32, unpackRGBAL},
{"RGB", "BGRX", 32, ImagingUnpackBGRX}, {"RGB", "BGRX", 32, ImagingUnpackBGRX},
{"RGB", "XRGB", 24, ImagingUnpackXRGB}, {"RGB", "XRGB", 24, ImagingUnpackXRGB},
{"RGB", "XBGR", 32, ImagingUnpackXBGR}, {"RGB", "XBGR", 32, ImagingUnpackXBGR},

View File

@ -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:\ 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

View File

@ -109,6 +109,7 @@ set INCLUDE=%%INCLUDE%%;%%INCLIB%%\%(inc_dir)s;%%INCLIB%%\tcl%(tcl_ver)s\include
setlocal setlocal
set LIB=%%LIB%%;C:\Python%(py_ver)s\tcl%(vc_setup)s 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 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
endlocal endlocal

View File

@ -187,37 +187,30 @@ endlocal
return script % compiler return script % compiler
def msbuild_freetype(compiler): def msbuild_freetype(compiler, bit):
if compiler['env_version'] == 'v7.1': script = r"""
return msbuild_freetype_71(compiler)
return msbuild_freetype_70(compiler)
def msbuild_freetype_71(compiler):
return r"""
rem Build freetype rem Build freetype
setlocal setlocal
rd /S /Q %%FREETYPE%%\objs 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%% 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 endlocal
""" % compiler # noqa: E501 """
return script % 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
def build_lcms2(compiler): def build_lcms2(compiler):
@ -238,9 +231,9 @@ setlocal
rd /S /Q %%LCMS%%\Lib rd /S /Q %%LCMS%%\Lib
rd /S /Q %%LCMS%%\Projects\VC%(vc_version)s\Release 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: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%% 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 endlocal
""" % compiler # noqa: E501 """ % compiler # noqa: E501
@ -265,7 +258,7 @@ rem Build gs
setlocal setlocal
""" + vc_setup(compiler, bit) + r""" """ + vc_setup(compiler, bit) + r"""
set MSVC_VERSION=""" + { set MSVC_VERSION=""" + {
"2008": "9", "2010": "90",
"2015": "14" "2015": "14"
}[compiler['vc_version']] + r""" }[compiler['vc_version']] + r"""
set RCOMP="C:\Program Files (x86)\Microsoft SDKs\Windows\v7.1A\Bin\RC.Exe" 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(extract_openjpeg(compiler))
script.append(msbuild_freetype(compiler)) script.append(msbuild_freetype(compiler, bit))
script.append(build_lcms2(compiler)) script.append(build_lcms2(compiler))
# script.append(nmake_openjpeg(compiler)) # script.append(nmake_openjpeg(compiler))
script.append(build_ghostscript(compiler, bit)) script.append(build_ghostscript(compiler, bit))
@ -308,7 +301,7 @@ if 'PYTHON' in os.environ:
else: else:
# for compiler in all_compilers(): # for compiler in all_compilers():
# add_compiler(compiler) # 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: with open('build_deps.cmd', 'w') as f:
f.write("\n".join(script)) f.write("\n".join(script))

View File

@ -3,8 +3,8 @@ import os
SF_MIRROR = 'http://iweb.dl.sourceforge.net' SF_MIRROR = 'http://iweb.dl.sourceforge.net'
PILLOW_DEPENDS_DIR = 'C:\\pillow-depends\\' PILLOW_DEPENDS_DIR = 'C:\\pillow-depends\\'
pythons = {'27': {'compiler': 7, 'vc': 2008}, pythons = {'27': {'compiler': 7, 'vc': 2010},
'pypy2': {'compiler': 7, 'vc': 2008}, 'pypy2': {'compiler': 7, 'vc': 2010},
'35': {'compiler': 7.1, 'vc': 2015}, '35': {'compiler': 7.1, 'vc': 2015},
'36': {'compiler': 7.1, 'vc': 2015}, '36': {'compiler': 7.1, 'vc': 2015},
'37': {'compiler': 7.1, 'vc': 2015}} '37': {'compiler': 7.1, 'vc': 2015}}
@ -43,9 +43,9 @@ libs = {
'dir': 'lcms2-2.7', 'dir': 'lcms2-2.7',
}, },
'ghostscript': { 'ghostscript': {
'url': 'https://github.com/ArtifexSoftware/ghostpdl-downloads/releases/download/gs926/ghostscript-9.26.tar.gz', # noqa: E501 'url': 'https://github.com/ArtifexSoftware/ghostpdl-downloads/releases/download/gs927/ghostscript-9.27.tar.gz', # noqa: E501
'filename': PILLOW_DEPENDS_DIR + 'ghostscript-9.26.tar.gz', 'filename': PILLOW_DEPENDS_DIR + 'ghostscript-9.27.tar.gz',
'dir': 'ghostscript-9.26', 'dir': 'ghostscript-9.27',
}, },
'tcl-8.5': { 'tcl-8.5': {
'url': SF_MIRROR+'/project/tcl/Tcl/8.5.19/tcl8519-src.zip', 'url': SF_MIRROR+'/project/tcl/Tcl/8.5.19/tcl8519-src.zip',
@ -83,10 +83,10 @@ libs = {
compilers = { compilers = {
7: { 7: {
2008: { 2010: {
64: { 64: {
'env_version': 'v7.0', 'env_version': 'v7.0',
'vc_version': '2008', 'vc_version': '2010',
'env_flags': '/x64 /xp', 'env_flags': '/x64 /xp',
'inc_dir': 'msvcr90-x64', 'inc_dir': 'msvcr90-x64',
'platform': 'x64', 'platform': 'x64',
@ -94,7 +94,7 @@ compilers = {
}, },
32: { 32: {
'env_version': 'v7.0', 'env_version': 'v7.0',
'vc_version': '2008', 'vc_version': '2010',
'env_flags': '/x86 /xp', 'env_flags': '/x86 /xp',
'inc_dir': 'msvcr90-x32', 'inc_dir': 'msvcr90-x32',
'platform': 'Win32', 'platform': 'Win32',