Merge branch 'master' into pr_2687

This commit is contained in:
Andrew Murray 2019-03-07 23:11:02 +11:00 committed by GitHub
commit fdebb6f2f2
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
41 changed files with 402 additions and 109 deletions

View File

@ -0,0 +1,28 @@
parameters:
name: '' # defaults for any parameters that aren't specified
vmImage: ''
jobs:
- job: ${{ parameters.name }}
pool:
vmImage: ${{ parameters.vmImage }}
strategy:
matrix:
Python37:
python.version: '3.7'
steps:
- task: UsePythonVersion@0
inputs:
versionSpec: '$(python.version)'
architecture: 'x64'
- script: |
python -m pip install --upgrade tox
displayName: 'Install dependencies'
- script: |
tox -e lint
displayName: 'Lint'

View File

@ -0,0 +1,22 @@
parameters:
docker: '' # defaults for any parameters that aren't specified
dockerTag: 'master'
name: ''
vmImage: 'Ubuntu-16.04'
jobs:
- job: ${{ parameters.name }}
pool:
vmImage: ${{ parameters.vmImage }}
steps:
- script: |
docker pull pythonpillow/${{ parameters.docker }}:${{ parameters.dockerTag }}
displayName: 'Docker pull'
- script: |
# The Pillow user in the docker container is UID 1000
sudo chown -R 1000 $(Build.SourcesDirectory)
docker run -v $(Build.SourcesDirectory):/Pillow pythonpillow/${{ parameters.docker }}:${{ parameters.dockerTag }}
displayName: 'Docker build'

View File

@ -5,6 +5,39 @@ Changelog (Pillow)
6.0.0 (unreleased) 6.0.0 (unreleased)
------------------ ------------------
- Python 2.7 support will be removed in Pillow 7.0.0 #3682
[hugovk]
- Removed deprecated VERSION #3624
[hugovk]
- Fix 'BytesWarning: Comparison between bytes and string' in PdfDict #3580
[jdufresne]
- Do not resize in Image.thumbnail if already the destination size #3632
[radarhere]
- Replace .seek() magic numbers with io.SEEK_* constants #3572
[jdufresne]
- Make ContainerIO.isatty() return a bool, not int #3568
[jdufresne]
- Add support for I;16 modes for more transpose operations #3563
[radarhere]
- Deprecate support for PyQt4 and PySide #3655
[hugovk, radarhere]
- Add TIFF compression codecs: LZMA, Zstd, WebP #3555
[cgohlke]
- Fixed pickling of iTXt class with protocol > 1 #3537
[radarhere]
- _util.isPath returns True for pathlib.Path objects #3616
[wbadart]
- Remove unnecessary unittest.main() boilerplate from test files #3631 - Remove unnecessary unittest.main() boilerplate from test files #3631
[jdufresne] [jdufresne]

View File

@ -23,9 +23,10 @@ exclude .codecov.yml
exclude .editorconfig exclude .editorconfig
exclude .landscape.yaml exclude .landscape.yaml
exclude .readthedocs.yml exclude .readthedocs.yml
exclude .travis exclude azure-pipelines.yml
exclude .travis/*
exclude tox.ini exclude tox.ini
global-exclude .git* global-exclude .git*
global-exclude *.pyc global-exclude *.pyc
global-exclude *.so global-exclude *.so
prune .azure-pipelines
prune .travis

View File

@ -88,14 +88,7 @@ Released as needed privately to individual vendors for critical security-related
```bash ```bash
git clone https://github.com/python-pillow/pillow-wheels git clone https://github.com/python-pillow/pillow-wheels
cd pillow-wheels cd pillow-wheels
git submodule init ./update-pillow-tag.sh [[release tag]]
git submodule update Pillow
cd Pillow
git fetch --all
git checkout [[release tag]]
cd ..
git commit -m "Pillow -> 5.2.0" Pillow
git push
``` ```
* [ ] Download distributions from the [Pillow Wheel Builder container](http://a365fff413fe338398b6-1c8a9b3114517dc5fe17b7c3f8c63a43.r19.cf2.rackcdn.com/). * [ ] Download distributions from the [Pillow Wheel Builder container](http://a365fff413fe338398b6-1c8a9b3114517dc5fe17b7c3f8c63a43.r19.cf2.rackcdn.com/).
```bash ```bash

Binary file not shown.

After

Width:  |  Height:  |  Size: 68 KiB

View File

@ -11,8 +11,6 @@ class TestSanity(PillowTestCase):
# Make sure we have the binary extension # Make sure we have the binary extension
PIL.Image.core.new("L", (100, 100)) PIL.Image.core.new("L", (100, 100))
self.assertEqual(PIL.Image.VERSION[:3], '1.1')
# Create an image and do stuff with it. # Create an image and do stuff with it.
im = PIL.Image.new("1", (100, 100)) im = PIL.Image.new("1", (100, 100))
self.assertEqual((im.mode, im.size), ('1', (100, 100))) self.assertEqual((im.mode, im.size), ('1', (100, 100)))

View File

@ -16,7 +16,7 @@ class TestFileContainer(PillowTestCase):
im = hopper() im = hopper()
container = ContainerIO.ContainerIO(im, 0, 0) container = ContainerIO.ContainerIO(im, 0, 0)
self.assertEqual(container.isatty(), 0) self.assertFalse(container.isatty())
def test_seek_mode_0(self): def test_seek_mode_0(self):
# Arrange # Arrange

View File

@ -12,11 +12,11 @@ YA_EXTRA_DIR = "Tests/images/msp"
class TestFileMsp(PillowTestCase): class TestFileMsp(PillowTestCase):
def test_sanity(self): def test_sanity(self):
file = self.tempfile("temp.msp") test_file = self.tempfile("temp.msp")
hopper("1").save(file) hopper("1").save(test_file)
im = Image.open(file) im = Image.open(test_file)
im.load() im.load()
self.assertEqual(im.mode, "1") self.assertEqual(im.mode, "1")
self.assertEqual(im.size, (128, 128)) self.assertEqual(im.size, (128, 128))

View File

@ -14,12 +14,14 @@ class TestFilePpm(PillowTestCase):
self.assertEqual(im.mode, "RGB") self.assertEqual(im.mode, "RGB")
self.assertEqual(im.size, (128, 128)) self.assertEqual(im.size, (128, 128))
self.assertEqual(im.format, "PPM") self.assertEqual(im.format, "PPM")
self.assertEqual(im.get_format_mimetype(), "image/x-portable-pixmap")
def test_16bit_pgm(self): def test_16bit_pgm(self):
im = Image.open('Tests/images/16_bit_binary.pgm') im = Image.open('Tests/images/16_bit_binary.pgm')
im.load() im.load()
self.assertEqual(im.mode, 'I') self.assertEqual(im.mode, 'I')
self.assertEqual(im.size, (20, 100)) self.assertEqual(im.size, (20, 100))
self.assertEqual(im.get_format_mimetype(), "image/x-portable-graymap")
tgt = Image.open('Tests/images/16_bit_binary_pgm.png') tgt = Image.open('Tests/images/16_bit_binary_pgm.png')
self.assert_image_equal(im, tgt) self.assert_image_equal(im, tgt)
@ -49,3 +51,16 @@ class TestFilePpm(PillowTestCase):
with self.assertRaises(IOError): with self.assertRaises(IOError):
Image.open('Tests/images/negative_size.ppm') Image.open('Tests/images/negative_size.ppm')
def test_mimetypes(self):
path = self.tempfile('temp.pgm')
with open(path, 'w') as f:
f.write("P4\n128 128\n255")
im = Image.open(path)
self.assertEqual(im.get_format_mimetype(), "image/x-portable-bitmap")
with open(path, 'w') as f:
f.write("PyCMYK\n128 128\n255")
im = Image.open(path)
self.assertEqual(im.get_format_mimetype(), "image/x-portable-anymap")

View File

@ -1,4 +1,5 @@
from .helper import PillowTestCase, hopper from .helper import PillowTestCase, hopper
from PIL import Image
class TestImageThumbnail(PillowTestCase): class TestImageThumbnail(PillowTestCase):
@ -35,3 +36,14 @@ class TestImageThumbnail(PillowTestCase):
im = hopper().resize((128, 128)) im = hopper().resize((128, 128))
im.thumbnail((100, 100)) im.thumbnail((100, 100))
self.assert_image(im, im.mode, (100, 100)) self.assert_image(im, im.mode, (100, 100))
def test_no_resize(self):
# Check that draft() can resize the image to the destination size
im = Image.open("Tests/images/hopper.jpg")
im.draft(None, (64, 64))
self.assertEqual(im.size, (64, 64))
# Test thumbnail(), where only draft() is necessary to resize the image
im = Image.open("Tests/images/hopper.jpg")
im.thumbnail((64, 64))
self.assert_image(im, im.mode, (64, 64))

View File

@ -7,10 +7,9 @@ from PIL.Image import (FLIP_LEFT_RIGHT, FLIP_TOP_BOTTOM, ROTATE_90, ROTATE_180,
class TestImageTranspose(PillowTestCase): class TestImageTranspose(PillowTestCase):
hopper = { hopper = {mode: helper.hopper(mode).crop((0, 0, 121, 127)).copy() for mode in [
'L': helper.hopper('L').crop((0, 0, 121, 127)).copy(), 'L', 'RGB', 'I;16', 'I;16L', 'I;16B'
'RGB': helper.hopper('RGB').crop((0, 0, 121, 127)).copy(), ]}
}
def test_flip_left_right(self): def test_flip_left_right(self):
def transpose(mode): def transpose(mode):
@ -25,7 +24,7 @@ class TestImageTranspose(PillowTestCase):
self.assertEqual(im.getpixel((1, y-2)), out.getpixel((x-2, y-2))) self.assertEqual(im.getpixel((1, y-2)), out.getpixel((x-2, y-2)))
self.assertEqual(im.getpixel((x-2, y-2)), out.getpixel((1, y-2))) self.assertEqual(im.getpixel((x-2, y-2)), out.getpixel((1, y-2)))
for mode in ("L", "RGB"): for mode in ("L", "RGB", "I;16", "I;16L", "I;16B"):
transpose(mode) transpose(mode)
def test_flip_top_bottom(self): def test_flip_top_bottom(self):
@ -41,7 +40,7 @@ class TestImageTranspose(PillowTestCase):
self.assertEqual(im.getpixel((1, y-2)), out.getpixel((1, 1))) self.assertEqual(im.getpixel((1, y-2)), out.getpixel((1, 1)))
self.assertEqual(im.getpixel((x-2, y-2)), out.getpixel((x-2, 1))) self.assertEqual(im.getpixel((x-2, y-2)), out.getpixel((x-2, 1)))
for mode in ("L", "RGB"): for mode in ("L", "RGB", "I;16", "I;16L", "I;16B"):
transpose(mode) transpose(mode)
def test_rotate_90(self): def test_rotate_90(self):
@ -73,7 +72,7 @@ class TestImageTranspose(PillowTestCase):
self.assertEqual(im.getpixel((1, y-2)), out.getpixel((x-2, 1))) self.assertEqual(im.getpixel((1, y-2)), out.getpixel((x-2, 1)))
self.assertEqual(im.getpixel((x-2, y-2)), out.getpixel((1, 1))) self.assertEqual(im.getpixel((x-2, y-2)), out.getpixel((1, 1)))
for mode in ("L", "RGB"): for mode in ("L", "RGB", "I;16", "I;16L", "I;16B"):
transpose(mode) transpose(mode)
def test_rotate_270(self): def test_rotate_270(self):

View File

@ -1,7 +1,16 @@
from .helper import PillowTestCase, hopper from .helper import PillowTestCase, hopper
from PIL import ImageQt import warnings
deprecated = False
with warnings.catch_warnings():
warnings.filterwarnings("error", category=DeprecationWarning)
try:
from PIL import ImageQt
except DeprecationWarning:
deprecated = True
warnings.filterwarnings("ignore", category=DeprecationWarning)
from PIL import ImageQt
if ImageQt.qt_is_installed: if ImageQt.qt_is_installed:
from PIL.ImageQt import qRgba from PIL.ImageQt import qRgba
@ -78,3 +87,6 @@ class TestImageQt(PillowQtTestCase, PillowTestCase):
def test_image(self): def test_image(self):
for mode in ('1', 'RGB', 'RGBA', 'L', 'P'): for mode in ('1', 'RGB', 'RGBA', 'L', 'P'):
ImageQt.ImageQt(hopper(mode)) ImageQt.ImageQt(hopper(mode))
def test_deprecated(self):
self.assertEqual(ImageQt.qt_version in ["4", "side"], deprecated)

View File

@ -67,9 +67,13 @@ class TestPickle(PillowTestCase):
"Tests/images/non_zero_bb.png", "Tests/images/non_zero_bb.png",
"Tests/images/non_zero_bb_scale2.png", "Tests/images/non_zero_bb_scale2.png",
"Tests/images/p_trns_single.png", "Tests/images/p_trns_single.png",
"Tests/images/pil123p.png" "Tests/images/pil123p.png",
"Tests/images/itxt_chunks.png"
]: ]:
self.helper_pickle_string(pickle, test_file=test_file) for protocol in range(0, pickle.HIGHEST_PROTOCOL + 1):
self.helper_pickle_string(pickle,
protocol=protocol,
test_file=test_file)
def test_pickle_l_mode(self): def test_pickle_l_mode(self):
# Arrange # Arrange

View File

@ -1,4 +1,4 @@
from .helper import PillowTestCase from .helper import unittest, PillowTestCase
from PIL import _util from PIL import _util
@ -35,6 +35,18 @@ class TestUtil(PillowTestCase):
# Assert # Assert
self.assertTrue(it_is) self.assertTrue(it_is)
@unittest.skipIf(not _util.py36, 'os.path support for Paths added in 3.6')
def test_path_obj_is_path(self):
# Arrange
from pathlib import Path
test_path = Path('filename.ext')
# Act
it_is = _util.isPath(test_path)
# Assert
self.assertTrue(it_is)
def test_is_not_path(self): def test_is_not_path(self):
# Arrange # Arrange
filename = self.tempfile("temp.ext") filename = self.tempfile("temp.ext")

67
azure-pipelines.yml Normal file
View File

@ -0,0 +1,67 @@
# Python package
# Create and test a Python package on multiple Python versions.
# Add steps that analyze code, save the dist with the build record,
# publish to a PyPI-compatible index, and more:
# https://docs.microsoft.com/azure/devops/pipelines/languages/python
jobs:
- template: .azure-pipelines/jobs/lint.yml
parameters:
name: Lint
vmImage: 'Ubuntu-16.04'
- template: .azure-pipelines/jobs/test-docker.yml
parameters:
docker: 'alpine'
name: 'alpine'
- template: .azure-pipelines/jobs/test-docker.yml
parameters:
docker: 'arch'
name: 'arch'
- template: .azure-pipelines/jobs/test-docker.yml
parameters:
docker: 'ubuntu-trusty-x86'
name: 'ubuntu_trusty_x86'
- template: .azure-pipelines/jobs/test-docker.yml
parameters:
docker: 'ubuntu-xenial-amd64'
name: 'ubuntu_xenial_amd64'
- template: .azure-pipelines/jobs/test-docker.yml
parameters:
docker: 'debian-stretch-x86'
name: 'debian_stretch_x86'
- template: .azure-pipelines/jobs/test-docker.yml
parameters:
docker: 'centos-6-amd64'
name: 'centos_6_amd64'
- template: .azure-pipelines/jobs/test-docker.yml
parameters:
docker: 'centos-7-amd64'
name: 'centos_7_amd64'
- template: .azure-pipelines/jobs/test-docker.yml
parameters:
docker: 'amazon-1-amd64'
name: 'amazon_1_amd64'
- template: .azure-pipelines/jobs/test-docker.yml
parameters:
docker: 'amazon-2-amd64'
name: 'amazon_2_amd64'
- template: .azure-pipelines/jobs/test-docker.yml
parameters:
docker: 'fedora-28-amd64'
name: 'fedora_28_amd64'
- template: .azure-pipelines/jobs/test-docker.yml
parameters:
docker: 'fedora-29-amd64'
name: 'fedora_29_amd64'

View File

@ -12,6 +12,27 @@ Deprecated features
Below are features which are considered deprecated. Where appropriate, Below are features which are considered deprecated. Where appropriate,
a ``DeprecationWarning`` is issued. a ``DeprecationWarning`` is issued.
Python 2.7
~~~~~~~~~~
.. deprecated:: 6.0.0
Python 2.7 reaches end-of-life on 2020-01-01.
Pillow 7.0.0 will be released on 2020-01-01 and will drop support for Python 2.7, making
Pillow 6.x the last series to support Python 2.
PyQt4 and PySide
~~~~~~~~~~~~~~~~
.. deprecated:: 6.0.0
Qt 4 reached end-of-life on 2015-12-19. Its Python bindings are also EOL: PyQt4 since
2018-08-31 and PySide since 2015-10-14.
Support for PyQt4 and PySide has been deprecated from ``ImageQt`` and will be removed in
a future version. Please upgrade to PyQt5 or PySide2.
PIL.*ImagePlugin.__version__ attributes PIL.*ImagePlugin.__version__ attributes
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
@ -50,13 +71,12 @@ a ``DeprecationWarning``:
Setting the size of a TIFF image directly is deprecated, and will Setting the size of a TIFF image directly is deprecated, and will
be removed in a future version. Use the resize method instead. be removed in a future version. Use the resize method instead.
PILLOW_VERSION and VERSION constants PILLOW_VERSION constant
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ ~~~~~~~~~~~~~~~~~~~~~~~
.. deprecated:: 5.2.0 .. deprecated:: 5.2.0
Two version constants ``VERSION`` (the old PIL version, always 1.1.7) and ``PILLOW_VERSION`` has been deprecated and will be removed in the next
``PILLOW_VERSION`` have been deprecated and will be removed in the next
major release. Use ``__version__`` instead. major release. Use ``__version__`` instead.
Removed features Removed features
@ -65,6 +85,14 @@ Removed features
Deprecated features are only removed in major releases after an appropriate Deprecated features are only removed in major releases after an appropriate
period of deprecation has passed. period of deprecation has passed.
VERSION constant
~~~~~~~~~~~~~~~~
*Removed in version 6.0.0.*
``VERSION`` (the old PIL version, always 1.1.7) has been removed. Use
``__version__`` instead.
Undocumented ImageOps functions Undocumented ImageOps functions
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

View File

@ -15,21 +15,23 @@ Notes
.. note:: Pillow is supported on the following Python versions .. note:: Pillow is supported on the following Python versions
+--------------------+-------+-------+-------+-------+-------+-------+-------+-------+-------+-------+ +---------------------+-------+-------+-------+-------+-------+-------+-------+-------+-------+-------+
|**Python** |**2.4**|**2.5**|**2.6**|**2.7**|**3.2**|**3.3**|**3.4**|**3.5**|**3.6**|**3.7**| |**Python** |**2.4**|**2.5**|**2.6**|**2.7**|**3.2**|**3.3**|**3.4**|**3.5**|**3.6**|**3.7**|
+--------------------+-------+-------+-------+-------+-------+-------+-------+-------+-------+-------+ +---------------------+-------+-------+-------+-------+-------+-------+-------+-------+-------+-------+
|Pillow < 2.0.0 | Yes | Yes | Yes | Yes | | | | | | | |Pillow < 2.0.0 | Yes | Yes | Yes | Yes | | | | | | |
+--------------------+-------+-------+-------+-------+-------+-------+-------+-------+-------+-------+ +---------------------+-------+-------+-------+-------+-------+-------+-------+-------+-------+-------+
|Pillow 2.x - 3.x | | | Yes | Yes | Yes | Yes | Yes | Yes | | | |Pillow 2.x - 3.x | | | Yes | Yes | Yes | Yes | Yes | Yes | | |
+--------------------+-------+-------+-------+-------+-------+-------+-------+-------+-------+-------+ +---------------------+-------+-------+-------+-------+-------+-------+-------+-------+-------+-------+
|Pillow 4.x | | | | Yes | | Yes | Yes | Yes | Yes | | |Pillow 4.x | | | | Yes | | Yes | Yes | Yes | Yes | |
+--------------------+-------+-------+-------+-------+-------+-------+-------+-------+-------+-------+ +---------------------+-------+-------+-------+-------+-------+-------+-------+-------+-------+-------+
|Pillow 5.0.x - 5.1.x | | | | Yes | | | Yes | Yes | Yes | | |Pillow 5.0.x - 5.1.x | | | | Yes | | | Yes | Yes | Yes | |
+--------------------+-------+-------+-------+-------+-------+-------+-------+-------+-------+-------+ +---------------------+-------+-------+-------+-------+-------+-------+-------+-------+-------+-------+
|Pillow 5.2.x - 5.4.x | | | | Yes | | | Yes | Yes | Yes | Yes | |Pillow 5.2.x - 5.4.x | | | | Yes | | | Yes | Yes | Yes | Yes |
+--------------------+-------+-------+-------+-------+-------+-------+-------+-------+-------+-------+ +---------------------+-------+-------+-------+-------+-------+-------+-------+-------+-------+-------+
|Pillow >= 6.0.0 | | | | Yes | | | | Yes | Yes | Yes | |Pillow 6.x | | | | Yes | | | | Yes | Yes | Yes |
+--------------------+-------+-------+-------+-------+-------+-------+-------+-------+-------+-------+ +---------------------+-------+-------+-------+-------+-------+-------+-------+-------+-------+-------+
|Pillow >= 7.0.0 | | | | | | | | Yes | Yes | Yes |
+---------------------+-------+-------+-------+-------+-------+-------+-------+-------+-------+-------+
Basic Installation Basic Installation
------------------ ------------------

View File

@ -7,6 +7,12 @@
The :py:mod:`ImageQt` module contains support for creating PyQt4, PyQt5, PySide or The :py:mod:`ImageQt` module contains support for creating PyQt4, PyQt5, PySide or
PySide2 QImage objects from PIL images. PySide2 QImage objects from PIL images.
Qt 4 reached end-of-life on 2015-12-19. Its Python bindings are also EOL: PyQt4 since
2018-08-31 and PySide since 2015-10-14.
Support for PyQt4 and PySide is deprecated since Pillow 6.0.0 and will be removed in a
future version. Please upgrade to PyQt5 or PySide2.
.. versionadded:: 1.1.6 .. versionadded:: 1.1.6
.. py:class:: ImageQt.ImageQt(image) .. py:class:: ImageQt.ImageQt(image)

View File

@ -26,12 +26,38 @@ Several undocumented functions in ``ImageOps`` were deprecated in Pillow 4.3.0 (
and have now been removed: ``gaussian_blur``, ``gblur``, ``unsharp_mask``, ``usm`` and and have now been removed: ``gaussian_blur``, ``gblur``, ``unsharp_mask``, ``usm`` and
``box_blur``. Use the equivalent operations in ``ImageFilter`` instead. ``box_blur``. Use the equivalent operations in ``ImageFilter`` instead.
Removed deprecated VERSION
^^^^^^^^^^^^^^^^^^^^^^^^^^
``VERSION`` (the old PIL version, always 1.1.7) has been removed. Use ``__version__``
instead.
API Changes API Changes
=========== ===========
Deprecations Deprecations
^^^^^^^^^^^^ ^^^^^^^^^^^^
Python 2.7
~~~~~~~~~~
Python 2.7 reaches end-of-life on 2020-01-01.
Pillow 7.0.0 will be released on 2020-01-01 and will drop support for Python 2.7, making
Pillow 6.x the last series to support Python 2.
PyQt4 and PySide
~~~~~~~~~~~~~~~~
Qt 4 reached end-of-life on 2015-12-19. Its Python bindings are also EOL: PyQt4 since
2018-08-31 and PySide since 2015-10-14.
Support for PyQt4 and PySide has been deprecated from ``ImageQt`` and will be removed in
a future version. Please upgrade to PyQt5 or PySide2.
PIL.*ImagePlugin.__version__ attributes
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
These version constants have been deprecated and will be removed in a future These version constants have been deprecated and will be removed in a future
version. version.

View File

@ -18,6 +18,8 @@
# A file object that provides read access to a part of an existing # A file object that provides read access to a part of an existing
# file (for example a TAR file). # file (for example a TAR file).
import io
class ContainerIO(object): class ContainerIO(object):
@ -39,9 +41,9 @@ class ContainerIO(object):
# Always false. # Always false.
def isatty(self): def isatty(self):
return 0 return False
def seek(self, offset, mode=0): def seek(self, offset, mode=io.SEEK_SET):
""" """
Move file pointer. Move file pointer.

View File

@ -104,7 +104,7 @@ def Ghostscript(tile, size, fp, scale=1):
# Copy whole file to read in Ghostscript # Copy whole file to read in Ghostscript
with open(infile_temp, 'wb') as f: with open(infile_temp, 'wb') as f:
# fetch length of fp # fetch length of fp
fp.seek(0, 2) fp.seek(0, io.SEEK_END)
fsize = fp.tell() fsize = fp.tell()
# ensure start position # ensure start position
# go back # go back
@ -169,7 +169,7 @@ class PSFile(object):
self.fp = fp self.fp = fp
self.char = None self.char = None
def seek(self, offset, whence=0): def seek(self, offset, whence=io.SEEK_SET):
self.char = None self.char = None
self.fp.seek(offset, whence) self.fp.seek(offset, whence)
@ -312,7 +312,7 @@ class EpsImageFile(ImageFile.ImageFile):
if s[:4] == b"%!PS": if s[:4] == b"%!PS":
# for HEAD without binary preview # for HEAD without binary preview
fp.seek(0, 2) fp.seek(0, io.SEEK_END)
length = fp.tell() length = fp.tell()
offset = 0 offset = 0
elif i32(s[0:4]) == 0xC6D3D0C5: elif i32(s[0:4]) == 0xC6D3D0C5:

View File

@ -604,16 +604,16 @@ def _save_netpbm(im, fp, filename):
import os import os
from subprocess import Popen, check_call, PIPE, CalledProcessError from subprocess import Popen, check_call, PIPE, CalledProcessError
file = im._dump() tempfile = im._dump()
with open(filename, 'wb') as f: with open(filename, 'wb') as f:
if im.mode != "RGB": if im.mode != "RGB":
with open(os.devnull, 'wb') as devnull: with open(os.devnull, 'wb') as devnull:
check_call(["ppmtogif", file], stdout=f, stderr=devnull) check_call(["ppmtogif", tempfile], stdout=f, stderr=devnull)
else: else:
# Pipe ppmquant output into ppmtogif # Pipe ppmquant output into ppmtogif
# "ppmquant 256 %s | ppmtogif > %s" % (file, filename) # "ppmquant 256 %s | ppmtogif > %s" % (tempfile, filename)
quant_cmd = ["ppmquant", "256", file] quant_cmd = ["ppmquant", "256", tempfile]
togif_cmd = ["ppmtogif"] togif_cmd = ["ppmtogif"]
with open(os.devnull, 'wb') as devnull: with open(os.devnull, 'wb') as devnull:
quant_proc = Popen(quant_cmd, stdout=PIPE, stderr=devnull) quant_proc = Popen(quant_cmd, stdout=PIPE, stderr=devnull)
@ -632,7 +632,7 @@ def _save_netpbm(im, fp, filename):
raise CalledProcessError(retcode, togif_cmd) raise CalledProcessError(retcode, togif_cmd)
try: try:
os.unlink(file) os.unlink(tempfile)
except OSError: except OSError:
pass pass

View File

@ -195,7 +195,7 @@ class IcnsFile(object):
i += HEADERSIZE i += HEADERSIZE
blocksize -= HEADERSIZE blocksize -= HEADERSIZE
dct[sig] = (i, blocksize) dct[sig] = (i, blocksize)
fobj.seek(blocksize, 1) fobj.seek(blocksize, io.SEEK_CUR)
i += blocksize i += blocksize
def itersizes(self): def itersizes(self):

View File

@ -40,10 +40,10 @@ if False:
from PyQt5.QtGui import QImage, QPixmap from PyQt5.QtGui import QImage, QPixmap
# VERSION is deprecated and will be removed in Pillow 6.0.0. # VERSION was removed in Pillow 6.0.0.
# PILLOW_VERSION is deprecated and will be removed after that. # PILLOW_VERSION is deprecated and will be removed in Pillow 7.0.0.
# Use __version__ instead. # Use __version__ instead.
from . import VERSION, PILLOW_VERSION, __version__, _plugins from . import PILLOW_VERSION, __version__, _plugins
from ._util import py3 from ._util import py3
import logging import logging
@ -76,8 +76,7 @@ except ImportError:
from collections import Callable from collections import Callable
# Silence warnings # Silence warning
assert VERSION
assert PILLOW_VERSION assert PILLOW_VERSION
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
@ -680,8 +679,7 @@ class Image(object):
def __eq__(self, other): def __eq__(self, other):
# type: (object) -> bool # type: (object) -> bool
return (isinstance(other, Image) and return (self.__class__ is other.__class__ and
self.__class__.__name__ == other.__class__.__name__ and
self.mode == other.mode and self.mode == other.mode and
self.size == other.size and self.size == other.size and
self.info == other.info and self.info == other.info and
@ -709,8 +707,7 @@ class Image(object):
:returns: png version of the image as bytes :returns: png version of the image as bytes
""" """
from io import BytesIO b = io.BytesIO()
b = BytesIO()
self.save(b, 'PNG') self.save(b, 'PNG')
return b.getvalue() return b.getvalue()
@ -2138,10 +2135,10 @@ class Image(object):
debugging purposes. debugging purposes.
On Unix platforms, this method saves the image to a temporary On Unix platforms, this method saves the image to a temporary
PPM file, and calls either the **xv** utility or the **display** PPM file, and calls the **display**, **eog** or **xv**
utility, depending on which one can be found. utility, depending on which one can be found.
On macOS, this method saves the image to a temporary BMP file, and On macOS, this method saves the image to a temporary PNG file, and
opens it with the native Preview application. opens it with the native Preview application.
On Windows, it saves the image to a temporary BMP file, and uses On Windows, it saves the image to a temporary BMP file, and uses
@ -2251,11 +2248,12 @@ class Image(object):
self.draft(None, size) self.draft(None, size)
if self.size != size:
im = self.resize(size, resample) im = self.resize(size, resample)
self.im = im.im self.im = im.im
self.mode = im.mode
self._size = size self._size = size
self.mode = self.im.mode
self.readonly = 0 self.readonly = 0
self.pyaccess = None self.pyaccess = None

View File

@ -950,5 +950,5 @@ def versions():
return ( return (
VERSION, core.littlecms_version, VERSION, core.littlecms_version,
sys.version.split()[0], Image.VERSION sys.version.split()[0], Image.__version__
) )

View File

@ -525,7 +525,7 @@ def _save(im, fp, tile, bufsize=0):
for e, b, o, a in tile: for e, b, o, a in tile:
e = Image._getencoder(im.mode, e, a, im.encoderconfig) e = Image._getencoder(im.mode, e, a, im.encoderconfig)
if o > 0: if o > 0:
fp.seek(o, 0) fp.seek(o)
e.setimage(im.im, b) e.setimage(im.im, b)
if e.pushes_fd: if e.pushes_fd:
e.setfd(fp) e.setfd(fp)
@ -544,7 +544,7 @@ def _save(im, fp, tile, bufsize=0):
for e, b, o, a in tile: for e, b, o, a in tile:
e = Image._getencoder(im.mode, e, a, im.encoderconfig) e = Image._getencoder(im.mode, e, a, im.encoderconfig)
if o > 0: if o > 0:
fp.seek(o, 0) fp.seek(o)
e.setimage(im.im, b) e.setimage(im.im, b)
if e.pushes_fd: if e.pushes_fd:
e.setfd(fp) e.setfd(fp)

View File

@ -28,7 +28,7 @@ VERBOSE = 0
def _isconstant(v): def _isconstant(v):
return isinstance(v, int) or isinstance(v, float) return isinstance(v, (int, float))
class _Operand(object): class _Operand(object):

View File

@ -20,6 +20,7 @@ from . import Image
from ._util import isPath, py3 from ._util import isPath, py3
from io import BytesIO from io import BytesIO
import sys import sys
import warnings
qt_versions = [ qt_versions = [
['5', 'PyQt5'], ['5', 'PyQt5'],
@ -27,6 +28,12 @@ qt_versions = [
['4', 'PyQt4'], ['4', 'PyQt4'],
['side', 'PySide'] ['side', 'PySide']
] ]
WARNING_TEXT = (
"Support for EOL {} is deprecated and will be removed in a future version. "
"Please upgrade to PyQt5 or PySide2."
)
# If a version has already been imported, attempt it first # If a version has already been imported, attempt it first
qt_versions.sort(key=lambda qt_version: qt_version[1] in sys.modules, qt_versions.sort(key=lambda qt_version: qt_version[1] in sys.modules,
reverse=True) reverse=True)
@ -41,9 +48,13 @@ for qt_version, qt_module in qt_versions:
elif qt_module == 'PyQt4': elif qt_module == 'PyQt4':
from PyQt4.QtGui import QImage, qRgba, QPixmap from PyQt4.QtGui import QImage, qRgba, QPixmap
from PyQt4.QtCore import QBuffer, QIODevice from PyQt4.QtCore import QBuffer, QIODevice
warnings.warn(WARNING_TEXT.format(qt_module), DeprecationWarning)
elif qt_module == 'PySide': elif qt_module == 'PySide':
from PySide.QtGui import QImage, qRgba, QPixmap from PySide.QtGui import QImage, qRgba, QPixmap
from PySide.QtCore import QBuffer, QIODevice from PySide.QtCore import QBuffer, QIODevice
warnings.warn(WARNING_TEXT.format(qt_module), DeprecationWarning)
except (ImportError, RuntimeError): except (ImportError, RuntimeError):
continue continue
qt_is_installed = True qt_is_installed = True
@ -67,7 +78,7 @@ def fromqimage(im):
""" """
buffer = QBuffer() buffer = QBuffer()
buffer.open(QIODevice.ReadWrite) buffer.open(QIODevice.ReadWrite)
# preserve alha channel with png # preserve alpha channel with png
# otherwise ppm is more friendly with Image.open # otherwise ppm is more friendly with Image.open
if im.hasAlphaChannel(): if im.hasAlphaChannel():
im.save(buffer, 'png') im.save(buffer, 'png')

View File

@ -193,9 +193,9 @@ class Jpeg2KImageFile(ImageFile.ImageFile):
fd = -1 fd = -1
try: try:
pos = self.fp.tell() pos = self.fp.tell()
self.fp.seek(0, 2) self.fp.seek(0, io.SEEK_END)
length = self.fp.tell() length = self.fp.tell()
self.fp.seek(pos, 0) self.fp.seek(pos)
except Exception: except Exception:
length = -1 length = -1

View File

@ -16,6 +16,7 @@
# See the README file for information on usage and redistribution. # See the README file for information on usage and redistribution.
# #
import io
from . import Image, FontFile from . import Image, FontFile
from ._binary import i8, i16le as l16, i32le as l32, i16be as b16, i32be as b32 from ._binary import i8, i16le as l16, i32le as l32, i16be as b16, i32be as b32
@ -117,7 +118,7 @@ class PcfFontFile(FontFile.FontFile):
for i in range(nprops): for i in range(nprops):
p.append((i32(fp.read(4)), i8(fp.read(1)), i32(fp.read(4)))) p.append((i32(fp.read(4)), i8(fp.read(1)), i32(fp.read(4))))
if nprops & 3: if nprops & 3:
fp.seek(4 - (nprops & 3), 1) # pad fp.seek(4 - (nprops & 3), io.SEEK_CUR) # pad
data = fp.read(i32(fp.read(4))) data = fp.read(i32(fp.read(4)))

View File

@ -25,6 +25,7 @@
# See the README file for information on usage and redistribution. # See the README file for information on usage and redistribution.
# #
import io
import logging import logging
from . import Image, ImageFile, ImagePalette from . import Image, ImageFile, ImagePalette
from ._binary import i8, i16le as i16, o8, o16le as o16 from ._binary import i8, i16le as i16, o8, o16le as o16
@ -82,7 +83,7 @@ class PcxImageFile(ImageFile.ImageFile):
elif version == 5 and bits == 8 and planes == 1: elif version == 5 and bits == 8 and planes == 1:
mode = rawmode = "L" mode = rawmode = "L"
# FIXME: hey, this doesn't work with the incremental loader !!! # FIXME: hey, this doesn't work with the incremental loader !!!
self.fp.seek(-769, 2) self.fp.seek(-769, io.SEEK_END)
s = self.fp.read(769) s = self.fp.read(769)
if len(s) == 769 and i8(s[0]) == 12: if len(s) == 769 and i8(s[0]) == 12:
# check if the palette is linear greyscale # check if the palette is linear greyscale

View File

@ -269,14 +269,9 @@ class PdfDict(UserDict):
else: else:
self.__dict__[key] = value self.__dict__[key] = value
else: else:
if isinstance(key, str): self[key.encode("us-ascii")] = value
key = key.encode("us-ascii")
self[key] = value
def __getattr__(self, key): def __getattr__(self, key):
try:
value = self[key]
except KeyError:
try: try:
value = self[key.encode("us-ascii")] value = self[key.encode("us-ascii")]
except KeyError: except KeyError:
@ -361,8 +356,7 @@ def pdf_repr(x):
return b"false" return b"false"
elif x is None: elif x is None:
return b"null" return b"null"
elif (isinstance(x, PdfName) or isinstance(x, PdfDict) or elif isinstance(x, (PdfName, PdfDict, PdfArray, PdfBinary)):
isinstance(x, PdfArray) or isinstance(x, PdfBinary)):
return bytes(x) return bytes(x)
elif isinstance(x, int): elif isinstance(x, int):
return str(x).encode("us-ascii") return str(x).encode("us-ascii")

View File

@ -193,7 +193,7 @@ class iTXt(str):
""" """
@staticmethod @staticmethod
def __new__(cls, text, lang, tkey): def __new__(cls, text, lang=None, tkey=None):
""" """
:param cls: the class to use when creating the instance :param cls: the class to use when creating the instance
:param text: value for this key :param text: value for this key

View File

@ -70,7 +70,14 @@ class PpmImageFile(ImageFile.ImageFile):
s = self.fp.read(1) s = self.fp.read(1)
if s != b"P": if s != b"P":
raise SyntaxError("not a PPM file") raise SyntaxError("not a PPM file")
mode = MODES[self._token(s)] magic_number = self._token(s)
mode = MODES[magic_number]
self.custom_mimetype = {
b"P4": "image/x-portable-bitmap",
b"P5": "image/x-portable-graymap",
b"P6": "image/x-portable-pixmap",
}.get(magic_number)
if mode == "1": if mode == "1":
self.mode = "1" self.mode = "1"
@ -158,3 +165,5 @@ Image.register_open(PpmImageFile.format, PpmImageFile, _accept)
Image.register_save(PpmImageFile.format, _save) Image.register_save(PpmImageFile.format, _save)
Image.register_extensions(PpmImageFile.format, [".pbm", ".pgm", ".ppm"]) Image.register_extensions(PpmImageFile.format, [".pbm", ".pgm", ".ppm"])
Image.register_mime(PpmImageFile.format, "image/x-portable-anymap")

View File

@ -20,6 +20,7 @@
# PIL.__version__ instead. # PIL.__version__ instead.
__version__ = "0.4" __version__ = "0.4"
import io
from . import Image, ImageFile, ImagePalette from . import Image, ImageFile, ImagePalette
from ._binary import i8, i16be as i16, i32be as i32 from ._binary import i8, i16be as i16, i32be as i32
@ -216,12 +217,12 @@ def _layerinfo(file):
if size: if size:
length = i32(read(4)) length = i32(read(4))
if length: if length:
file.seek(length - 16, 1) file.seek(length - 16, io.SEEK_CUR)
combined += length + 4 combined += length + 4
length = i32(read(4)) length = i32(read(4))
if length: if length:
file.seek(length, 1) file.seek(length, io.SEEK_CUR)
combined += length + 4 combined += length + 4
length = i8(read(1)) length = i8(read(1))
@ -231,7 +232,7 @@ def _layerinfo(file):
name = read(length).decode('latin-1', 'replace') name = read(length).decode('latin-1', 'replace')
combined += length + 1 combined += length + 1
file.seek(size - combined, 1) file.seek(size - combined, io.SEEK_CUR)
layers.append((name, mode, (x0, y0, x1, y1))) layers.append((name, mode, (x0, y0, x1, y1)))
# get tiles # get tiles

View File

@ -14,6 +14,7 @@
# See the README file for information on usage and redistribution. # See the README file for information on usage and redistribution.
# #
import io
import sys import sys
from . import ContainerIO from . import ContainerIO
@ -51,7 +52,7 @@ class TarIO(ContainerIO.ContainerIO):
if file == name: if file == name:
break break
self.fh.seek((size + 511) & (~511), 1) self.fh.seek((size + 511) & (~511), io.SEEK_CUR)
# Open region # Open region
ContainerIO.ContainerIO.__init__(self, self.fh, self.fh.tell(), size) ContainerIO.ContainerIO.__init__(self, self.fh, self.fh.tell(), size)

View File

@ -135,6 +135,9 @@ COMPRESSION_INFO = {
32946: "tiff_deflate", 32946: "tiff_deflate",
34676: "tiff_sgilog", 34676: "tiff_sgilog",
34677: "tiff_sgilog24", 34677: "tiff_sgilog24",
34925: "lzma",
50000: "zstd",
50001: "webp",
} }
COMPRESSION_INFO_REV = {v: k for k, v in COMPRESSION_INFO.items()} COMPRESSION_INFO_REV = {v: k for k, v in COMPRESSION_INFO.items()}
@ -423,7 +426,7 @@ class ImageFileDirectory_v2(MutableMapping):
ifd = ImageFileDirectory_v2() ifd = ImageFileDirectory_v2()
ifd[key] = 'Some Data' ifd[key] = 'Some Data'
ifd.tagtype[key] = 2 ifd.tagtype[key] = TiffTags.ASCII
print(ifd[key]) print(ifd[key])
'Some Data' 'Some Data'
@ -557,7 +560,7 @@ class ImageFileDirectory_v2(MutableMapping):
if info.type: if info.type:
self.tagtype[tag] = info.type self.tagtype[tag] = info.type
else: else:
self.tagtype[tag] = 7 self.tagtype[tag] = TiffTags.UNDEFINED
if all(isinstance(v, IFDRational) for v in values): if all(isinstance(v, IFDRational) for v in values):
self.tagtype[tag] = TiffTags.RATIONAL self.tagtype[tag] = TiffTags.RATIONAL
elif all(isinstance(v, int) for v in values): elif all(isinstance(v, int) for v in values):
@ -872,7 +875,7 @@ class ImageFileDirectory_v1(ImageFileDirectory_v2):
ifd = ImageFileDirectory_v1() ifd = ImageFileDirectory_v1()
ifd[key] = 'Some Data' ifd[key] = 'Some Data'
ifd.tagtype[key] = 2 ifd.tagtype[key] = TiffTags.ASCII
print(ifd[key]) print(ifd[key])
('Some Data',) ('Some Data',)
@ -1436,7 +1439,7 @@ def _save(im, fp, filename):
try: try:
ifd.tagtype[key] = info.tagtype[key] ifd.tagtype[key] = info.tagtype[key]
except Exception: except Exception:
pass # might not be an IFD, Might not have populated type pass # might not be an IFD. Might not have populated type
# additions written by Greg Couch, gregc@cgl.ucsf.edu # additions written by Greg Couch, gregc@cgl.ucsf.edu
# inspired by image-sig posting from Kevin Cazabon, kcazabon@home.com # inspired by image-sig posting from Kevin Cazabon, kcazabon@home.com
@ -1680,7 +1683,7 @@ class AppendingTiffWriter:
def tell(self): def tell(self):
return self.f.tell() - self.offsetOfNewPage return self.f.tell() - self.offsetOfNewPage
def seek(self, offset, whence): def seek(self, offset, whence=io.SEEK_SET):
if whence == os.SEEK_SET: if whence == os.SEEK_SET:
offset += self.offsetOfNewPage offset += self.offsetOfNewPage

View File

@ -16,10 +16,9 @@ PIL.VERSION is the old PIL version and will be removed in the future.
from . import _version from . import _version
# VERSION is deprecated and will be removed in Pillow 6.0.0. # VERSION was removed in Pillow 6.0.0.
# PILLOW_VERSION is deprecated and will be removed after that. # PILLOW_VERSION is deprecated and will be removed in Pillow 7.0.0.
# Use __version__ instead. # Use __version__ instead.
VERSION = '1.1.7' # PIL Version
PILLOW_VERSION = __version__ = _version.__version__ PILLOW_VERSION = __version__ = _version.__version__
del _version del _version

View File

@ -2,11 +2,18 @@ import os
import sys import sys
py3 = sys.version_info.major >= 3 py3 = sys.version_info.major >= 3
py36 = sys.version_info[0:2] >= (3, 6)
if py3: if py3:
def isStringType(t): def isStringType(t):
return isinstance(t, str) return isinstance(t, str)
if py36:
from pathlib import Path
def isPath(f):
return isinstance(f, (bytes, str, Path))
else:
def isPath(f): def isPath(f):
return isinstance(f, (bytes, str)) return isinstance(f, (bytes, str))
else: else:

View File

@ -39,7 +39,11 @@ ImagingFlipLeftRight(Imaging imOut, Imaging imIn)
ImagingSectionEnter(&cookie); ImagingSectionEnter(&cookie);
if (imIn->image8) { if (imIn->image8) {
if (strncmp(imIn->mode, "I;16", 4) == 0) {
FLIP_LEFT_RIGHT(UINT16, image8)
} else {
FLIP_LEFT_RIGHT(UINT8, image8) FLIP_LEFT_RIGHT(UINT8, image8)
}
} else { } else {
FLIP_LEFT_RIGHT(INT32, image32) FLIP_LEFT_RIGHT(INT32, image32)
} }
@ -253,7 +257,11 @@ ImagingRotate180(Imaging imOut, Imaging imIn)
yr = imIn->ysize-1; yr = imIn->ysize-1;
if (imIn->image8) { if (imIn->image8) {
if (strncmp(imIn->mode, "I;16", 4) == 0) {
ROTATE_180(UINT16, image8)
} else {
ROTATE_180(UINT8, image8) ROTATE_180(UINT8, image8)
}
} else { } else {
ROTATE_180(INT32, image32) ROTATE_180(INT32, image32)
} }