Merge branch 'master' into more-tiff-modes

This commit is contained in:
Alexander Karpinsky 2018-09-05 17:58:31 +03:00 committed by GitHub
commit f7eb11cc3d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
111 changed files with 1955 additions and 970 deletions

View File

@ -21,7 +21,9 @@ Please send a pull request to the master branch. Please include [documentation](
## Reporting Issues ## Reporting Issues
When reporting issues, please include code that reproduces the issue and whenever possible, an image that demonstrates the issue. The best reproductions are self-contained scripts with minimal dependencies. When reporting issues, please include code that reproduces the issue and whenever possible, an image that demonstrates the issue. Please upload images to GitHub, not to third-party file hosting sites. If necessary, add the image to a zip or tar archive.
The best reproductions are self-contained scripts with minimal dependencies. If you are using a framework such as plone, Django, or buildout, try to replicate the issue just using Pillow.
### Provide details ### Provide details

View File

@ -1,3 +1,5 @@
dist: xenial
sudo: required
language: python language: python
cache: pip cache: pip
@ -12,13 +14,24 @@ matrix:
fast_finish: true fast_finish: true
include: include:
- python: "pypy" - python: "pypy"
dist: trusty
- python: "pypy3" - python: "pypy3"
- python: '3.7-dev' dist: trusty
- python: '3.7'
- python: '2.7' - python: '2.7'
- python: '2.7'
dist: trusty
- python: "2.7_with_system_site_packages" # For PyQt4 - python: "2.7_with_system_site_packages" # For PyQt4
- python: "2.7_with_system_site_packages" # For PyQt4
dist: trusty
- python: '3.6' - python: '3.6'
- python: '3.6'
dist: trusty
- python: '3.5' - python: '3.5'
- python: '3.5'
dist: trusty
- python: '3.4' - python: '3.4'
dist: trusty
- env: DOCKER="alpine" DOCKER_TAG="pytest" - env: DOCKER="alpine" DOCKER_TAG="pytest"
- env: DOCKER="arch" DOCKER_TAG="pytest" # contains PyQt5 - env: DOCKER="arch" DOCKER_TAG="pytest" # contains PyQt5
- env: DOCKER="ubuntu-trusty-x86" DOCKER_TAG="pytest" - env: DOCKER="ubuntu-trusty-x86" DOCKER_TAG="pytest"
@ -31,10 +44,6 @@ matrix:
- env: DOCKER="fedora-26-amd64" DOCKER_TAG="pytest" - env: DOCKER="fedora-26-amd64" DOCKER_TAG="pytest"
- env: DOCKER="fedora-27-amd64" DOCKER_TAG="pytest" - env: DOCKER="fedora-27-amd64" DOCKER_TAG="pytest"
dist: trusty
sudo: required
services: services:
- docker - docker

View File

@ -2,6 +2,48 @@
Changelog (Pillow) Changelog (Pillow)
================== ==================
5.3.0 (unreleased)
------------------
- Changed ImageFilter.Kernel to subclass ImageFilter.BuiltinFilter, instead of the other way around #3273
[radarhere]
- Remove unused draw.draw_line, draw.draw_point and font.getabc methods #3232
[hugovk]
- Tests: Added ImageFilter tests #3295
[radarhere]
- Tests: Added ImageChops tests #3230
[hugovk, radarhere]
- AppVeyor: Download lib if not present in pillow-depends #3316
[radarhere]
- Travis CI: Add Python 3.7 and Xenial #3234
[hugovk]
- Docs: Added documentation for NumPy conversion #3301
[radarhere]
- Depends: Update libimagequant to 2.12.1 #3281
[radarhere]
- Add three-color support to ImageOps.colorize #3242
[tsennott]
- Tests: Add LA to TGA test modes #3222
[danpla]
- Skip outline if the draw operation fills with the same colour #2922
[radarhere]
- Flake8 fixes #3173
[radarhere]
- Avoid deprecated 'U' mode when opening files #2187
[jdufresne]
5.2.0 (2018-07-01) 5.2.0 (2018-07-01)
------------------ ------------------

View File

@ -4,17 +4,17 @@
Released quarterly on the first day of January, April, July, October. Released quarterly on the first day of January, April, July, October.
* [ ] Open a release ticket e.g. https://github.com/python-pillow/Pillow/issues/1174 * [ ] Open a release ticket e.g. https://github.com/python-pillow/Pillow/issues/3154
* [ ] Develop and prepare release in ``master`` branch. * [ ] Develop and prepare release in ``master`` branch.
* [ ] Check [Travis CI](https://travis-ci.org/python-pillow/Pillow) and [AppVeyor CI](https://ci.appveyor.com/project/python-pillow/Pillow) to confirm passing tests in ``master`` branch. * [ ] Check [Travis CI](https://travis-ci.org/python-pillow/Pillow) and [AppVeyor CI](https://ci.appveyor.com/project/python-pillow/Pillow) to confirm passing tests in ``master`` branch.
* [ ] Check that all of the wheel builds [Pillow Wheel Builder](https://github.com/python-pillow/pillow-wheels) pass the tests in TravisCI. * [ ] Check that all of the wheel builds [Pillow Wheel Builder](https://github.com/python-pillow/pillow-wheels) pass the tests in Travis CI.
* [ ] In compliance with https://www.python.org/dev/peps/pep-0440/, update version identifier in `src/PIL/_version.py` * [ ] In compliance with [PEP 440](https://www.python.org/dev/peps/pep-0440/), update version identifier in `src/PIL/_version.py`
* [ ] Update `CHANGES.rst`. * [ ] Update `CHANGES.rst`.
* [ ] Run pre-release check via `make release-test` in a freshly cloned repo. * [ ] Run pre-release check via `make release-test` in a freshly cloned repo.
* [ ] Create branch and tag for release e.g.: * [ ] Create branch and tag for release e.g.:
``` ```
$ git branch 2.9.x $ git branch 5.2.x
$ git tag 2.9.0 $ git tag 5.2.0
$ git push --all $ git push --all
$ git push --tags $ git push --tags
``` ```
@ -23,8 +23,9 @@ Released quarterly on the first day of January, April, July, October.
$ make sdist $ make sdist
``` ```
* [ ] Create [binary distributions](#binary-distributions) * [ ] Create [binary distributions](#binary-distributions)
* [ ] Upload all binaries and source distributions with ``twine upload dist/Pillow-4.1.0-*`` * [ ] Upload all binaries and source distributions e.g. ``twine upload dist/Pillow-5.2.0-*``
* [ ] Manually hide old versions on PyPI such that only the latest major release is visible when viewing https://pypi.org/project/Pillow/ (https://pypi.org/manage/project/Pillow/releases/) * [ ] Create a [new release on GitHub](https://github.com/python-pillow/Pillow/releases/new)
* [ ] In compliance with [PEP 440](https://www.python.org/dev/peps/pep-0440/), append `.dev0` to version identifier in `src/PIL/_version.py`
## Point Release ## Point Release
@ -32,17 +33,17 @@ Released as needed for security, installation or critical bug fixes.
* [ ] Make necessary changes in ``master`` branch. * [ ] Make necessary changes in ``master`` branch.
* [ ] Update `CHANGES.rst`. * [ ] Update `CHANGES.rst`.
* [ ] Cherry pick individual commits from ``master`` branch to release branch e.g. ``2.9.x``. * [ ] Cherry pick individual commits from ``master`` branch to release branch e.g. ``5.2.x``.
* [ ] Check [Travis CI](https://travis-ci.org/python-pillow/Pillow) to confirm passing tests in release branch e.g. ``2.9.x``. * [ ] Check [Travis CI](https://travis-ci.org/python-pillow/Pillow) to confirm passing tests in release branch e.g. ``5.2.x``.
* [ ] Checkout release branch e.g.: * [ ] Check out release branch e.g.:
``` ```
git checkout -t remotes/origin/2.9.x git checkout -t remotes/origin/5.2.x
``` ```
* [ ] In compliance with https://www.python.org/dev/peps/pep-0440/, update version identifier in `src/PIL/_version.py` * [ ] In compliance with [PEP 440](https://www.python.org/dev/peps/pep-0440/), update version identifier in `src/PIL/_version.py`
* [ ] Run pre-release check via `make release-test`. * [ ] Run pre-release check via `make release-test`.
* [ ] Create tag for release e.g.: * [ ] Create tag for release e.g.:
``` ```
$ git tag 2.9.1 $ git tag 5.2.1
$ git push --tags $ git push --tags
``` ```
* [ ] Create source distributions e.g.: * [ ] Create source distributions e.g.:
@ -50,6 +51,7 @@ Released as needed for security, installation or critical bug fixes.
$ make sdist $ make sdist
``` ```
* [ ] Create [binary distributions](#binary-distributions) * [ ] Create [binary distributions](#binary-distributions)
* [ ] Create a [new release on GitHub](https://github.com/python-pillow/Pillow/releases/new)
## Embargoed Release ## Embargoed Release
@ -73,6 +75,7 @@ Released as needed privately to individual vendors for critical security-related
$ make sdist $ make sdist
``` ```
* [ ] Create [binary distributions](#binary-distributions) * [ ] Create [binary distributions](#binary-distributions)
* [ ] Create a [new release on GitHub](https://github.com/python-pillow/Pillow/releases/new)
## Binary Distributions ## Binary Distributions
@ -83,15 +86,15 @@ Released as needed privately to individual vendors for critical security-related
### Mac and Linux ### Mac and Linux
* [ ] Use the [Pillow Wheel Builder](https://github.com/python-pillow/pillow-wheels): * [ ] Use the [Pillow Wheel Builder](https://github.com/python-pillow/pillow-wheels):
``` ```
$ git checkout 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 $ git submodule init
$ git submodule update $ git submodule update Pillow
$ cd Pillow $ cd Pillow
$ git fetch --all $ git fetch --all
$ git checkout [[release tag]] $ git checkout [[release tag]]
$ cd .. $ cd ..
$ git commit -m "Pillow -> 2.9.0" Pillow $ git commit -m "Pillow -> 5.2.0" Pillow
$ git push $ 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/).
@ -99,8 +102,8 @@ Released as needed privately to individual vendors for critical security-related
## Publicize Release ## Publicize Release
* [ ] Announce release availability via [Twitter](https://twitter.com/pythonpillow) e.g. https://twitter.com/aclark4life/status/583366798302691328. * [ ] Announce release availability via [Twitter](https://twitter.com/pythonpillow) e.g. https://twitter.com/PythonPillow/status/1013789184354603010
## Documentation ## Documentation
* [ ] Make sure the default version for Read the Docs is the latest release version, e.g. ``3.1.x`` rather than ``latest``: https://readthedocs.org/projects/pillow/versions/ * [ ] Make sure the default version for Read the Docs is the latest release version, i.e. ``5.2.0`` rather than ``latest`` e.g. https://pillow.readthedocs.io/en/5.2.x/

View File

@ -153,7 +153,8 @@ class PillowTestCase(unittest.TestCase):
pass pass
raise e raise e
def assert_image_similar_tofile(self, a, filename, epsilon, msg=None, mode=None): def assert_image_similar_tofile(self, a, filename, epsilon, msg=None,
mode=None):
with Image.open(filename) as img: with Image.open(filename) as img:
if mode: if mode:
img = img.convert(mode) img = img.convert(mode)
@ -191,6 +192,16 @@ class PillowTestCase(unittest.TestCase):
def assert_not_all_same(self, items, msg=None): def assert_not_all_same(self, items, msg=None):
self.assertFalse(items.count(items[0]) == len(items), msg) self.assertFalse(items.count(items[0]) == len(items), msg)
def assert_tuple_approx_equal(self, actuals, targets, threshold, msg):
"""Tests if actuals has values within threshold from targets"""
value = True
for i, target in enumerate(targets):
value *= (target - threshold <= actuals[i] <= target + threshold)
self.assertTrue(value,
msg + ': ' + repr(actuals) + ' != ' + repr(targets))
def skipKnownBadTest(self, msg=None, platform=None, def skipKnownBadTest(self, msg=None, platform=None,
travis=None, interpreter=None): travis=None, interpreter=None):
# Skip if platform/travis matches, and # Skip if platform/travis matches, and
@ -246,7 +257,8 @@ class PillowLeakTestCase(PillowTestCase):
mem = getrusage(RUSAGE_SELF).ru_maxrss mem = getrusage(RUSAGE_SELF).ru_maxrss
if sys.platform == 'darwin': if sys.platform == 'darwin':
# man 2 getrusage: # man 2 getrusage:
# ru_maxrss the maximum resident set size utilized (in bytes). # ru_maxrss
# This is the maximum resident set size utilized (in bytes).
return mem / 1024 # Kb return mem / 1024 # Kb
else: else:
# linux # linux

Binary file not shown.

After

Width:  |  Height:  |  Size: 102 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 212 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 259 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 290 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 378 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 266 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 340 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 309 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 393 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 125 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 212 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 263 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 316 B

View File

@ -27,8 +27,8 @@ class TestColorLut3DCoreAPI(PillowTestCase):
g / float(size2D-1) if size2D != 1 else 0, g / float(size2D-1) if size2D != 1 else 0,
][:channels] ][:channels]
for b in range(size3D) for b in range(size3D)
for g in range(size2D) for g in range(size2D)
for r in range(size1D) for r in range(size1D)
] ]
return ( return (
channels, size1D, size2D, size3D, channels, size1D, size2D, size3D,
@ -38,112 +38,123 @@ class TestColorLut3DCoreAPI(PillowTestCase):
im = Image.new('RGB', (10, 10), 0) im = Image.new('RGB', (10, 10), 0)
with self.assertRaisesRegex(ValueError, "filter"): with self.assertRaisesRegex(ValueError, "filter"):
im.im.color_lut_3d('RGB', Image.CUBIC, im.im.color_lut_3d('RGB',
*self.generate_identity_table(3, 3)) Image.CUBIC,
*self.generate_identity_table(3, 3))
with self.assertRaisesRegex(ValueError, "image mode"): with self.assertRaisesRegex(ValueError, "image mode"):
im.im.color_lut_3d('wrong', Image.LINEAR, im.im.color_lut_3d('wrong',
*self.generate_identity_table(3, 3)) Image.LINEAR,
*self.generate_identity_table(3, 3))
with self.assertRaisesRegex(ValueError, "table_channels"): with self.assertRaisesRegex(ValueError, "table_channels"):
im.im.color_lut_3d('RGB', Image.LINEAR, im.im.color_lut_3d('RGB',
*self.generate_identity_table(5, 3)) Image.LINEAR,
*self.generate_identity_table(5, 3))
with self.assertRaisesRegex(ValueError, "table_channels"): with self.assertRaisesRegex(ValueError, "table_channels"):
im.im.color_lut_3d('RGB', Image.LINEAR, im.im.color_lut_3d('RGB',
*self.generate_identity_table(1, 3)) Image.LINEAR,
*self.generate_identity_table(1, 3))
with self.assertRaisesRegex(ValueError, "table_channels"): with self.assertRaisesRegex(ValueError, "table_channels"):
im.im.color_lut_3d('RGB', Image.LINEAR, im.im.color_lut_3d('RGB',
*self.generate_identity_table(2, 3)) Image.LINEAR,
*self.generate_identity_table(2, 3))
with self.assertRaisesRegex(ValueError, "Table size"): with self.assertRaisesRegex(ValueError, "Table size"):
im.im.color_lut_3d('RGB', Image.LINEAR, im.im.color_lut_3d('RGB',
*self.generate_identity_table(3, (1, 3, 3))) Image.LINEAR,
*self.generate_identity_table(3, (1, 3, 3)))
with self.assertRaisesRegex(ValueError, "Table size"): with self.assertRaisesRegex(ValueError, "Table size"):
im.im.color_lut_3d('RGB', Image.LINEAR, im.im.color_lut_3d('RGB',
*self.generate_identity_table(3, (66, 3, 3))) Image.LINEAR,
*self.generate_identity_table(3, (66, 3, 3)))
with self.assertRaisesRegex(ValueError, r"size1D \* size2D \* size3D"): with self.assertRaisesRegex(ValueError, r"size1D \* size2D \* size3D"):
im.im.color_lut_3d('RGB', Image.LINEAR, im.im.color_lut_3d('RGB',
3, 2, 2, 2, [0, 0, 0] * 7) Image.LINEAR,
3, 2, 2, 2, [0, 0, 0] * 7)
with self.assertRaisesRegex(ValueError, r"size1D \* size2D \* size3D"): with self.assertRaisesRegex(ValueError, r"size1D \* size2D \* size3D"):
im.im.color_lut_3d('RGB', Image.LINEAR, im.im.color_lut_3d('RGB',
3, 2, 2, 2, [0, 0, 0] * 9) Image.LINEAR,
3, 2, 2, 2, [0, 0, 0] * 9)
with self.assertRaises(TypeError): with self.assertRaises(TypeError):
im.im.color_lut_3d('RGB', Image.LINEAR, im.im.color_lut_3d('RGB',
3, 2, 2, 2, [0, 0, "0"] * 8) Image.LINEAR,
3, 2, 2, 2, [0, 0, "0"] * 8)
with self.assertRaises(TypeError): with self.assertRaises(TypeError):
im.im.color_lut_3d('RGB', Image.LINEAR, im.im.color_lut_3d('RGB',
3, 2, 2, 2, 16) Image.LINEAR,
3, 2, 2, 2, 16)
def test_correct_args(self): def test_correct_args(self):
im = Image.new('RGB', (10, 10), 0) im = Image.new('RGB', (10, 10), 0)
im.im.color_lut_3d('RGB', Image.LINEAR, im.im.color_lut_3d('RGB', Image.LINEAR,
*self.generate_identity_table(3, 3)) *self.generate_identity_table(3, 3))
im.im.color_lut_3d('CMYK', Image.LINEAR, im.im.color_lut_3d('CMYK', Image.LINEAR,
*self.generate_identity_table(4, 3)) *self.generate_identity_table(4, 3))
im.im.color_lut_3d('RGB', Image.LINEAR, im.im.color_lut_3d('RGB', Image.LINEAR,
*self.generate_identity_table(3, (2, 3, 3))) *self.generate_identity_table(3, (2, 3, 3)))
im.im.color_lut_3d('RGB', Image.LINEAR, im.im.color_lut_3d('RGB', Image.LINEAR,
*self.generate_identity_table(3, (65, 3, 3))) *self.generate_identity_table(3, (65, 3, 3)))
im.im.color_lut_3d('RGB', Image.LINEAR, im.im.color_lut_3d('RGB', Image.LINEAR,
*self.generate_identity_table(3, (3, 65, 3))) *self.generate_identity_table(3, (3, 65, 3)))
im.im.color_lut_3d('RGB', Image.LINEAR, im.im.color_lut_3d('RGB', Image.LINEAR,
*self.generate_identity_table(3, (3, 3, 65))) *self.generate_identity_table(3, (3, 3, 65)))
def test_wrong_mode(self): def test_wrong_mode(self):
with self.assertRaisesRegex(ValueError, "wrong mode"): with self.assertRaisesRegex(ValueError, "wrong mode"):
im = Image.new('L', (10, 10), 0) im = Image.new('L', (10, 10), 0)
im.im.color_lut_3d('RGB', Image.LINEAR, im.im.color_lut_3d('RGB', Image.LINEAR,
*self.generate_identity_table(3, 3)) *self.generate_identity_table(3, 3))
with self.assertRaisesRegex(ValueError, "wrong mode"): with self.assertRaisesRegex(ValueError, "wrong mode"):
im = Image.new('RGB', (10, 10), 0) im = Image.new('RGB', (10, 10), 0)
im.im.color_lut_3d('L', Image.LINEAR, im.im.color_lut_3d('L', Image.LINEAR,
*self.generate_identity_table(3, 3)) *self.generate_identity_table(3, 3))
with self.assertRaisesRegex(ValueError, "wrong mode"): with self.assertRaisesRegex(ValueError, "wrong mode"):
im = Image.new('L', (10, 10), 0) im = Image.new('L', (10, 10), 0)
im.im.color_lut_3d('L', Image.LINEAR, im.im.color_lut_3d('L', Image.LINEAR,
*self.generate_identity_table(3, 3)) *self.generate_identity_table(3, 3))
with self.assertRaisesRegex(ValueError, "wrong mode"): with self.assertRaisesRegex(ValueError, "wrong mode"):
im = Image.new('RGB', (10, 10), 0) im = Image.new('RGB', (10, 10), 0)
im.im.color_lut_3d('RGBA', Image.LINEAR, im.im.color_lut_3d('RGBA', Image.LINEAR,
*self.generate_identity_table(3, 3)) *self.generate_identity_table(3, 3))
with self.assertRaisesRegex(ValueError, "wrong mode"): with self.assertRaisesRegex(ValueError, "wrong mode"):
im = Image.new('RGB', (10, 10), 0) im = Image.new('RGB', (10, 10), 0)
im.im.color_lut_3d('RGB', Image.LINEAR, im.im.color_lut_3d('RGB', Image.LINEAR,
*self.generate_identity_table(4, 3)) *self.generate_identity_table(4, 3))
def test_correct_mode(self): def test_correct_mode(self):
im = Image.new('RGBA', (10, 10), 0) im = Image.new('RGBA', (10, 10), 0)
im.im.color_lut_3d('RGBA', Image.LINEAR, im.im.color_lut_3d('RGBA', Image.LINEAR,
*self.generate_identity_table(3, 3)) *self.generate_identity_table(3, 3))
im = Image.new('RGBA', (10, 10), 0) im = Image.new('RGBA', (10, 10), 0)
im.im.color_lut_3d('RGBA', Image.LINEAR, im.im.color_lut_3d('RGBA', Image.LINEAR,
*self.generate_identity_table(4, 3)) *self.generate_identity_table(4, 3))
im = Image.new('RGB', (10, 10), 0) im = Image.new('RGB', (10, 10), 0)
im.im.color_lut_3d('HSV', Image.LINEAR, im.im.color_lut_3d('HSV', Image.LINEAR,
*self.generate_identity_table(3, 3)) *self.generate_identity_table(3, 3))
im = Image.new('RGB', (10, 10), 0) im = Image.new('RGB', (10, 10), 0)
im.im.color_lut_3d('RGBA', Image.LINEAR, im.im.color_lut_3d('RGBA', Image.LINEAR,
*self.generate_identity_table(4, 3)) *self.generate_identity_table(4, 3))
def test_identities(self): def test_identities(self):
g = Image.linear_gradient('L') g = Image.linear_gradient('L')
@ -154,12 +165,12 @@ class TestColorLut3DCoreAPI(PillowTestCase):
for size in [2, 3, 5, 7, 11, 16, 17]: for size in [2, 3, 5, 7, 11, 16, 17]:
self.assert_image_equal(im, im._new( self.assert_image_equal(im, im._new(
im.im.color_lut_3d('RGB', Image.LINEAR, im.im.color_lut_3d('RGB', Image.LINEAR,
*self.generate_identity_table(3, size)))) *self.generate_identity_table(3, size))))
# Not so fast # Not so fast
self.assert_image_equal(im, im._new( self.assert_image_equal(im, im._new(
im.im.color_lut_3d('RGB', Image.LINEAR, im.im.color_lut_3d('RGB', Image.LINEAR,
*self.generate_identity_table(3, (2, 2, 65))))) *self.generate_identity_table(3, (2, 2, 65)))))
def test_identities_4_channels(self): def test_identities_4_channels(self):
g = Image.linear_gradient('L') g = Image.linear_gradient('L')
@ -170,7 +181,7 @@ class TestColorLut3DCoreAPI(PillowTestCase):
self.assert_image_equal( self.assert_image_equal(
Image.merge('RGBA', (im.split()*2)[:4]), Image.merge('RGBA', (im.split()*2)[:4]),
im._new(im.im.color_lut_3d('RGBA', Image.LINEAR, im._new(im.im.color_lut_3d('RGBA', Image.LINEAR,
*self.generate_identity_table(4, 17)))) *self.generate_identity_table(4, 17))))
def test_copy_alpha_channel(self): def test_copy_alpha_channel(self):
g = Image.linear_gradient('L') g = Image.linear_gradient('L')
@ -180,7 +191,7 @@ class TestColorLut3DCoreAPI(PillowTestCase):
self.assert_image_equal(im, im._new( self.assert_image_equal(im, im._new(
im.im.color_lut_3d('RGBA', Image.LINEAR, im.im.color_lut_3d('RGBA', Image.LINEAR,
*self.generate_identity_table(3, 17)))) *self.generate_identity_table(3, 17))))
def test_channels_order(self): def test_channels_order(self):
g = Image.linear_gradient('L') g = Image.linear_gradient('L')
@ -191,13 +202,13 @@ class TestColorLut3DCoreAPI(PillowTestCase):
self.assert_image_equal( self.assert_image_equal(
Image.merge('RGB', im.split()[::-1]), Image.merge('RGB', im.split()[::-1]),
im._new(im.im.color_lut_3d('RGB', Image.LINEAR, im._new(im.im.color_lut_3d('RGB', Image.LINEAR,
3, 2, 2, 2, [ 3, 2, 2, 2, [
0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1,
0, 1, 0, 0, 1, 1, 0, 1, 0, 0, 1, 1,
1, 0, 0, 1, 0, 1, 1, 0, 0, 1, 0, 1,
1, 1, 0, 1, 1, 1, 1, 1, 0, 1, 1, 1,
]))) ])))
def test_overflow(self): def test_overflow(self):
g = Image.linear_gradient('L') g = Image.linear_gradient('L')
@ -205,14 +216,14 @@ class TestColorLut3DCoreAPI(PillowTestCase):
g.transpose(Image.ROTATE_180)]) g.transpose(Image.ROTATE_180)])
transformed = im._new(im.im.color_lut_3d('RGB', Image.LINEAR, transformed = im._new(im.im.color_lut_3d('RGB', Image.LINEAR,
3, 2, 2, 2, 3, 2, 2, 2,
[ [
-1, -1, -1, 2, -1, -1, -1, -1, -1, 2, -1, -1,
-1, 2, -1, 2, 2, -1, -1, 2, -1, 2, 2, -1,
-1, -1, 2, 2, -1, 2, -1, -1, 2, 2, -1, 2,
-1, 2, 2, 2, 2, 2, -1, 2, 2, 2, 2, 2,
])).load() ])).load()
self.assertEqual(transformed[0, 0], (0, 0, 255)) self.assertEqual(transformed[0, 0], (0, 0, 255))
self.assertEqual(transformed[50, 50], (0, 0, 255)) self.assertEqual(transformed[50, 50], (0, 0, 255))
self.assertEqual(transformed[255, 0], (0, 255, 255)) self.assertEqual(transformed[255, 0], (0, 255, 255))
@ -223,14 +234,14 @@ class TestColorLut3DCoreAPI(PillowTestCase):
self.assertEqual(transformed[205, 205], (255, 255, 0)) self.assertEqual(transformed[205, 205], (255, 255, 0))
transformed = im._new(im.im.color_lut_3d('RGB', Image.LINEAR, transformed = im._new(im.im.color_lut_3d('RGB', Image.LINEAR,
3, 2, 2, 2, 3, 2, 2, 2,
[ [
-3, -3, -3, 5, -3, -3, -3, -3, -3, 5, -3, -3,
-3, 5, -3, 5, 5, -3, -3, 5, -3, 5, 5, -3,
-3, -3, 5, 5, -3, 5, -3, -3, 5, 5, -3, 5,
-3, 5, 5, 5, 5, 5, -3, 5, 5, 5, 5, 5,
])).load() ])).load()
self.assertEqual(transformed[0, 0], (0, 0, 255)) self.assertEqual(transformed[0, 0], (0, 0, 255))
self.assertEqual(transformed[50, 50], (0, 0, 255)) self.assertEqual(transformed[50, 50], (0, 0, 255))
self.assertEqual(transformed[255, 0], (0, 255, 255)) self.assertEqual(transformed[255, 0], (0, 255, 255))
@ -366,22 +377,23 @@ class TestColorLut3DFilter(PillowTestCase):
lut = ImageFilter.Color3DLUT( lut = ImageFilter.Color3DLUT(
(3, 4, 5), array('f', [0, 0, 0, 0] * (3 * 4 * 5)), (3, 4, 5), array('f', [0, 0, 0, 0] * (3 * 4 * 5)),
channels=4, target_mode='YCbCr', _copy_table=False) channels=4, target_mode='YCbCr', _copy_table=False)
self.assertEqual(repr(lut), self.assertEqual(
repr(lut),
"<Color3DLUT from array size=3x4x5 channels=4 target_mode=YCbCr>") "<Color3DLUT from array size=3x4x5 channels=4 target_mode=YCbCr>")
class TestGenerateColorLut3D(PillowTestCase): class TestGenerateColorLut3D(PillowTestCase):
def test_wrong_channels_count(self): def test_wrong_channels_count(self):
with self.assertRaisesRegex(ValueError, "3 or 4 output channels"): with self.assertRaisesRegex(ValueError, "3 or 4 output channels"):
ImageFilter.Color3DLUT.generate(5, channels=2, ImageFilter.Color3DLUT.generate(
callback=lambda r, g, b: (r, g, b)) 5, channels=2, callback=lambda r, g, b: (r, g, b))
with self.assertRaisesRegex(ValueError, "should have either channels"): with self.assertRaisesRegex(ValueError, "should have either channels"):
ImageFilter.Color3DLUT.generate(5, lambda r, g, b: (r, g, b, r)) ImageFilter.Color3DLUT.generate(5, lambda r, g, b: (r, g, b, r))
with self.assertRaisesRegex(ValueError, "should have either channels"): with self.assertRaisesRegex(ValueError, "should have either channels"):
ImageFilter.Color3DLUT.generate(5, channels=4, ImageFilter.Color3DLUT.generate(
callback=lambda r, g, b: (r, g, b)) 5, channels=4, callback=lambda r, g, b: (r, g, b))
def test_3_channels(self): def test_3_channels(self):
lut = ImageFilter.Color3DLUT.generate(5, lambda r, g, b: (r, g, b)) lut = ImageFilter.Color3DLUT.generate(5, lambda r, g, b: (r, g, b))
@ -392,13 +404,14 @@ class TestGenerateColorLut3D(PillowTestCase):
1.0, 0.0, 0.0, 0.0, 0.25, 0.0, 0.25, 0.25, 0.0, 0.5, 0.25, 0.0]) 1.0, 0.0, 0.0, 0.0, 0.25, 0.0, 0.25, 0.25, 0.0, 0.5, 0.25, 0.0])
def test_4_channels(self): def test_4_channels(self):
lut = ImageFilter.Color3DLUT.generate(5, channels=4, lut = ImageFilter.Color3DLUT.generate(
callback=lambda r, g, b: (b, r, g, (r+g+b) / 2)) 5, channels=4, callback=lambda r, g, b: (b, r, g, (r+g+b) / 2))
self.assertEqual(tuple(lut.size), (5, 5, 5)) self.assertEqual(tuple(lut.size), (5, 5, 5))
self.assertEqual(lut.name, "Color 3D LUT") self.assertEqual(lut.name, "Color 3D LUT")
self.assertEqual(lut.table[:24], [ self.assertEqual(lut.table[:24], [
0.0, 0.0, 0.0, 0.0, 0.0, 0.25, 0.0, 0.125, 0.0, 0.5, 0.0, 0.25, 0.0, 0.0, 0.0, 0.0, 0.0, 0.25, 0.0, 0.125, 0.0, 0.5, 0.0, 0.25,
0.0, 0.75, 0.0, 0.375, 0.0, 1.0, 0.0, 0.5, 0.0, 0.0, 0.25, 0.125]) 0.0, 0.75, 0.0, 0.375, 0.0, 1.0, 0.0, 0.5, 0.0, 0.0, 0.25, 0.125
])
def test_apply(self): def test_apply(self):
lut = ImageFilter.Color3DLUT.generate(5, lambda r, g, b: (r, g, b)) lut = ImageFilter.Color3DLUT.generate(5, lambda r, g, b: (r, g, b))

View File

@ -1,7 +1,6 @@
from helper import unittest, PillowTestCase, hopper from helper import unittest, PillowTestCase, hopper
from PIL import Image, EpsImagePlugin from PIL import Image, EpsImagePlugin
from PIL._util import py3
import io import io
# Our two EPS test files (they are identical except for their bounding boxes) # Our two EPS test files (they are identical except for their bounding boxes)
@ -196,41 +195,15 @@ class TestFileEps(PillowTestCase):
self.assertEqual(t.readline().strip('\r\n'), 'baz', ending) self.assertEqual(t.readline().strip('\r\n'), 'baz', ending)
self.assertEqual(t.readline().strip('\r\n'), 'bif', ending) self.assertEqual(t.readline().strip('\r\n'), 'bif', ending)
def _test_readline_stringio(self, test_string, ending): def _test_readline_io_psfile(self, test_string, ending):
# check all the freaking line endings possible f = io.BytesIO(test_string.encode('latin-1'))
try: t = EpsImagePlugin.PSFile(f)
import StringIO
except ImportError:
# don't skip, it skips everything in the parent test
return
t = StringIO.StringIO(test_string)
self._test_readline(t, ending) self._test_readline(t, ending)
def _test_readline_io(self, test_string, ending):
if py3:
t = io.StringIO(test_string)
else:
t = io.StringIO(unicode(test_string))
self._test_readline(t, ending)
def _test_readline_file_universal(self, test_string, ending):
f = self.tempfile('temp.txt')
with open(f, 'wb') as w:
if py3:
w.write(test_string.encode('UTF-8'))
else:
w.write(test_string)
with open(f, 'rU') as t:
self._test_readline(t, ending)
def _test_readline_file_psfile(self, test_string, ending): def _test_readline_file_psfile(self, test_string, ending):
f = self.tempfile('temp.txt') f = self.tempfile('temp.txt')
with open(f, 'wb') as w: with open(f, 'wb') as w:
if py3: w.write(test_string.encode('latin-1'))
w.write(test_string.encode('UTF-8'))
else:
w.write(test_string)
with open(f, 'rb') as r: with open(f, 'rb') as r:
t = EpsImagePlugin.PSFile(r) t = EpsImagePlugin.PSFile(r)
@ -239,29 +212,12 @@ class TestFileEps(PillowTestCase):
def test_readline(self): def test_readline(self):
# check all the freaking line endings possible from the spec # check all the freaking line endings possible from the spec
# test_string = u'something\r\nelse\n\rbaz\rbif\n' # test_string = u'something\r\nelse\n\rbaz\rbif\n'
line_endings = ['\r\n', '\n'] line_endings = ['\r\n', '\n', '\n\r', '\r']
not_working_endings = ['\n\r', '\r']
strings = ['something', 'else', 'baz', 'bif'] strings = ['something', 'else', 'baz', 'bif']
for ending in line_endings: for ending in line_endings:
s = ending.join(strings) s = ending.join(strings)
# Native Python versions will pass these endings. self._test_readline_io_psfile(s, ending)
# self._test_readline_stringio(s, ending)
# self._test_readline_io(s, ending)
# self._test_readline_file_universal(s, ending)
self._test_readline_file_psfile(s, ending)
for ending in not_working_endings:
# these only work with the PSFile, while they're in spec,
# they're not likely to be used
s = ending.join(strings)
# Native Python versions may fail on these endings.
# self._test_readline_stringio(s, ending)
# self._test_readline_io(s, ending)
# self._test_readline_file_universal(s, ending)
self._test_readline_file_psfile(s, ending) self._test_readline_file_psfile(s, ending)
def test_open_eps(self): def test_open_eps(self):

View File

@ -402,7 +402,8 @@ class TestFileGif(PillowTestCase):
def test_comment(self): def test_comment(self):
im = Image.open(TEST_GIF) im = Image.open(TEST_GIF)
self.assertEqual(im.info['comment'], b"File written by Adobe Photoshop\xa8 4.0") self.assertEqual(im.info['comment'],
b"File written by Adobe Photoshop\xa8 4.0")
out = self.tempfile('temp.gif') out = self.tempfile('temp.gif')
im = Image.new('L', (100, 100), '#000') im = Image.new('L', (100, 100), '#000')

View File

@ -364,7 +364,6 @@ class TestFileJpeg(PillowTestCase):
with self.assertRaises(IOError): with self.assertRaises(IOError):
im.load() im.load()
def _n_qtables_helper(self, n, test_file): def _n_qtables_helper(self, n, test_file):
im = Image.open(test_file) im = Image.open(test_file)
f = self.tempfile('temp.jpg') f = self.tempfile('temp.jpg')

View File

@ -146,13 +146,13 @@ class TestFileJpeg2k(PillowTestCase):
self.assertEqual(j2k.mode, 'I;16') self.assertEqual(j2k.mode, 'I;16')
self.assertEqual(jp2.mode, 'I;16') self.assertEqual(jp2.mode, 'I;16')
def test_16bit_monchrome_jp2_like_tiff(self): def test_16bit_monochrome_jp2_like_tiff(self):
tiff_16bit = Image.open('Tests/images/16bit.cropped.tif') tiff_16bit = Image.open('Tests/images/16bit.cropped.tif')
jp2 = Image.open('Tests/images/16bit.cropped.jp2') jp2 = Image.open('Tests/images/16bit.cropped.jp2')
self.assert_image_similar(jp2, tiff_16bit, 1e-3) self.assert_image_similar(jp2, tiff_16bit, 1e-3)
def test_16bit_monchrome_j2k_like_tiff(self): def test_16bit_monochrome_j2k_like_tiff(self):
tiff_16bit = Image.open('Tests/images/16bit.cropped.tif') tiff_16bit = Image.open('Tests/images/16bit.cropped.tif')
j2k = Image.open('Tests/images/16bit.cropped.j2k') j2k = Image.open('Tests/images/16bit.cropped.j2k')

View File

@ -31,7 +31,7 @@ class LibTiffTestCase(PillowTestCase):
try: try:
self.assertEqual(im._compression, 'group4') self.assertEqual(im._compression, 'group4')
except: except AttributeError:
print("No _compression") print("No _compression")
print(dir(im)) print(dir(im))
@ -126,7 +126,8 @@ class TestFileLibTiff(LibTiffTestCase):
im.tile[0][:3], ('tiff_adobe_deflate', (0, 0, 278, 374), 0)) im.tile[0][:3], ('tiff_adobe_deflate', (0, 0, 278, 374), 0))
im.load() im.load()
self.assert_image_equal_tofile(im, 'Tests/images/tiff_adobe_deflate.png') self.assert_image_equal_tofile(im,
'Tests/images/tiff_adobe_deflate.png')
def test_write_metadata(self): def test_write_metadata(self):
""" Test metadata writing through libtiff """ """ Test metadata writing through libtiff """
@ -194,7 +195,7 @@ class TestFileLibTiff(LibTiffTestCase):
for tag in im.tag_v2: for tag in im.tag_v2:
try: try:
del(core_items[tag]) del(core_items[tag])
except: except KeyError:
pass pass
# Type codes: # Type codes:
@ -217,7 +218,8 @@ class TestFileLibTiff(LibTiffTestCase):
if info.length == 0: if info.length == 0:
new_ifd[tag] = tuple(values[info.type] for _ in range(3)) new_ifd[tag] = tuple(values[info.type] for _ in range(3))
else: else:
new_ifd[tag] = tuple(values[info.type] for _ in range(info.length)) new_ifd[tag] = tuple(values[info.type]
for _ in range(info.length))
# Extra samples really doesn't make sense in this application. # Extra samples really doesn't make sense in this application.
del(new_ifd[338]) del(new_ifd[338])
@ -525,7 +527,7 @@ class TestFileLibTiff(LibTiffTestCase):
f.write(src.read()) f.write(src.read())
im = Image.open(tmpfile) im = Image.open(tmpfile)
count = im.n_frames im.n_frames
im.close() im.close()
try: try:
os.remove(tmpfile) # Windows PermissionError here! os.remove(tmpfile) # Windows PermissionError here!
@ -578,10 +580,14 @@ class TestFileLibTiff(LibTiffTestCase):
self.assertEqual(im.mode, "RGBA") self.assertEqual(im.mode, "RGBA")
self.assertEqual(im.size, (100, 40)) self.assertEqual(im.size, (100, 40))
self.assertEqual(im.tile, [('tiff_lzw', (0, 0, 100, 40), 0, ('RGBa;16N', 'tiff_lzw', False))]) self.assertEqual(
im.tile,
[('tiff_lzw', (0, 0, 100, 40), 0, ('RGBa;16N', 'tiff_lzw', False))]
)
im.load() im.load()
self.assert_image_equal_tofile(im, "Tests/images/tiff_16bit_RGBa_target.png") self.assert_image_equal_tofile(
im, "Tests/images/tiff_16bit_RGBa_target.png")
def test_gimp_tiff(self): def test_gimp_tiff(self):
# Read TIFF JPEG images from GIMP [@PIL168] # Read TIFF JPEG images from GIMP [@PIL168]
@ -607,7 +613,8 @@ class TestFileLibTiff(LibTiffTestCase):
im = Image.open("Tests/images/copyleft.tiff") im = Image.open("Tests/images/copyleft.tiff")
self.assertEqual(im.mode, 'RGB') self.assertEqual(im.mode, 'RGB')
self.assert_image_equal_tofile(im, "Tests/images/copyleft.png", mode='RGB') self.assert_image_equal_tofile(im, "Tests/images/copyleft.png",
mode='RGB')
def test_lzw(self): def test_lzw(self):
im = Image.open("Tests/images/hopper_lzw.tif") im = Image.open("Tests/images/hopper_lzw.tif")

View File

@ -20,7 +20,8 @@ class TestFilePdf(PillowTestCase):
self.assertTrue(os.path.isfile(outfile)) self.assertTrue(os.path.isfile(outfile))
self.assertGreater(os.path.getsize(outfile), 0) self.assertGreater(os.path.getsize(outfile), 0)
with PdfParser.PdfParser(outfile) as pdf: with PdfParser.PdfParser(outfile) as pdf:
if kwargs.get("append_images", False) or kwargs.get("append", False): if kwargs.get("append_images", False) or \
kwargs.get("append", False):
self.assertGreater(len(pdf.pages), 1) self.assertGreater(len(pdf.pages), 1)
else: else:
self.assertGreater(len(pdf.pages), 0) self.assertGreater(len(pdf.pages), 0)
@ -116,7 +117,9 @@ class TestFilePdf(PillowTestCase):
def test_pdf_open(self): def test_pdf_open(self):
# fail on a buffer full of null bytes # fail on a buffer full of null bytes
self.assertRaises(PdfParser.PdfFormatError, PdfParser.PdfParser, buf=bytearray(65536)) self.assertRaises(
PdfParser.PdfFormatError,
PdfParser.PdfParser, buf=bytearray(65536))
# make an empty PDF object # make an empty PDF object
with PdfParser.PdfParser() as empty_pdf: with PdfParser.PdfParser() as empty_pdf:
@ -153,7 +156,10 @@ class TestFilePdf(PillowTestCase):
im = hopper("RGB") im = hopper("RGB")
temp_dir = tempfile.mkdtemp() temp_dir = tempfile.mkdtemp()
try: try:
self.assertRaises(IOError, im.save, os.path.join(temp_dir, "nonexistent.pdf"), append=True) self.assertRaises(IOError,
im.save,
os.path.join(temp_dir, "nonexistent.pdf"),
append=True)
finally: finally:
os.rmdir(temp_dir) os.rmdir(temp_dir)
@ -204,7 +210,8 @@ class TestFilePdf(PillowTestCase):
# append two images # append two images
mode_CMYK = hopper("CMYK") mode_CMYK = hopper("CMYK")
mode_P = hopper("P") mode_P = hopper("P")
mode_CMYK.save(pdf_filename, append=True, save_all=True, append_images=[mode_P]) mode_CMYK.save(pdf_filename,
append=True, save_all=True, append_images=[mode_P])
# open the PDF again, check pages and info again # open the PDF again, check pages and info again
with PdfParser.PdfParser(pdf_filename) as pdf: with PdfParser.PdfParser(pdf_filename) as pdf:
@ -219,7 +226,9 @@ class TestFilePdf(PillowTestCase):
def test_pdf_info(self): def test_pdf_info(self):
# make a PDF file # make a PDF file
pdf_filename = self.helper_save_as_pdf("RGB", title="title", author="author", subject="subject", keywords="keywords", creator="creator", producer="producer") pdf_filename = self.helper_save_as_pdf(
"RGB", title="title", author="author", subject="subject",
keywords="keywords", creator="creator", producer="producer")
# open it, check pages and info # open it, check pages and info
with PdfParser.PdfParser(pdf_filename) as pdf: with PdfParser.PdfParser(pdf_filename) as pdf:

View File

@ -355,7 +355,8 @@ class TestFilePng(PillowTestCase):
broken_crc_chunk_data = chunk_data[:-1] + b'q' # break CRC broken_crc_chunk_data = chunk_data[:-1] + b'q' # break CRC
image_data = HEAD + broken_crc_chunk_data + TAIL image_data = HEAD + broken_crc_chunk_data + TAIL
self.assertRaises(SyntaxError, PngImagePlugin.PngImageFile, BytesIO(image_data)) self.assertRaises(SyntaxError, PngImagePlugin.PngImageFile,
BytesIO(image_data))
ImageFile.LOAD_TRUNCATED_IMAGES = True ImageFile.LOAD_TRUNCATED_IMAGES = True
try: try:
@ -371,7 +372,8 @@ class TestFilePng(PillowTestCase):
ImageFile.LOAD_TRUNCATED_IMAGES = True ImageFile.LOAD_TRUNCATED_IMAGES = True
try: try:
self.assertRaises(SyntaxError, PngImagePlugin.PngImageFile, BytesIO(image_data)) self.assertRaises(SyntaxError, PngImagePlugin.PngImageFile,
BytesIO(image_data))
finally: finally:
ImageFile.LOAD_TRUNCATED_IMAGES = False ImageFile.LOAD_TRUNCATED_IMAGES = False

View File

@ -12,7 +12,7 @@ class TestFileTar(PillowTestCase):
def setUp(self): def setUp(self):
if "zip_decoder" not in codecs and "jpeg_decoder" not in codecs: if "zip_decoder" not in codecs and "jpeg_decoder" not in codecs:
self.skipTest("neither jpeg nor zip support not available") self.skipTest("neither jpeg nor zip support available")
def test_sanity(self): def test_sanity(self):
if "zip_decoder" in codecs: if "zip_decoder" in codecs:

View File

@ -13,7 +13,7 @@ _TGA_DIR_COMMON = os.path.join(_TGA_DIR, "common")
class TestFileTga(PillowTestCase): class TestFileTga(PillowTestCase):
_MODES = ("L", "P", "RGB", "RGBA") _MODES = ("L", "LA", "P", "RGB", "RGBA")
_ORIGINS = ("tl", "bl") _ORIGINS = ("tl", "bl")
_ORIGIN_TO_ORIENTATION = { _ORIGIN_TO_ORIENTATION = {

View File

@ -444,7 +444,8 @@ class TestFileTiff(PillowTestCase):
for im in ims: for im in ims:
yield im yield im
mp = io.BytesIO() mp = io.BytesIO()
im.save(mp, format="TIFF", save_all=True, append_images=imGenerator(ims)) im.save(mp, format="TIFF", save_all=True,
append_images=imGenerator(ims))
mp.seek(0, os.SEEK_SET) mp.seek(0, os.SEEK_SET)
reread = Image.open(mp) reread = Image.open(mp)

View File

@ -83,7 +83,8 @@ class TestFileWebpAnimation(PillowTestCase):
temp_file1 = self.tempfile("temp.webp") temp_file1 = self.tempfile("temp.webp")
frame1.copy().save(temp_file1, frame1.copy().save(temp_file1,
save_all=True, append_images=[frame2], lossless=True) save_all=True, append_images=[frame2],
lossless=True)
check(temp_file1) check(temp_file1)
# Tests appending using a generator # Tests appending using a generator
@ -92,7 +93,8 @@ class TestFileWebpAnimation(PillowTestCase):
yield im yield im
temp_file2 = self.tempfile("temp_generator.webp") temp_file2 = self.tempfile("temp_generator.webp")
frame1.copy().save(temp_file2, frame1.copy().save(temp_file2,
save_all=True, append_images=imGenerator([frame2]), lossless=True) save_all=True, append_images=imGenerator([frame2]),
lossless=True)
check(temp_file2) check(temp_file2)
def test_timestamp_and_duration(self): def test_timestamp_and_duration(self):

View File

@ -16,7 +16,8 @@ class TestTTypeFontLeak(PillowLeakTestCase):
self._test_leak(lambda: draw.text((0, 0), "some text "*1024, # ~10k self._test_leak(lambda: draw.text((0, 0), "some text "*1024, # ~10k
font=font, fill="black")) font=font, fill="black"))
@unittest.skipIf(not features.check('freetype2'), "Test requires freetype2") @unittest.skipIf(not features.check('freetype2'),
"Test requires freetype2")
def test_leak(self): def test_leak(self):
ttype = ImageFont.truetype('Tests/fonts/FreeMono.ttf', 20) ttype = ImageFont.truetype('Tests/fonts/FreeMono.ttf', 20)
self._test_font(ttype) self._test_font(ttype)

View File

@ -294,7 +294,8 @@ int main(int argc, char* argv[])
compiler = ccompiler.new_compiler() compiler = ccompiler.new_compiler()
compiler.add_include_dir(sysconfig.get_python_inc()) compiler.add_include_dir(sysconfig.get_python_inc())
libdir = sysconfig.get_config_var('LIBDIR') or sysconfig.get_python_inc().replace('include', 'libs') libdir = (sysconfig.get_config_var('LIBDIR') or
sysconfig.get_python_inc().replace('include', 'libs'))
print(libdir) print(libdir)
compiler.add_library_dir(libdir) compiler.add_library_dir(libdir)
objects = compiler.compile(['embed_pil.c']) objects = compiler.compile(['embed_pil.c'])

View File

@ -15,9 +15,11 @@ class TestImageArray(PillowTestCase):
self.assertEqual(test("L"), (3, (100, 128), '|u1', 12800)) self.assertEqual(test("L"), (3, (100, 128), '|u1', 12800))
# FIXME: wrong? # FIXME: wrong?
self.assertEqual(test("I"), (3, (100, 128), Image._ENDIAN + 'i4', 51200)) self.assertEqual(test("I"), (3, (100, 128),
Image._ENDIAN + 'i4', 51200))
# FIXME: wrong? # FIXME: wrong?
self.assertEqual(test("F"), (3, (100, 128), Image._ENDIAN + 'f4', 51200)) self.assertEqual(test("F"), (3, (100, 128),
Image._ENDIAN + 'f4', 51200))
self.assertEqual(test("LA"), (3, (100, 128, 2), '|u1', 25600)) self.assertEqual(test("LA"), (3, (100, 128, 2), '|u1', 25600))
self.assertEqual(test("RGB"), (3, (100, 128, 3), '|u1', 38400)) self.assertEqual(test("RGB"), (3, (100, 128, 3), '|u1', 38400))

View File

@ -67,13 +67,13 @@ class TestImageConvert(PillowTestCase):
f = self.tempfile('temp.png') f = self.tempfile('temp.png')
l = im.convert('L') im_l = im.convert('L')
self.assertEqual(l.info['transparency'], 0) # undone self.assertEqual(im_l.info['transparency'], 0) # undone
l.save(f) im_l.save(f)
rgb = im.convert('RGB') im_rgb = im.convert('RGB')
self.assertEqual(rgb.info['transparency'], (0, 0, 0)) # undone self.assertEqual(im_rgb.info['transparency'], (0, 0, 0)) # undone
rgb.save(f) im_rgb.save(f)
# ref https://github.com/python-pillow/Pillow/issues/664 # ref https://github.com/python-pillow/Pillow/issues/664
@ -83,12 +83,12 @@ class TestImageConvert(PillowTestCase):
im.info['transparency'] = 128 im.info['transparency'] = 128
# Act # Act
rgba = im.convert('RGBA') im_rgba = im.convert('RGBA')
# Assert # Assert
self.assertNotIn('transparency', rgba.info) self.assertNotIn('transparency', im_rgba.info)
# https://github.com/python-pillow/Pillow/issues/2702 # https://github.com/python-pillow/Pillow/issues/2702
self.assertEqual(rgba.palette, None) self.assertEqual(im_rgba.palette, None)
def test_trns_l(self): def test_trns_l(self):
im = hopper('L') im = hopper('L')
@ -96,19 +96,20 @@ class TestImageConvert(PillowTestCase):
f = self.tempfile('temp.png') f = self.tempfile('temp.png')
rgb = im.convert('RGB') im_rgb = im.convert('RGB')
self.assertEqual(rgb.info['transparency'], (128, 128, 128)) # undone self.assertEqual(im_rgb.info['transparency'],
rgb.save(f) (128, 128, 128)) # undone
im_rgb.save(f)
p = im.convert('P') im_p = im.convert('P')
self.assertIn('transparency', p.info) self.assertIn('transparency', im_p.info)
p.save(f) im_p.save(f)
p = self.assert_warning( im_p = self.assert_warning(
UserWarning, UserWarning,
im.convert, 'P', palette=Image.ADAPTIVE) im.convert, 'P', palette=Image.ADAPTIVE)
self.assertNotIn('transparency', p.info) self.assertNotIn('transparency', im_p.info)
p.save(f) im_p.save(f)
def test_trns_RGB(self): def test_trns_RGB(self):
im = hopper('RGB') im = hopper('RGB')
@ -116,23 +117,35 @@ class TestImageConvert(PillowTestCase):
f = self.tempfile('temp.png') f = self.tempfile('temp.png')
l = im.convert('L') im_l = im.convert('L')
self.assertEqual(l.info['transparency'], l.getpixel((0, 0))) # undone self.assertEqual(im_l.info['transparency'],
l.save(f) im_l.getpixel((0, 0))) # undone
im_l.save(f)
p = im.convert('P') im_p = im.convert('P')
self.assertIn('transparency', p.info) self.assertIn('transparency', im_p.info)
p.save(f) im_p.save(f)
p = im.convert('RGBA') im_rgba = im.convert('RGBA')
self.assertNotIn('transparency', p.info) self.assertNotIn('transparency', im_rgba.info)
p.save(f) im_rgba.save(f)
p = self.assert_warning( im_p = self.assert_warning(
UserWarning, UserWarning,
im.convert, 'P', palette=Image.ADAPTIVE) im.convert, 'P', palette=Image.ADAPTIVE)
self.assertNotIn('transparency', p.info) self.assertNotIn('transparency', im_p.info)
p.save(f) im_p.save(f)
def test_gif_with_rgba_palette_to_p(self):
# See https://github.com/python-pillow/Pillow/issues/2433
im = Image.open('Tests/images/hopper.gif')
im.info['transparency'] = 255
im.load()
self.assertEqual(im.palette.mode, 'RGBA')
im_p = im.convert('P')
# Should not raise ValueError: unrecognized raw mode
im_p.load()
def test_p_la(self): def test_p_la(self):
im = hopper('RGBA') im = hopper('RGBA')
@ -191,7 +204,8 @@ class TestImageConvert(PillowTestCase):
if converted_im.mode == 'RGB': if converted_im.mode == 'RGB':
self.assert_image_similar(converted_im, target, 3) self.assert_image_similar(converted_im, target, 3)
else: else:
self.assert_image_similar(converted_im, target.getchannel(0), 1) self.assert_image_similar(converted_im,
target.getchannel(0), 1)
matrix_convert('RGB') matrix_convert('RGB')
matrix_convert('L') matrix_convert('L')

View File

@ -94,6 +94,15 @@ class TestImageFilter(PillowTestCase):
self.assertEqual(rankfilter.size, 1) self.assertEqual(rankfilter.size, 1)
self.assertEqual(rankfilter.rank, 2) self.assertEqual(rankfilter.rank, 2)
def test_builtinfilter_p(self):
builtinFilter = ImageFilter.BuiltinFilter()
self.assertRaises(ValueError, builtinFilter.filter, hopper("P"))
def test_kernel_not_enough_coefficients(self):
self.assertRaises(ValueError,
lambda: ImageFilter.Kernel((3, 3), (0, 0)))
def test_consistency_3x3(self): def test_consistency_3x3(self):
source = Image.open("Tests/images/hopper.bmp") source = Image.open("Tests/images/hopper.bmp")
reference = Image.open("Tests/images/hopper_emboss.bmp") reference = Image.open("Tests/images/hopper_emboss.bmp")

View File

@ -245,8 +245,8 @@ class CoreResampleAlphaCorrectTest(PillowTestCase):
for y in range(i.size[1]): for y in range(i.size[1]):
used_colors = {px[x, y][0] for x in range(i.size[0])} used_colors = {px[x, y][0] for x in range(i.size[0])}
self.assertEqual(256, len(used_colors), self.assertEqual(256, len(used_colors),
'All colors should present in resized image. ' 'All colors should present in resized image. '
'Only {} on {} line.'.format(len(used_colors), y)) 'Only {} on {} line.'.format(len(used_colors), y))
@unittest.skip("current implementation isn't precise enough") @unittest.skip("current implementation isn't precise enough")
def test_levels_rgba(self): def test_levels_rgba(self):
@ -288,10 +288,14 @@ class CoreResampleAlphaCorrectTest(PillowTestCase):
def test_dirty_pixels_rgba(self): def test_dirty_pixels_rgba(self):
case = self.make_dirty_case('RGBA', (255, 255, 0, 128), (0, 0, 255, 0)) case = self.make_dirty_case('RGBA', (255, 255, 0, 128), (0, 0, 255, 0))
self.run_dirty_case(case.resize((20, 20), Image.BOX), (255, 255, 0)) self.run_dirty_case(case.resize((20, 20), Image.BOX), (255, 255, 0))
self.run_dirty_case(case.resize((20, 20), Image.BILINEAR), (255, 255, 0)) self.run_dirty_case(case.resize((20, 20), Image.BILINEAR),
self.run_dirty_case(case.resize((20, 20), Image.HAMMING), (255, 255, 0)) (255, 255, 0))
self.run_dirty_case(case.resize((20, 20), Image.BICUBIC), (255, 255, 0)) self.run_dirty_case(case.resize((20, 20), Image.HAMMING),
self.run_dirty_case(case.resize((20, 20), Image.LANCZOS), (255, 255, 0)) (255, 255, 0))
self.run_dirty_case(case.resize((20, 20), Image.BICUBIC),
(255, 255, 0))
self.run_dirty_case(case.resize((20, 20), Image.LANCZOS),
(255, 255, 0))
def test_dirty_pixels_la(self): def test_dirty_pixels_la(self):
case = self.make_dirty_case('LA', (255, 128), (0, 0)) case = self.make_dirty_case('LA', (255, 128), (0, 0))
@ -367,23 +371,28 @@ class CoreResampleCoefficientsTest(PillowTestCase):
im = Image.new('RGBA', (1280, 1280), (0x20, 0x40, 0x60, 0xff)) im = Image.new('RGBA', (1280, 1280), (0x20, 0x40, 0x60, 0xff))
histogram = im.resize((256, 256), Image.BICUBIC).histogram() histogram = im.resize((256, 256), Image.BICUBIC).histogram()
self.assertEqual(histogram[0x100 * 0 + 0x20], 0x10000) # first channel # first channel
self.assertEqual(histogram[0x100 * 1 + 0x40], 0x10000) # second channel self.assertEqual(histogram[0x100 * 0 + 0x20], 0x10000)
self.assertEqual(histogram[0x100 * 2 + 0x60], 0x10000) # third channel # second channel
self.assertEqual(histogram[0x100 * 3 + 0xff], 0x10000) # fourth channel self.assertEqual(histogram[0x100 * 1 + 0x40], 0x10000)
# third channel
self.assertEqual(histogram[0x100 * 2 + 0x60], 0x10000)
# fourth channel
self.assertEqual(histogram[0x100 * 3 + 0xff], 0x10000)
class CoreResampleBoxTest(PillowTestCase): class CoreResampleBoxTest(PillowTestCase):
def test_wrong_arguments(self): def test_wrong_arguments(self):
im = hopper() im = hopper()
for resample in (Image.NEAREST, Image.BOX, Image.BILINEAR, Image.HAMMING, for resample in (Image.NEAREST, Image.BOX, Image.BILINEAR,
Image.BICUBIC, Image.LANCZOS): Image.HAMMING, Image.BICUBIC, Image.LANCZOS):
im.resize((32, 32), resample, (0, 0, im.width, im.height)) im.resize((32, 32), resample, (0, 0, im.width, im.height))
im.resize((32, 32), resample, (20, 20, im.width, im.height)) im.resize((32, 32), resample, (20, 20, im.width, im.height))
im.resize((32, 32), resample, (20, 20, 20, 100)) im.resize((32, 32), resample, (20, 20, 20, 100))
im.resize((32, 32), resample, (20, 20, 100, 20)) im.resize((32, 32), resample, (20, 20, 100, 20))
with self.assertRaisesRegex(TypeError, "must be sequence of length 4"): with self.assertRaisesRegex(TypeError,
"must be sequence of length 4"):
im.resize((32, 32), resample, (im.width, im.height)) im.resize((32, 32), resample, (im.width, im.height))
with self.assertRaisesRegex(ValueError, "can't be negative"): with self.assertRaisesRegex(ValueError, "can't be negative"):

View File

@ -39,15 +39,15 @@ class TestImagingCoreResize(PillowTestCase):
self.assertEqual(r.im.bands, im.im.bands) self.assertEqual(r.im.bands, im.im.bands)
def test_reduce_filters(self): def test_reduce_filters(self):
for f in [Image.NEAREST, Image.BOX, Image.BILINEAR, Image.HAMMING, for f in [Image.NEAREST, Image.BOX, Image.BILINEAR,
Image.BICUBIC, Image.LANCZOS]: Image.HAMMING, Image.BICUBIC, Image.LANCZOS]:
r = self.resize(hopper("RGB"), (15, 12), f) r = self.resize(hopper("RGB"), (15, 12), f)
self.assertEqual(r.mode, "RGB") self.assertEqual(r.mode, "RGB")
self.assertEqual(r.size, (15, 12)) self.assertEqual(r.size, (15, 12))
def test_enlarge_filters(self): def test_enlarge_filters(self):
for f in [Image.NEAREST, Image.BOX, Image.BILINEAR, Image.HAMMING, for f in [Image.NEAREST, Image.BOX, Image.BILINEAR,
Image.BICUBIC, Image.LANCZOS]: Image.HAMMING, Image.BICUBIC, Image.LANCZOS]:
r = self.resize(hopper("RGB"), (212, 195), f) r = self.resize(hopper("RGB"), (212, 195), f)
self.assertEqual(r.mode, "RGB") self.assertEqual(r.mode, "RGB")
self.assertEqual(r.size, (212, 195)) self.assertEqual(r.size, (212, 195))
@ -66,8 +66,8 @@ class TestImagingCoreResize(PillowTestCase):
} }
samples['dirty'].putpixel((1, 1), 128) samples['dirty'].putpixel((1, 1), 128)
for f in [Image.NEAREST, Image.BOX, Image.BILINEAR, Image.HAMMING, for f in [Image.NEAREST, Image.BOX, Image.BILINEAR,
Image.BICUBIC, Image.LANCZOS]: Image.HAMMING, Image.BICUBIC, Image.LANCZOS]:
# samples resized with current filter # samples resized with current filter
references = { references = {
name: self.resize(ch, (4, 4), f) name: self.resize(ch, (4, 4), f)
@ -90,8 +90,8 @@ class TestImagingCoreResize(PillowTestCase):
self.assert_image_equal(ch, references[channels[i]]) self.assert_image_equal(ch, references[channels[i]])
def test_enlarge_zero(self): def test_enlarge_zero(self):
for f in [Image.NEAREST, Image.BOX, Image.BILINEAR, Image.HAMMING, for f in [Image.NEAREST, Image.BOX, Image.BILINEAR,
Image.BICUBIC, Image.LANCZOS]: Image.HAMMING, Image.BICUBIC, Image.LANCZOS]:
r = self.resize(Image.new('RGB', (0, 0), "white"), (212, 195), f) r = self.resize(Image.new('RGB', (0, 0), "white"), (212, 195), f)
self.assertEqual(r.mode, "RGB") self.assertEqual(r.mode, "RGB")
self.assertEqual(r.size, (212, 195)) self.assertEqual(r.size, (212, 195))

View File

@ -95,8 +95,9 @@ class TestImageRotate(PillowTestCase):
im = hopper() im = hopper()
self.rotate(im, im.mode, 45, center=(0, 0)) self.rotate(im, im.mode, 45, center=(0, 0))
self.rotate(im, im.mode, 45, translate=(im.size[0]/2, 0)) self.rotate(im, im.mode, 45, translate=(im.size[0]/2, 0))
self.rotate(im, im.mode, 45, center=(0, 0), translate=(im.size[0]/2, 0)) self.rotate(im, im.mode, 45, center=(0, 0),
translate=(im.size[0]/2, 0))
def test_rotate_no_fill(self): def test_rotate_no_fill(self):
im = Image.new('RGB', (100, 100), 'green') im = Image.new('RGB', (100, 100), 'green')
target = Image.open('Tests/images/rotate_45_no_fill.png') target = Image.open('Tests/images/rotate_45_no_fill.png')

View File

@ -3,6 +3,16 @@ from helper import unittest, PillowTestCase, hopper
from PIL import Image from PIL import Image
from PIL import ImageChops from PIL import ImageChops
BLACK = (0, 0, 0)
BROWN = (127, 64, 0)
CYAN = (0, 255, 255)
DARK_GREEN = (0, 128, 0)
GREEN = (0, 255, 0)
ORANGE = (255, 128, 0)
WHITE = (255, 255, 255)
GREY = 128
class TestImageChops(PillowTestCase): class TestImageChops(PillowTestCase):
@ -35,6 +45,303 @@ class TestImageChops(PillowTestCase):
ImageChops.offset(im, 10) ImageChops.offset(im, 10)
ImageChops.offset(im, 10, 20) ImageChops.offset(im, 10, 20)
def test_add(self):
# Arrange
im1 = Image.open("Tests/images/imagedraw_ellipse_RGB.png")
im2 = Image.open("Tests/images/imagedraw_floodfill.png")
# Act
new = ImageChops.add(im1, im2)
# Assert
self.assertEqual(new.getbbox(), (25, 25, 76, 76))
self.assertEqual(new.getpixel((50, 50)), ORANGE)
def test_add_scale_offset(self):
# Arrange
im1 = Image.open("Tests/images/imagedraw_ellipse_RGB.png")
im2 = Image.open("Tests/images/imagedraw_floodfill.png")
# Act
new = ImageChops.add(im1, im2, scale=2.5, offset=100)
# Assert
self.assertEqual(new.getbbox(), (0, 0, 100, 100))
self.assertEqual(new.getpixel((50, 50)), (202, 151, 100))
def test_add_clip(self):
# Arrange
im = hopper()
# Act
new = ImageChops.add(im, im)
# Assert
self.assertEqual(new.getpixel((50, 50)), (255, 255, 254))
def test_add_modulo(self):
# Arrange
im1 = Image.open("Tests/images/imagedraw_ellipse_RGB.png")
im2 = Image.open("Tests/images/imagedraw_floodfill.png")
# Act
new = ImageChops.add_modulo(im1, im2)
# Assert
self.assertEqual(new.getbbox(), (25, 25, 76, 76))
self.assertEqual(new.getpixel((50, 50)), ORANGE)
def test_add_modulo_no_clip(self):
# Arrange
im = hopper()
# Act
new = ImageChops.add_modulo(im, im)
# Assert
self.assertEqual(new.getpixel((50, 50)), (224, 76, 254))
def test_blend(self):
# Arrange
im1 = Image.open("Tests/images/imagedraw_ellipse_RGB.png")
im2 = Image.open("Tests/images/imagedraw_floodfill.png")
# Act
new = ImageChops.blend(im1, im2, 0.5)
# Assert
self.assertEqual(new.getbbox(), (25, 25, 76, 76))
self.assertEqual(new.getpixel((50, 50)), BROWN)
def test_constant(self):
# Arrange
im = Image.new("RGB", (20, 10))
# Act
new = ImageChops.constant(im, GREY)
# Assert
self.assertEqual(new.size, im.size)
self.assertEqual(new.getpixel((0, 0)), GREY)
self.assertEqual(new.getpixel((19, 9)), GREY)
def test_darker_image(self):
# Arrange
im1 = Image.open("Tests/images/imagedraw_chord_RGB.png")
im2 = Image.open("Tests/images/imagedraw_outline_chord_RGB.png")
# Act
new = ImageChops.darker(im1, im2)
# Assert
self.assert_image_equal(new, im2)
def test_darker_pixel(self):
# Arrange
im1 = hopper()
im2 = Image.open("Tests/images/imagedraw_chord_RGB.png")
# Act
new = ImageChops.darker(im1, im2)
# Assert
self.assertEqual(new.getpixel((50, 50)), (240, 166, 0))
def test_difference(self):
# Arrange
im1 = Image.open("Tests/images/imagedraw_arc_end_le_start.png")
im2 = Image.open("Tests/images/imagedraw_arc_no_loops.png")
# Act
new = ImageChops.difference(im1, im2)
# Assert
self.assertEqual(new.getbbox(), (25, 25, 76, 76))
def test_difference_pixel(self):
# Arrange
im1 = hopper()
im2 = Image.open("Tests/images/imagedraw_polygon_kite_RGB.png")
# Act
new = ImageChops.difference(im1, im2)
# Assert
self.assertEqual(new.getpixel((50, 50)), (240, 166, 128))
def test_duplicate(self):
# Arrange
im = hopper()
# Act
new = ImageChops.duplicate(im)
# Assert
self.assert_image_equal(new, im)
def test_invert(self):
# Arrange
im = Image.open("Tests/images/imagedraw_floodfill.png")
# Act
new = ImageChops.invert(im)
# Assert
self.assertEqual(new.getbbox(), (0, 0, 100, 100))
self.assertEqual(new.getpixel((0, 0)), WHITE)
self.assertEqual(new.getpixel((50, 50)), CYAN)
def test_lighter_image(self):
# Arrange
im1 = Image.open("Tests/images/imagedraw_chord_RGB.png")
im2 = Image.open("Tests/images/imagedraw_outline_chord_RGB.png")
# Act
new = ImageChops.lighter(im1, im2)
# Assert
self.assert_image_equal(new, im1)
def test_lighter_pixel(self):
# Arrange
im1 = hopper()
im2 = Image.open("Tests/images/imagedraw_chord_RGB.png")
# Act
new = ImageChops.lighter(im1, im2)
# Assert
self.assertEqual(new.getpixel((50, 50)), (255, 255, 127))
def test_multiply_black(self):
"""If you multiply an image with a solid black image,
the result is black."""
# Arrange
im1 = hopper()
black = Image.new("RGB", im1.size, "black")
# Act
new = ImageChops.multiply(im1, black)
# Assert
self.assert_image_equal(new, black)
def test_multiply_green(self):
# Arrange
im = Image.open("Tests/images/imagedraw_floodfill.png")
green = Image.new("RGB", im.size, "green")
# Act
new = ImageChops.multiply(im, green)
# Assert
self.assertEqual(new.getbbox(), (25, 25, 76, 76))
self.assertEqual(new.getpixel((25, 25)), DARK_GREEN)
self.assertEqual(new.getpixel((50, 50)), BLACK)
def test_multiply_white(self):
"""If you multiply with a solid white image,
the image is unaffected."""
# Arrange
im1 = hopper()
white = Image.new("RGB", im1.size, "white")
# Act
new = ImageChops.multiply(im1, white)
# Assert
self.assert_image_equal(new, im1)
def test_offset(self):
# Arrange
im = Image.open("Tests/images/imagedraw_ellipse_RGB.png")
xoffset = 45
yoffset = 20
# Act
new = ImageChops.offset(im, xoffset, yoffset)
# Assert
self.assertEqual(new.getbbox(), (0, 45, 100, 96))
self.assertEqual(new.getpixel((50, 50)), BLACK)
self.assertEqual(new.getpixel((50+xoffset, 50+yoffset)), DARK_GREEN)
# Test no yoffset
self.assertEqual(ImageChops.offset(im, xoffset),
ImageChops.offset(im, xoffset, xoffset))
def test_screen(self):
# Arrange
im1 = Image.open("Tests/images/imagedraw_ellipse_RGB.png")
im2 = Image.open("Tests/images/imagedraw_floodfill.png")
# Act
new = ImageChops.screen(im1, im2)
# Assert
self.assertEqual(new.getbbox(), (25, 25, 76, 76))
self.assertEqual(new.getpixel((50, 50)), ORANGE)
def test_subtract(self):
# Arrange
im1 = Image.open("Tests/images/imagedraw_chord_RGB.png")
im2 = Image.open("Tests/images/imagedraw_outline_chord_RGB.png")
# Act
new = ImageChops.subtract(im1, im2)
# Assert
self.assertEqual(new.getbbox(), (25, 50, 76, 76))
self.assertEqual(new.getpixel((50, 50)), GREEN)
self.assertEqual(new.getpixel((50, 51)), BLACK)
def test_subtract_scale_offset(self):
# Arrange
im1 = Image.open("Tests/images/imagedraw_chord_RGB.png")
im2 = Image.open("Tests/images/imagedraw_outline_chord_RGB.png")
# Act
new = ImageChops.subtract(im1, im2, scale=2.5, offset=100)
# Assert
self.assertEqual(new.getbbox(), (0, 0, 100, 100))
self.assertEqual(new.getpixel((50, 50)), (100, 202, 100))
def test_subtract_clip(self):
# Arrange
im1 = hopper()
im2 = Image.open("Tests/images/imagedraw_chord_RGB.png")
# Act
new = ImageChops.subtract(im1, im2)
# Assert
self.assertEqual(new.getpixel((50, 50)), (0, 0, 127))
def test_subtract_modulo(self):
# Arrange
im1 = Image.open("Tests/images/imagedraw_chord_RGB.png")
im2 = Image.open("Tests/images/imagedraw_outline_chord_RGB.png")
# Act
new = ImageChops.subtract_modulo(im1, im2)
# Assert
self.assertEqual(new.getbbox(), (25, 50, 76, 76))
self.assertEqual(new.getpixel((50, 50)), GREEN)
self.assertEqual(new.getpixel((50, 51)), BLACK)
def test_subtract_modulo_no_clip(self):
# Arrange
im1 = hopper()
im2 = Image.open("Tests/images/imagedraw_chord_RGB.png")
# Act
new = ImageChops.subtract_modulo(im1, im2)
# Assert
self.assertEqual(new.getpixel((50, 50)), (241, 167, 127))
def test_logical(self): def test_logical(self):
def table(op, a, b): def table(op, a, b):

View File

@ -196,13 +196,13 @@ class TestImageCms(PillowTestCase):
# not a linear luminance map. so L != 128: # not a linear luminance map. so L != 128:
self.assertEqual(k, (137, 128, 128)) self.assertEqual(k, (137, 128, 128))
l = i_lab.getdata(0) l_data = i_lab.getdata(0)
a = i_lab.getdata(1) a_data = i_lab.getdata(1)
b = i_lab.getdata(2) b_data = i_lab.getdata(2)
self.assertEqual(list(l), [137] * 100) self.assertEqual(list(l_data), [137] * 100)
self.assertEqual(list(a), [128] * 100) self.assertEqual(list(a_data), [128] * 100)
self.assertEqual(list(b), [128] * 100) self.assertEqual(list(b_data), [128] * 100)
def test_lab_color(self): def test_lab_color(self):
psRGB = ImageCms.createProfile("sRGB") psRGB = ImageCms.createProfile("sRGB")
@ -286,53 +286,104 @@ class TestImageCms(PillowTestCase):
self.assertEqual(truncate_tuple(tup1), truncate_tuple(tup2)) self.assertEqual(truncate_tuple(tup1), truncate_tuple(tup2))
self.assertEqual(p.attributes, 4294967296) self.assertEqual(p.attributes, 4294967296)
assert_truncated_tuple_equal(p.blue_colorant, ((0.14306640625, 0.06060791015625, 0.7140960693359375), (0.1558847490315394, 0.06603820639433387, 0.06060791015625))) assert_truncated_tuple_equal(
assert_truncated_tuple_equal(p.blue_primary, ((0.14306641366715667, 0.06060790921083026, 0.7140960805782015), (0.15588475410450106, 0.06603820408959558, 0.06060790921083026))) p.blue_colorant,
((0.14306640625, 0.06060791015625, 0.7140960693359375),
(0.1558847490315394, 0.06603820639433387, 0.06060791015625)))
assert_truncated_tuple_equal(
p.blue_primary,
((0.14306641366715667, 0.06060790921083026, 0.7140960805782015),
(0.15588475410450106, 0.06603820408959558, 0.06060790921083026)))
assert_truncated_tuple_equal(p.chromatic_adaptation, (((1.04791259765625, 0.0229339599609375, -0.050201416015625), (0.02960205078125, 0.9904632568359375, -0.0170745849609375), (-0.009246826171875, 0.0150604248046875, 0.7517852783203125)), ((1.0267159024652783, 0.022470062342089134, 0.0229339599609375), (0.02951378324103937, 0.9875098886387147, 0.9904632568359375), (-0.012205438066465256, 0.01987915407854985, 0.0150604248046875)))) assert_truncated_tuple_equal(p.chromatic_adaptation, (((1.04791259765625, 0.0229339599609375, -0.050201416015625), (0.02960205078125, 0.9904632568359375, -0.0170745849609375), (-0.009246826171875, 0.0150604248046875, 0.7517852783203125)), ((1.0267159024652783, 0.022470062342089134, 0.0229339599609375), (0.02951378324103937, 0.9875098886387147, 0.9904632568359375), (-0.012205438066465256, 0.01987915407854985, 0.0150604248046875))))
self.assertIsNone(p.chromaticity) self.assertIsNone(p.chromaticity)
self.assertEqual(p.clut, {0: (False, False, True), 1: (False, False, True), 2: (False, False, True), 3: (False, False, True)}) self.assertEqual(p.clut, {
0: (False, False, True),
1: (False, False, True),
2: (False, False, True),
3: (False, False, True)
})
self.assertEqual(p.color_space, 'RGB') 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)
self.assertEqual(p.connection_space, 'XYZ ') self.assertEqual(p.connection_space, 'XYZ ')
self.assertEqual(p.copyright, 'Copyright International Color Consortium, 2009') self.assertEqual(p.copyright,
self.assertEqual(p.creation_date, datetime.datetime(2009, 2, 27, 21, 36, 31)) 'Copyright International Color Consortium, 2009')
self.assertEqual(p.creation_date,
datetime.datetime(2009, 2, 27, 21, 36, 31))
self.assertEqual(p.device_class, 'mntr') self.assertEqual(p.device_class, 'mntr')
assert_truncated_tuple_equal(p.green_colorant, ((0.3851470947265625, 0.7168731689453125, 0.097076416015625), (0.32119769927720654, 0.5978443449048152, 0.7168731689453125))) assert_truncated_tuple_equal(
assert_truncated_tuple_equal(p.green_primary, ((0.3851470888162112, 0.7168731974161346, 0.09707641738998518), (0.32119768793686687, 0.5978443567149709, 0.7168731974161346))) p.green_colorant,
((0.3851470947265625, 0.7168731689453125, 0.097076416015625),
(0.32119769927720654, 0.5978443449048152, 0.7168731689453125)))
assert_truncated_tuple_equal(
p.green_primary,
((0.3851470888162112, 0.7168731974161346, 0.09707641738998518),
(0.32119768793686687, 0.5978443567149709, 0.7168731974161346)))
self.assertEqual(p.header_flags, 0) self.assertEqual(p.header_flags, 0)
self.assertEqual(p.header_manufacturer, '\x00\x00\x00\x00') self.assertEqual(p.header_manufacturer, '\x00\x00\x00\x00')
self.assertEqual(p.header_model, '\x00\x00\x00\x00') self.assertEqual(p.header_model, '\x00\x00\x00\x00')
self.assertEqual(p.icc_measurement_condition, {'backing': (0.0, 0.0, 0.0), 'flare': 0.0, 'geo': 'unknown', 'observer': 1, 'illuminant_type': 'D65'}) self.assertEqual(p.icc_measurement_condition, {
'backing': (0.0, 0.0, 0.0),
'flare': 0.0,
'geo': 'unknown',
'observer': 1,
'illuminant_type': 'D65'
})
self.assertEqual(p.icc_version, 33554432) self.assertEqual(p.icc_version, 33554432)
self.assertIsNone(p.icc_viewing_condition) self.assertIsNone(p.icc_viewing_condition)
self.assertEqual(p.intent_supported, {0: (True, True, True), 1: (True, True, True), 2: (True, True, True), 3: (True, True, True)}) self.assertEqual(p.intent_supported, {
0: (True, True, True),
1: (True, True, True),
2: (True, True, True),
3: (True, True, True)
})
self.assertTrue(p.is_matrix_shaper) self.assertTrue(p.is_matrix_shaper)
self.assertEqual(p.luminance, ((0.0, 80.0, 0.0), (0.0, 1.0, 80.0))) self.assertEqual(p.luminance, ((0.0, 80.0, 0.0), (0.0, 1.0, 80.0)))
self.assertIsNone(p.manufacturer) self.assertIsNone(p.manufacturer)
assert_truncated_tuple_equal(p.media_black_point, ((0.012054443359375, 0.0124969482421875, 0.01031494140625), (0.34573304157549234, 0.35842450765864337, 0.0124969482421875))) assert_truncated_tuple_equal(
assert_truncated_tuple_equal(p.media_white_point, ((0.964202880859375, 1.0, 0.8249053955078125), (0.3457029219802284, 0.3585375327567059, 1.0))) p.media_black_point,
assert_truncated_tuple_equal((p.media_white_point_temperature,), (5000.722328847392,)) ((0.012054443359375, 0.0124969482421875, 0.01031494140625),
self.assertEqual(p.model, 'IEC 61966-2-1 Default RGB Colour Space - sRGB') (0.34573304157549234, 0.35842450765864337, 0.0124969482421875)))
assert_truncated_tuple_equal(
p.media_white_point,
((0.964202880859375, 1.0, 0.8249053955078125),
(0.3457029219802284, 0.3585375327567059, 1.0)))
assert_truncated_tuple_equal(
(p.media_white_point_temperature,),
(5000.722328847392,))
self.assertEqual(p.model,
'IEC 61966-2-1 Default RGB Colour Space - sRGB')
self.assertEqual(p.pcs, 'XYZ') 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_copyright,
'Copyright International Color Consortium, 2009')
self.assertEqual(p.product_desc, 'sRGB IEC61966-2-1 black scaled') 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_description,
'sRGB IEC61966-2-1 black scaled')
self.assertEqual(p.product_manufacturer, '') 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.product_model, 'IEC 61966-2-1 Default RGB Colour Space - sRGB')
self.assertEqual(p.profile_id, b')\xf8=\xde\xaf\xf2U\xaexB\xfa\xe4\xca\x839\r') self.assertEqual(
assert_truncated_tuple_equal(p.red_colorant, ((0.436065673828125, 0.2224884033203125, 0.013916015625), (0.6484536316398539, 0.3308524880306778, 0.2224884033203125))) p.profile_description, 'sRGB IEC61966-2-1 black scaled')
assert_truncated_tuple_equal(p.red_primary, ((0.43606566581047446, 0.22248840582960838, 0.013916015621759925), (0.6484536250319214, 0.3308524944738204, 0.22248840582960838))) self.assertEqual(
p.profile_id, b')\xf8=\xde\xaf\xf2U\xaexB\xfa\xe4\xca\x839\r')
assert_truncated_tuple_equal(
p.red_colorant,
((0.436065673828125, 0.2224884033203125, 0.013916015625),
(0.6484536316398539, 0.3308524880306778, 0.2224884033203125)))
assert_truncated_tuple_equal(
p.red_primary,
((0.43606566581047446, 0.22248840582960838, 0.013916015621759925),
(0.6484536250319214, 0.3308524944738204, 0.22248840582960838)))
self.assertEqual(p.rendering_intent, 0) self.assertEqual(p.rendering_intent, 0)
self.assertIsNone(p.saturation_rendering_intent_gamut) self.assertIsNone(p.saturation_rendering_intent_gamut)
self.assertIsNone(p.screening_description) self.assertIsNone(p.screening_description)
self.assertIsNone(p.target) self.assertIsNone(p.target)
self.assertEqual(p.technology, 'CRT ') self.assertEqual(p.technology, 'CRT ')
self.assertEqual(p.version, 2.0) self.assertEqual(p.version, 2.0)
self.assertEqual(p.viewing_condition, 'Reference Viewing Condition in IEC 61966-2-1') self.assertEqual(p.viewing_condition,
'Reference Viewing Condition in IEC 61966-2-1')
self.assertEqual(p.xcolor_space, 'RGB ') self.assertEqual(p.xcolor_space, 'RGB ')
def test_profile_typesafety(self): def test_profile_typesafety(self):
@ -346,7 +397,8 @@ class TestImageCms(PillowTestCase):
with self.assertRaises(TypeError): with self.assertRaises(TypeError):
ImageCms.ImageCmsProfile(1).tobytes() ImageCms.ImageCmsProfile(1).tobytes()
def assert_aux_channel_preserved(self, mode, transform_in_place, preserved_channel): def assert_aux_channel_preserved(self, mode,
transform_in_place, preserved_channel):
def create_test_image(): def create_test_image():
# set up test image with something interesting in the tested aux # set up test image with something interesting in the tested aux
# channel. # channel.
@ -379,29 +431,35 @@ class TestImageCms(PillowTestCase):
# create some transform, it doesn't matter which one # create some transform, it doesn't matter which one
source_profile = ImageCms.createProfile("sRGB") source_profile = ImageCms.createProfile("sRGB")
destination_profile = ImageCms.createProfile("sRGB") destination_profile = ImageCms.createProfile("sRGB")
t = ImageCms.buildTransform(source_profile, destination_profile, inMode=mode, outMode=mode) t = ImageCms.buildTransform(
source_profile, destination_profile, inMode=mode, outMode=mode)
# apply transform # apply transform
if transform_in_place: if transform_in_place:
ImageCms.applyTransform(source_image, t, inPlace=True) ImageCms.applyTransform(source_image, t, inPlace=True)
result_image = source_image result_image = source_image
else: else:
result_image = ImageCms.applyTransform(source_image, t, inPlace=False) result_image = ImageCms.applyTransform(
source_image, t, inPlace=False)
result_image_aux = result_image.getchannel(preserved_channel) result_image_aux = result_image.getchannel(preserved_channel)
self.assert_image_equal(source_image_aux, result_image_aux) self.assert_image_equal(source_image_aux, result_image_aux)
def test_preserve_auxiliary_channels_rgba(self): def test_preserve_auxiliary_channels_rgba(self):
self.assert_aux_channel_preserved(mode='RGBA', transform_in_place=False, preserved_channel='A') self.assert_aux_channel_preserved(mode='RGBA',
transform_in_place=False, preserved_channel='A')
def test_preserve_auxiliary_channels_rgba_in_place(self): def test_preserve_auxiliary_channels_rgba_in_place(self):
self.assert_aux_channel_preserved(mode='RGBA', transform_in_place=True, preserved_channel='A') self.assert_aux_channel_preserved(mode='RGBA',
transform_in_place=True, preserved_channel='A')
def test_preserve_auxiliary_channels_rgbx(self): def test_preserve_auxiliary_channels_rgbx(self):
self.assert_aux_channel_preserved(mode='RGBX', transform_in_place=False, preserved_channel='X') self.assert_aux_channel_preserved(mode='RGBX',
transform_in_place=False, preserved_channel='X')
def test_preserve_auxiliary_channels_rgbx_in_place(self): def test_preserve_auxiliary_channels_rgbx_in_place(self):
self.assert_aux_channel_preserved(mode='RGBX', transform_in_place=True, preserved_channel='X') self.assert_aux_channel_preserved(mode='RGBX',
transform_in_place=True, preserved_channel='X')
def test_auxiliary_channels_isolated(self): def test_auxiliary_channels_isolated(self):
# test data in aux channels does not affect non-aux channels # test data in aux channels does not affect non-aux channels
@ -422,20 +480,29 @@ class TestImageCms(PillowTestCase):
source_profile = ImageCms.createProfile(src_format[1]) source_profile = ImageCms.createProfile(src_format[1])
destination_profile = ImageCms.createProfile(dst_format[1]) destination_profile = ImageCms.createProfile(dst_format[1])
source_image = src_format[3] source_image = src_format[3]
test_transform = ImageCms.buildTransform(source_profile, destination_profile, inMode=src_format[0], outMode=dst_format[0]) test_transform = ImageCms.buildTransform(
source_profile, destination_profile,
inMode=src_format[0], outMode=dst_format[0])
# test conversion from aux-ful source # test conversion from aux-ful source
if transform_in_place: if transform_in_place:
test_image = source_image.copy() test_image = source_image.copy()
ImageCms.applyTransform(test_image, test_transform, inPlace=True) ImageCms.applyTransform(
test_image, test_transform, inPlace=True)
else: else:
test_image = ImageCms.applyTransform(source_image, test_transform, inPlace=False) test_image = ImageCms.applyTransform(
source_image, test_transform, inPlace=False)
# reference conversion from aux-less source # reference conversion from aux-less source
reference_transform = ImageCms.buildTransform(source_profile, destination_profile, inMode=src_format[2], outMode=dst_format[2]) reference_transform = ImageCms.buildTransform(
reference_image = ImageCms.applyTransform(source_image.convert(src_format[2]), reference_transform) source_profile, destination_profile,
inMode=src_format[2], outMode=dst_format[2])
reference_image = ImageCms.applyTransform(
source_image.convert(src_format[2]),
reference_transform)
self.assert_image_equal(test_image.convert(dst_format[2]), reference_image) self.assert_image_equal(test_image.convert(dst_format[2]),
reference_image)
if __name__ == '__main__': if __name__ == '__main__':

View File

@ -31,7 +31,8 @@ class TestImageColor(PillowTestCase):
# case insensitivity # case insensitivity
self.assertEqual(ImageColor.getrgb("#DEF"), ImageColor.getrgb("#def")) self.assertEqual(ImageColor.getrgb("#DEF"), ImageColor.getrgb("#def"))
self.assertEqual(ImageColor.getrgb("#CDEF"), ImageColor.getrgb("#cdef")) self.assertEqual(ImageColor.getrgb("#CDEF"),
ImageColor.getrgb("#cdef"))
self.assertEqual(ImageColor.getrgb("#DEFDEF"), self.assertEqual(ImageColor.getrgb("#DEFDEF"),
ImageColor.getrgb("#defdef")) ImageColor.getrgb("#defdef"))
self.assertEqual(ImageColor.getrgb("#CDEFCDEF"), self.assertEqual(ImageColor.getrgb("#CDEFCDEF"),
@ -80,18 +81,23 @@ class TestImageColor(PillowTestCase):
self.assertEqual((255, 0, 0), ImageColor.getrgb("hsv(0,100%,100%)")) self.assertEqual((255, 0, 0), ImageColor.getrgb("hsv(0,100%,100%)"))
self.assertEqual((255, 0, 0), ImageColor.getrgb("hsv(360,100%,100%)")) self.assertEqual((255, 0, 0), ImageColor.getrgb("hsv(360,100%,100%)"))
self.assertEqual((0, 255, 255), ImageColor.getrgb("hsv(180,100%,100%)")) self.assertEqual((0, 255, 255),
ImageColor.getrgb("hsv(180,100%,100%)"))
# alternate format # alternate format
self.assertEqual(ImageColor.getrgb("hsb(0,100%,50%)"), self.assertEqual(ImageColor.getrgb("hsb(0,100%,50%)"),
ImageColor.getrgb("hsv(0,100%,50%)")) ImageColor.getrgb("hsv(0,100%,50%)"))
# floats # floats
self.assertEqual((254, 3, 3), ImageColor.getrgb("hsl(0.1,99.2%,50.3%)")) self.assertEqual((254, 3, 3),
self.assertEqual((255, 0, 0), ImageColor.getrgb("hsl(360.,100.0%,50%)")) ImageColor.getrgb("hsl(0.1,99.2%,50.3%)"))
self.assertEqual((255, 0, 0),
ImageColor.getrgb("hsl(360.,100.0%,50%)"))
self.assertEqual((253, 2, 2), ImageColor.getrgb("hsv(0.1,99.2%,99.3%)")) self.assertEqual((253, 2, 2),
self.assertEqual((255, 0, 0), ImageColor.getrgb("hsv(360.,100.0%,100%)")) ImageColor.getrgb("hsv(0.1,99.2%,99.3%)"))
self.assertEqual((255, 0, 0),
ImageColor.getrgb("hsv(360.,100.0%,100%)"))
# case insensitivity # case insensitivity
self.assertEqual(ImageColor.getrgb("RGB(255,0,0)"), self.assertEqual(ImageColor.getrgb("RGB(255,0,0)"),

View File

@ -366,6 +366,11 @@ class TestImageDraw(PillowTestCase):
ImageDraw.floodfill(im, (W, H), red) ImageDraw.floodfill(im, (W, H), red)
self.assert_image_equal(im, im_floodfill) self.assert_image_equal(im, im_floodfill)
# Test filling at the edge of an image
im = Image.new("RGB", (1, 1))
ImageDraw.floodfill(im, (0, 0), red)
self.assert_image_equal(im, Image.new("RGB", (1, 1), red))
def test_floodfill_border(self): def test_floodfill_border(self):
# floodfill() is experimental # floodfill() is experimental
@ -576,6 +581,47 @@ class TestImageDraw(PillowTestCase):
draw.textsize("\n") draw.textsize("\n")
draw.textsize("test\n") draw.textsize("test\n")
def test_same_color_outline(self):
# Prepare shape
x0, y0 = 5, 5
x1, y1 = 5, 50
x2, y2 = 95, 50
x3, y3 = 95, 5
s = ImageDraw.Outline()
s.move(x0, y0)
s.curve(x1, y1, x2, y2, x3, y3)
s.line(x0, y0)
# Begin
for mode in ["RGB", "L"]:
for fill, outline in [
["red", None],
["red", "red"],
["red", "#f00"]
]:
for operation, args in {
'chord':[BBOX1, 0, 180],
'ellipse':[BBOX1],
'shape':[s],
'pieslice':[BBOX1, -90, 45],
'polygon':[[(18, 30), (85, 30), (60, 72)]],
'rectangle':[BBOX1]
}.items():
# Arrange
im = Image.new(mode, (W, H))
draw = ImageDraw.Draw(im)
# Act
draw_method = getattr(draw, operation)
args += [fill, outline]
draw_method(*args)
# Assert
expected = ("Tests/images/imagedraw_outline"
"_{}_{}.png".format(operation, mode))
self.assert_image_similar(im, Image.open(expected), 1)
if __name__ == '__main__': if __name__ == '__main__':
unittest.main() unittest.main()

View File

@ -45,8 +45,9 @@ class TestImageEnhance(PillowTestCase):
for op in ['Color', 'Brightness', 'Contrast', 'Sharpness']: for op in ['Color', 'Brightness', 'Contrast', 'Sharpness']:
for amount in [0, 0.5, 1.0]: for amount in [0, 0.5, 1.0]:
self._check_alpha(getattr(ImageEnhance, op)(original).enhance(amount), self._check_alpha(
original, op, amount) getattr(ImageEnhance, op)(original).enhance(amount),
original, op, amount)
if __name__ == '__main__': if __name__ == '__main__':

View File

@ -215,12 +215,16 @@ class TestPyDecoder(PillowTestCase):
buf = BytesIO(b'\x00'*255) buf = BytesIO(b'\x00'*255)
im = MockImageFile(buf) im = MockImageFile(buf)
im.tile = [("MOCK", (xoff, yoff, xoff+xsize + 100, yoff+ysize), 32, None)] im.tile = [
("MOCK", (xoff, yoff, xoff+xsize + 100, yoff+ysize), 32, None)
]
d = self.get_decoder() d = self.get_decoder()
self.assertRaises(ValueError, im.load) self.assertRaises(ValueError, im.load)
im.tile = [("MOCK", (xoff, yoff, xoff+xsize, yoff+ysize + 100), 32, None)] im.tile = [
("MOCK", (xoff, yoff, xoff+xsize, yoff+ysize + 100), 32, None)
]
self.assertRaises(ValueError, im.load) self.assertRaises(ValueError, im.load)
def test_no_format(self): def test_no_format(self):

View File

@ -67,8 +67,11 @@ class TestImageFont(PillowTestCase):
} }
def setUp(self): def setUp(self):
freetype_version = tuple(ImageFont.core.freetype2_version.split('.'))[:2] freetype_version = tuple(
self.metrics = self.METRICS.get(freetype_version, self.METRICS['Default']) ImageFont.core.freetype2_version.split('.')
)[:2]
self.metrics = self.METRICS.get(freetype_version,
self.METRICS['Default'])
def get_font(self): def get_font(self):
return ImageFont.truetype(FONT_PATH, FONT_SIZE, return ImageFont.truetype(FONT_PATH, FONT_SIZE,
@ -202,7 +205,8 @@ class TestImageFont(PillowTestCase):
target_img = Image.open(target) target_img = Image.open(target)
# Epsilon ~.5 fails with FreeType 2.7 # Epsilon ~.5 fails with FreeType 2.7
self.assert_image_similar(im, target_img, self.metrics['multiline']) self.assert_image_similar(im, target_img,
self.metrics['multiline'])
def test_unknown_align(self): def test_unknown_align(self):
im = Image.new(mode='RGB', size=(300, 100)) im = Image.new(mode='RGB', size=(300, 100))
@ -210,10 +214,9 @@ class TestImageFont(PillowTestCase):
ttf = self.get_font() ttf = self.get_font()
# Act/Assert # Act/Assert
self.assertRaises(AssertionError, self.assertRaises(
draw.multiline_text, (0, 0), TEST_TEXT, AssertionError,
font=ttf, draw.multiline_text, (0, 0), TEST_TEXT, font=ttf, align="unknown")
align="unknown")
def test_draw_align(self): def test_draw_align(self):
im = Image.new('RGB', (300, 100), 'white') im = Image.new('RGB', (300, 100), 'white')
@ -413,7 +416,7 @@ class TestImageFont(PillowTestCase):
im = Image.new(mode='RGB', size=(300, 100)) im = Image.new(mode='RGB', size=(300, 100))
target = im.copy() target = im.copy()
draw = ImageDraw.Draw(im) draw = ImageDraw.Draw(im)
#should not crash here. # should not crash here.
draw.text((10, 10), '', font=font) draw.text((10, 10), '', font=font)
self.assert_image_equal(im, target) self.assert_image_equal(im, target)
@ -428,7 +431,8 @@ class TestImageFont(PillowTestCase):
# Make a copy of FreeTypeFont so we can patch the original # Make a copy of FreeTypeFont so we can patch the original
free_type_font = copy.deepcopy(ImageFont.FreeTypeFont) free_type_font = copy.deepcopy(ImageFont.FreeTypeFont)
with SimplePatcher(ImageFont, '_FreeTypeFont', free_type_font): with SimplePatcher(ImageFont, '_FreeTypeFont', free_type_font):
def loadable_font(filepath, size, index, encoding, *args, **kwargs): def loadable_font(filepath, size, index, encoding,
*args, **kwargs):
if filepath == path_to_fake: if filepath == path_to_fake:
return ImageFont._FreeTypeFont(FONT_PATH, size, index, return ImageFont._FreeTypeFont(FONT_PATH, size, index,
encoding, *args, **kwargs) encoding, *args, **kwargs)

View File

@ -19,7 +19,8 @@ class TestImageFontBitmap(PillowTestCase):
font='Tests/fonts/DejaVuSans-bitmap.ttf', size=24) font='Tests/fonts/DejaVuSans-bitmap.ttf', size=24)
size_outline = font_outline.getsize(text) size_outline = font_outline.getsize(text)
size_bitmap = font_bitmap.getsize(text) size_bitmap = font_bitmap.getsize(text)
size_final = max(size_outline[0], size_bitmap[0]), max(size_outline[1], size_bitmap[1]) size_final = (max(size_outline[0], size_bitmap[0]),
max(size_outline[1], size_bitmap[1]))
im_bitmap = Image.new('RGB', size_final, (255, 255, 255)) im_bitmap = Image.new('RGB', size_final, (255, 255, 255))
im_outline = im_bitmap.copy() im_outline = im_bitmap.copy()
draw_bitmap = ImageDraw.Draw(im_bitmap) draw_bitmap = ImageDraw.Draw(im_bitmap)

View File

@ -11,7 +11,7 @@ FONT_PATH = "Tests/fonts/DejaVuSans.ttf"
class TestImagecomplextext(PillowTestCase): class TestImagecomplextext(PillowTestCase):
def test_english(self): def test_english(self):
#smoke test, this should not fail # smoke test, this should not fail
ttf = ImageFont.truetype(FONT_PATH, FONT_SIZE) ttf = ImageFont.truetype(FONT_PATH, FONT_SIZE)
im = Image.new(mode='RGB', size=(300, 100)) im = Image.new(mode='RGB', size=(300, 100))
draw = ImageDraw.Draw(im) draw = ImageDraw.Draw(im)
@ -30,7 +30,8 @@ class TestImagecomplextext(PillowTestCase):
self.assert_image_similar(im, target_img, .5) self.assert_image_similar(im, target_img, .5)
def test_y_offset(self): def test_y_offset(self):
ttf = ImageFont.truetype("Tests/fonts/NotoNastaliqUrdu-Regular.ttf", FONT_SIZE) ttf = ImageFont.truetype("Tests/fonts/NotoNastaliqUrdu-Regular.ttf",
FONT_SIZE)
im = Image.new(mode='RGB', size=(300, 100)) im = Image.new(mode='RGB', size=(300, 100))
draw = ImageDraw.Draw(im) draw = ImageDraw.Draw(im)
@ -70,7 +71,8 @@ class TestImagecomplextext(PillowTestCase):
im = Image.new(mode='RGB', size=(300, 100)) im = Image.new(mode='RGB', size=(300, 100))
draw = ImageDraw.Draw(im) draw = ImageDraw.Draw(im)
draw.text((0, 0), 'سلطنة عمان Oman', font=ttf, fill=500, direction='ltr') draw.text((0, 0), 'سلطنة عمان Oman',
font=ttf, fill=500, direction='ltr')
target = 'Tests/images/test_direction_ltr.png' target = 'Tests/images/test_direction_ltr.png'
target_img = Image.open(target) target_img = Image.open(target)
@ -82,7 +84,8 @@ class TestImagecomplextext(PillowTestCase):
im = Image.new(mode='RGB', size=(300, 100)) im = Image.new(mode='RGB', size=(300, 100))
draw = ImageDraw.Draw(im) draw = ImageDraw.Draw(im)
draw.text((0, 0), 'Oman سلطنة عمان', font=ttf, fill=500, direction='rtl') draw.text((0, 0), 'Oman سلطنة عمان',
font=ttf, fill=500, direction='rtl')
target = 'Tests/images/test_direction_ltr.png' target = 'Tests/images/test_direction_ltr.png'
target_img = Image.open(target) target_img = Image.open(target)
@ -120,7 +123,8 @@ class TestImagecomplextext(PillowTestCase):
im = Image.new(mode='RGB', size=(300, 100)) im = Image.new(mode='RGB', size=(300, 100))
draw = ImageDraw.Draw(im) draw = ImageDraw.Draw(im)
draw.text((0, 0), 'اللغة العربية', font=ttf, fill=500, features=['-fina', '-init', '-medi']) draw.text((0, 0), 'اللغة العربية', font=ttf, fill=500,
features=['-fina', '-init', '-medi'])
target = 'Tests/images/test_arabictext_features.png' target = 'Tests/images/test_arabictext_features.png'
target_img = Image.open(target) target_img = Image.open(target)

View File

@ -12,11 +12,6 @@ try:
im = ImageGrab.grab() im = ImageGrab.grab()
self.assert_image(im, im.mode, im.size) self.assert_image(im, im.mode, im.size)
@unittest.skipIf(on_appveyor(), "Test fails on appveyor")
def test_grab2(self):
im = ImageGrab.grab()
self.assert_image(im, im.mode, im.size)
except ImportError: except ImportError:
class TestImageGrab(PillowTestCase): class TestImageGrab(PillowTestCase):
def test_skip(self): def test_skip(self):

View File

@ -265,8 +265,9 @@ class MorphTests(PillowTestCase):
# Act / Assert # Act / Assert
with self.assertRaises(Exception) as e: with self.assertRaises(Exception) as e:
lb.build_lut() lb.build_lut()
self.assertEqual(str(e.exception), self.assertEqual(
'Syntax error in pattern "a pattern with a syntax error"') str(e.exception),
'Syntax error in pattern "a pattern with a syntax error"')
def test_load_invalid_mrl(self): def test_load_invalid_mrl(self):
# Arrange # Arrange

View File

@ -1,6 +1,7 @@
from helper import unittest, PillowTestCase, hopper from helper import unittest, PillowTestCase, hopper
from PIL import ImageOps from PIL import ImageOps
from PIL import Image
class TestImageOps(PillowTestCase): class TestImageOps(PillowTestCase):
@ -94,6 +95,107 @@ class TestImageOps(PillowTestCase):
newimg = ImageOps.scale(i, 0.5) newimg = ImageOps.scale(i, 0.5)
self.assertEqual(newimg.size, (25, 25)) self.assertEqual(newimg.size, (25, 25))
def test_colorize_2color(self):
# Test the colorizing function with 2-color functionality
# Open test image (256px by 10px, black to white)
im = Image.open("Tests/images/bw_gradient.png")
im = im.convert("L")
# Create image with original 2-color functionality
im_test = ImageOps.colorize(im, 'red', 'green')
# Test output image (2-color)
left = (0, 1)
middle = (127, 1)
right = (255, 1)
self.assert_tuple_approx_equal(im_test.getpixel(left),
(255, 0, 0),
threshold=1,
msg='black test pixel incorrect')
self.assert_tuple_approx_equal(im_test.getpixel(middle),
(127, 63, 0),
threshold=1,
msg='mid test pixel incorrect')
self.assert_tuple_approx_equal(im_test.getpixel(right),
(0, 127, 0),
threshold=1,
msg='white test pixel incorrect')
def test_colorize_2color_offset(self):
# Test the colorizing function with 2-color functionality and offset
# Open test image (256px by 10px, black to white)
im = Image.open("Tests/images/bw_gradient.png")
im = im.convert("L")
# Create image with original 2-color functionality with offsets
im_test = ImageOps.colorize(im,
black='red',
white='green',
blackpoint=50,
whitepoint=100)
# Test output image (2-color) with offsets
left = (25, 1)
middle = (75, 1)
right = (125, 1)
self.assert_tuple_approx_equal(im_test.getpixel(left),
(255, 0, 0),
threshold=1,
msg='black test pixel incorrect')
self.assert_tuple_approx_equal(im_test.getpixel(middle),
(127, 63, 0),
threshold=1,
msg='mid test pixel incorrect')
self.assert_tuple_approx_equal(im_test.getpixel(right),
(0, 127, 0),
threshold=1,
msg='white test pixel incorrect')
def test_colorize_3color_offset(self):
# Test the colorizing function with 3-color functionality and offset
# Open test image (256px by 10px, black to white)
im = Image.open("Tests/images/bw_gradient.png")
im = im.convert("L")
# Create image with new three color functionality with offsets
im_test = ImageOps.colorize(im,
black='red',
white='green',
mid='blue',
blackpoint=50,
whitepoint=200,
midpoint=100)
# Test output image (3-color) with offsets
left = (25, 1)
left_middle = (75, 1)
middle = (100, 1)
right_middle = (150, 1)
right = (225, 1)
self.assert_tuple_approx_equal(im_test.getpixel(left),
(255, 0, 0),
threshold=1,
msg='black test pixel incorrect')
self.assert_tuple_approx_equal(im_test.getpixel(left_middle),
(127, 0, 127),
threshold=1,
msg='low-mid test pixel incorrect')
self.assert_tuple_approx_equal(im_test.getpixel(middle),
(0, 0, 255),
threshold=1,
msg='mid incorrect')
self.assert_tuple_approx_equal(im_test.getpixel(right_middle),
(0, 63, 127),
threshold=1,
msg='high-mid test pixel incorrect')
self.assert_tuple_approx_equal(im_test.getpixel(right),
(0, 127, 0),
threshold=1,
msg='white test pixel incorrect')
if __name__ == '__main__': if __name__ == '__main__':
unittest.main() unittest.main()

View File

@ -13,27 +13,25 @@ class TestImageOpsUsm(PillowTestCase):
def test_ops_api(self): def test_ops_api(self):
i = self.assert_warning(DeprecationWarning, i = self.assert_warning(DeprecationWarning,
ImageOps.gaussian_blur, im, 2.0) ImageOps.gaussian_blur, im, 2.0)
self.assertEqual(i.mode, "RGB")
self.assertEqual(i.size, (128, 128))
i = self.assert_warning(DeprecationWarning, ImageOps.box_blur, im, 1)
self.assertEqual(i.mode, "RGB")
self.assertEqual(i.size, (128, 128))
i = self.assert_warning(DeprecationWarning, ImageOps.gblur, im, 2.0)
self.assertEqual(i.mode, "RGB") self.assertEqual(i.mode, "RGB")
self.assertEqual(i.size, (128, 128)) self.assertEqual(i.size, (128, 128))
i = self.assert_warning(DeprecationWarning, i = self.assert_warning(DeprecationWarning,
ImageOps.box_blur, im, 1) ImageOps.unsharp_mask, im, 2.0, 125, 8)
self.assertEqual(i.mode, "RGB") self.assertEqual(i.mode, "RGB")
self.assertEqual(i.size, (128, 128)) self.assertEqual(i.size, (128, 128))
i = self.assert_warning(DeprecationWarning, i = self.assert_warning(DeprecationWarning,
ImageOps.gblur, im, 2.0) ImageOps.usm, im, 2.0, 125, 8)
self.assertEqual(i.mode, "RGB")
self.assertEqual(i.size, (128, 128))
i = self.assert_warning(DeprecationWarning,
ImageOps.unsharp_mask, im, 2.0, 125, 8)
self.assertEqual(i.mode, "RGB")
self.assertEqual(i.size, (128, 128))
i = self.assert_warning(DeprecationWarning,
ImageOps.usm, im, 2.0, 125, 8)
self.assertEqual(i.mode, "RGB") self.assertEqual(i.mode, "RGB")
self.assertEqual(i.size, (128, 128)) self.assertEqual(i.size, (128, 128))

View File

@ -9,7 +9,7 @@ class TestImagePalette(PillowTestCase):
ImagePalette.ImagePalette("RGB", list(range(256))*3) ImagePalette.ImagePalette("RGB", list(range(256))*3)
self.assertRaises(ValueError, self.assertRaises(ValueError,
ImagePalette.ImagePalette, "RGB", list(range(256))*2) ImagePalette.ImagePalette, "RGB", list(range(256))*2)
def test_getcolor(self): def test_getcolor(self):
@ -66,7 +66,7 @@ class TestImagePalette(PillowTestCase):
# Act # Act
self.assertRaises(NotImplementedError, self.assertRaises(NotImplementedError,
ImagePalette.make_linear_lut, black, white) ImagePalette.make_linear_lut, black, white)
def test_make_gamma_lut(self): def test_make_gamma_lut(self):
# Arrange # Arrange
@ -133,7 +133,7 @@ class TestImagePalette(PillowTestCase):
def test_invalid_palette(self): def test_invalid_palette(self):
self.assertRaises(IOError, self.assertRaises(IOError,
ImagePalette.load, "Tests/images/hopper.jpg") ImagePalette.load, "Tests/images/hopper.jpg")
if __name__ == '__main__': if __name__ == '__main__':

View File

@ -34,7 +34,8 @@ class TestLibPack(PillowTestCase):
self.assert_pack("1", "1;R", b'\xaa', 0, X, 0, X, 0, X, 0, X) self.assert_pack("1", "1;R", b'\xaa', 0, X, 0, X, 0, X, 0, X)
self.assert_pack("1", "1;IR", b'\xaa', X, 0, X, 0, X, 0, X, 0) self.assert_pack("1", "1;IR", b'\xaa', X, 0, X, 0, X, 0, X, 0)
self.assert_pack("1", "L", b'\xff\x00\x00\xff\x00\x00', X, 0, 0, X, 0, 0) self.assert_pack(
"1", "L", b'\xff\x00\x00\xff\x00\x00', X, 0, 0, X, 0, 0)
def test_L(self): def test_L(self):
self.assert_pack("L", "L", 1, 1, 2, 3, 4) self.assert_pack("L", "L", 1, 1, 2, 3, 4)
@ -57,14 +58,18 @@ class TestLibPack(PillowTestCase):
def test_RGB(self): def test_RGB(self):
self.assert_pack("RGB", "RGB", 3, (1, 2, 3), (4, 5, 6), (7, 8, 9)) self.assert_pack("RGB", "RGB", 3, (1, 2, 3), (4, 5, 6), (7, 8, 9))
self.assert_pack("RGB", "RGBX", self.assert_pack(
"RGB", "RGBX",
b'\x01\x02\x03\xff\x05\x06\x07\xff', (1, 2, 3), (5, 6, 7)) b'\x01\x02\x03\xff\x05\x06\x07\xff', (1, 2, 3), (5, 6, 7))
self.assert_pack("RGB", "XRGB", self.assert_pack(
"RGB", "XRGB",
b'\x00\x02\x03\x04\x00\x06\x07\x08', (2, 3, 4), (6, 7, 8)) b'\x00\x02\x03\x04\x00\x06\x07\x08', (2, 3, 4), (6, 7, 8))
self.assert_pack("RGB", "BGR", 3, (3, 2, 1), (6, 5, 4), (9, 8, 7)) self.assert_pack("RGB", "BGR", 3, (3, 2, 1), (6, 5, 4), (9, 8, 7))
self.assert_pack("RGB", "BGRX", self.assert_pack(
"RGB", "BGRX",
b'\x01\x02\x03\x00\x05\x06\x07\x00', (3, 2, 1), (7, 6, 5)) b'\x01\x02\x03\x00\x05\x06\x07\x00', (3, 2, 1), (7, 6, 5))
self.assert_pack("RGB", "XBGR", self.assert_pack(
"RGB", "XBGR",
b'\x00\x02\x03\x04\x00\x06\x07\x08', (4, 3, 2), (8, 7, 6)) b'\x00\x02\x03\x04\x00\x06\x07\x08', (4, 3, 2), (8, 7, 6))
self.assert_pack("RGB", "RGB;L", 3, (1, 4, 7), (2, 5, 8), (3, 6, 9)) self.assert_pack("RGB", "RGB;L", 3, (1, 4, 7), (2, 5, 8), (3, 6, 9))
self.assert_pack("RGB", "R", 1, (1, 9, 9), (2, 9, 9), (3, 9, 9)) self.assert_pack("RGB", "R", 1, (1, 9, 9), (2, 9, 9), (3, 9, 9))
@ -72,71 +77,98 @@ class TestLibPack(PillowTestCase):
self.assert_pack("RGB", "B", 1, (9, 9, 1), (9, 9, 2), (9, 9, 3)) self.assert_pack("RGB", "B", 1, (9, 9, 1), (9, 9, 2), (9, 9, 3))
def test_RGBA(self): def test_RGBA(self):
self.assert_pack("RGBA", "RGBA", 4, self.assert_pack(
(1, 2, 3, 4), (5, 6, 7, 8), (9, 10, 11, 12)) "RGBA", "RGBA", 4, (1, 2, 3, 4), (5, 6, 7, 8), (9, 10, 11, 12))
self.assert_pack("RGBA", "RGBA;L", 4, self.assert_pack(
(1, 4, 7, 10), (2, 5, 8, 11), (3, 6, 9, 12)) "RGBA", "RGBA;L", 4, (1, 4, 7, 10), (2, 5, 8, 11), (3, 6, 9, 12))
self.assert_pack("RGBA", "RGB", 3, (1, 2, 3, 14), (4, 5, 6, 15), (7, 8, 9, 16)) self.assert_pack(
self.assert_pack("RGBA", "BGR", 3, (3, 2, 1, 14), (6, 5, 4, 15), (9, 8, 7, 16)) "RGBA", "RGB", 3, (1, 2, 3, 14), (4, 5, 6, 15), (7, 8, 9, 16))
self.assert_pack("RGBA", "BGRA", 4, self.assert_pack(
"RGBA", "BGR", 3, (3, 2, 1, 14), (6, 5, 4, 15), (9, 8, 7, 16))
self.assert_pack(
"RGBA", "BGRA", 4,
(3, 2, 1, 4), (7, 6, 5, 8), (11, 10, 9, 12)) (3, 2, 1, 4), (7, 6, 5, 8), (11, 10, 9, 12))
self.assert_pack("RGBA", "ABGR", 4, self.assert_pack(
(4, 3, 2, 1), (8, 7, 6, 5), (12, 11, 10, 9)) "RGBA", "ABGR", 4, (4, 3, 2, 1), (8, 7, 6, 5), (12, 11, 10, 9))
self.assert_pack("RGBA", "BGRa", 4, self.assert_pack(
"RGBA", "BGRa", 4,
(191, 127, 63, 4), (223, 191, 159, 8), (233, 212, 191, 12)) (191, 127, 63, 4), (223, 191, 159, 8), (233, 212, 191, 12))
self.assert_pack("RGBA", "R", 1, (1, 0, 8, 9), (2, 0, 8, 9), (3, 0, 8, 0)) self.assert_pack(
self.assert_pack("RGBA", "G", 1, (6, 1, 8, 9), (6, 2, 8, 9), (6, 3, 8, 9)) "RGBA", "R", 1, (1, 0, 8, 9), (2, 0, 8, 9), (3, 0, 8, 0))
self.assert_pack("RGBA", "B", 1, (6, 7, 1, 9), (6, 7, 2, 0), (6, 7, 3, 9)) self.assert_pack(
self.assert_pack("RGBA", "A", 1, (6, 7, 0, 1), (6, 7, 0, 2), (0, 7, 0, 3)) "RGBA", "G", 1, (6, 1, 8, 9), (6, 2, 8, 9), (6, 3, 8, 9))
self.assert_pack(
"RGBA", "B", 1, (6, 7, 1, 9), (6, 7, 2, 0), (6, 7, 3, 9))
self.assert_pack(
"RGBA", "A", 1, (6, 7, 0, 1), (6, 7, 0, 2), (0, 7, 0, 3))
def test_RGBa(self): def test_RGBa(self):
self.assert_pack("RGBa", "RGBa", 4, self.assert_pack(
(1, 2, 3, 4), (5, 6, 7, 8), (9, 10, 11, 12)) "RGBa", "RGBa", 4, (1, 2, 3, 4), (5, 6, 7, 8), (9, 10, 11, 12))
self.assert_pack("RGBa", "BGRa", 4, self.assert_pack(
(3, 2, 1, 4), (7, 6, 5, 8), (11, 10, 9, 12)) "RGBa", "BGRa", 4, (3, 2, 1, 4), (7, 6, 5, 8), (11, 10, 9, 12))
self.assert_pack("RGBa", "aBGR", 4, self.assert_pack(
(4, 3, 2, 1), (8, 7, 6, 5), (12, 11, 10, 9)) "RGBa", "aBGR", 4, (4, 3, 2, 1), (8, 7, 6, 5), (12, 11, 10, 9))
def test_RGBX(self): def test_RGBX(self):
self.assert_pack("RGBX", "RGBX", 4, (1, 2, 3, 4), (5, 6, 7, 8), (9, 10, 11, 12)) self.assert_pack(
self.assert_pack("RGBX", "RGBX;L", 4, (1, 4, 7, 10), (2, 5, 8, 11), (3, 6, 9, 12)) "RGBX", "RGBX", 4, (1, 2, 3, 4), (5, 6, 7, 8), (9, 10, 11, 12))
self.assert_pack("RGBX", "RGB", 3, (1, 2, 3, X), (4, 5, 6, X), (7, 8, 9, X)) self.assert_pack(
self.assert_pack("RGBX", "BGR", 3, (3, 2, 1, X), (6, 5, 4, X), (9, 8, 7, X)) "RGBX", "RGBX;L", 4, (1, 4, 7, 10), (2, 5, 8, 11), (3, 6, 9, 12))
self.assert_pack("RGBX", "BGRX", self.assert_pack(
"RGBX", "RGB", 3, (1, 2, 3, X), (4, 5, 6, X), (7, 8, 9, X))
self.assert_pack(
"RGBX", "BGR", 3, (3, 2, 1, X), (6, 5, 4, X), (9, 8, 7, X))
self.assert_pack(
"RGBX", "BGRX",
b'\x01\x02\x03\x00\x05\x06\x07\x00\t\n\x0b\x00', b'\x01\x02\x03\x00\x05\x06\x07\x00\t\n\x0b\x00',
(3, 2, 1, X), (7, 6, 5, X), (11, 10, 9, X)) (3, 2, 1, X), (7, 6, 5, X), (11, 10, 9, X))
self.assert_pack("RGBX", "XBGR", self.assert_pack(
"RGBX", "XBGR",
b'\x00\x02\x03\x04\x00\x06\x07\x08\x00\n\x0b\x0c', b'\x00\x02\x03\x04\x00\x06\x07\x08\x00\n\x0b\x0c',
(4, 3, 2, X), (8, 7, 6, X), (12, 11, 10, X)) (4, 3, 2, X), (8, 7, 6, X), (12, 11, 10, X))
self.assert_pack("RGBX", "R", 1, (1, 0, 8, 9), (2, 0, 8, 9), (3, 0, 8, 0)) self.assert_pack("RGBX", "R", 1,
self.assert_pack("RGBX", "G", 1, (6, 1, 8, 9), (6, 2, 8, 9), (6, 3, 8, 9)) (1, 0, 8, 9), (2, 0, 8, 9), (3, 0, 8, 0))
self.assert_pack("RGBX", "B", 1, (6, 7, 1, 9), (6, 7, 2, 0), (6, 7, 3, 9)) self.assert_pack("RGBX", "G", 1,
self.assert_pack("RGBX", "X", 1, (6, 7, 0, 1), (6, 7, 0, 2), (0, 7, 0, 3)) (6, 1, 8, 9), (6, 2, 8, 9), (6, 3, 8, 9))
self.assert_pack("RGBX", "B", 1,
(6, 7, 1, 9), (6, 7, 2, 0), (6, 7, 3, 9))
self.assert_pack("RGBX", "X", 1,
(6, 7, 0, 1), (6, 7, 0, 2), (0, 7, 0, 3))
def test_CMYK(self): def test_CMYK(self):
self.assert_pack("CMYK", "CMYK", 4, (1, 2, 3, 4), (5, 6, 7, 8), (9, 10, 11, 12)) self.assert_pack("CMYK", "CMYK", 4,
self.assert_pack("CMYK", "CMYK;I", 4, (1, 2, 3, 4), (5, 6, 7, 8), (9, 10, 11, 12))
self.assert_pack(
"CMYK", "CMYK;I", 4,
(254, 253, 252, 251), (250, 249, 248, 247), (246, 245, 244, 243)) (254, 253, 252, 251), (250, 249, 248, 247), (246, 245, 244, 243))
self.assert_pack("CMYK", "CMYK;L", 4, self.assert_pack(
(1, 4, 7, 10), (2, 5, 8, 11), (3, 6, 9, 12)) "CMYK", "CMYK;L", 4, (1, 4, 7, 10), (2, 5, 8, 11), (3, 6, 9, 12))
self.assert_pack("CMYK", "K", 1, (6, 7, 0, 1), (6, 7, 0, 2), (0, 7, 0, 3)) self.assert_pack("CMYK", "K", 1,
(6, 7, 0, 1), (6, 7, 0, 2), (0, 7, 0, 3))
def test_YCbCr(self): def test_YCbCr(self):
self.assert_pack("YCbCr", "YCbCr", 3, (1, 2, 3), (4, 5, 6), (7, 8, 9)) self.assert_pack("YCbCr", "YCbCr", 3, (1, 2, 3), (4, 5, 6), (7, 8, 9))
self.assert_pack("YCbCr", "YCbCr;L", 3, (1, 4, 7), (2, 5, 8), (3, 6, 9)) self.assert_pack("YCbCr", "YCbCr;L", 3,
self.assert_pack("YCbCr", "YCbCrX", (1, 4, 7), (2, 5, 8), (3, 6, 9))
self.assert_pack(
"YCbCr", "YCbCrX",
b'\x01\x02\x03\xff\x05\x06\x07\xff\t\n\x0b\xff', b'\x01\x02\x03\xff\x05\x06\x07\xff\t\n\x0b\xff',
(1, 2, 3), (5, 6, 7), (9, 10, 11)) (1, 2, 3), (5, 6, 7), (9, 10, 11))
self.assert_pack("YCbCr", "YCbCrK", self.assert_pack(
"YCbCr", "YCbCrK",
b'\x01\x02\x03\xff\x05\x06\x07\xff\t\n\x0b\xff', b'\x01\x02\x03\xff\x05\x06\x07\xff\t\n\x0b\xff',
(1, 2, 3), (5, 6, 7), (9, 10, 11)) (1, 2, 3), (5, 6, 7), (9, 10, 11))
self.assert_pack("YCbCr", "Y", 1, (1, 0, 8, 9), (2, 0, 8, 9), (3, 0, 8, 0)) self.assert_pack("YCbCr", "Y", 1,
self.assert_pack("YCbCr", "Cb", 1, (6, 1, 8, 9), (6, 2, 8, 9), (6, 3, 8, 9)) (1, 0, 8, 9), (2, 0, 8, 9), (3, 0, 8, 0))
self.assert_pack("YCbCr", "Cr", 1, (6, 7, 1, 9), (6, 7, 2, 0), (6, 7, 3, 9)) self.assert_pack("YCbCr", "Cb", 1,
(6, 1, 8, 9), (6, 2, 8, 9), (6, 3, 8, 9))
self.assert_pack("YCbCr", "Cr", 1,
(6, 7, 1, 9), (6, 7, 2, 0), (6, 7, 3, 9))
def test_LAB(self): def test_LAB(self):
self.assert_pack("LAB", "LAB", 3, self.assert_pack(
(1, 130, 131), (4, 133, 134), (7, 136, 137)) "LAB", "LAB", 3, (1, 130, 131), (4, 133, 134), (7, 136, 137))
self.assert_pack("LAB", "L", 1, (1, 9, 9), (2, 9, 9), (3, 9, 9)) self.assert_pack("LAB", "L", 1, (1, 9, 9), (2, 9, 9), (3, 9, 9))
self.assert_pack("LAB", "A", 1, (9, 1, 9), (9, 2, 9), (9, 3, 9)) self.assert_pack("LAB", "A", 1, (9, 1, 9), (9, 2, 9), (9, 3, 9))
self.assert_pack("LAB", "B", 1, (9, 9, 1), (9, 9, 2), (9, 9, 3)) self.assert_pack("LAB", "B", 1, (9, 9, 1), (9, 9, 2), (9, 9, 3))
@ -149,35 +181,35 @@ class TestLibPack(PillowTestCase):
def test_I(self): def test_I(self):
self.assert_pack("I", "I;16B", 2, 0x0102, 0x0304) self.assert_pack("I", "I;16B", 2, 0x0102, 0x0304)
self.assert_pack("I", "I;32S", self.assert_pack(
b'\x83\x00\x00\x01\x01\x00\x00\x83', "I", "I;32S",
0x01000083, -2097151999) b'\x83\x00\x00\x01\x01\x00\x00\x83', 0x01000083, -2097151999)
if sys.byteorder == 'little': if sys.byteorder == 'little':
self.assert_pack("I", "I", 4, 0x04030201, 0x08070605) self.assert_pack("I", "I", 4, 0x04030201, 0x08070605)
self.assert_pack("I", "I;32NS", self.assert_pack(
b'\x83\x00\x00\x01\x01\x00\x00\x83', "I", "I;32NS",
0x01000083, -2097151999) b'\x83\x00\x00\x01\x01\x00\x00\x83', 0x01000083, -2097151999)
else: else:
self.assert_pack("I", "I", 4, 0x01020304, 0x05060708) self.assert_pack("I", "I", 4, 0x01020304, 0x05060708)
self.assert_pack("I", "I;32NS", self.assert_pack(
b'\x83\x00\x00\x01\x01\x00\x00\x83', "I", "I;32NS",
-2097151999, 0x01000083) b'\x83\x00\x00\x01\x01\x00\x00\x83', -2097151999, 0x01000083)
def test_F_float(self): def test_F_float(self):
self.assert_pack("F", "F;32F", 4, self.assert_pack(
1.539989614439558e-36, 4.063216068939723e-34) "F", "F;32F", 4, 1.539989614439558e-36, 4.063216068939723e-34)
if sys.byteorder == 'little': if sys.byteorder == 'little':
self.assert_pack("F", "F", 4, self.assert_pack(
1.539989614439558e-36, 4.063216068939723e-34) "F", "F", 4, 1.539989614439558e-36, 4.063216068939723e-34)
self.assert_pack("F", "F;32NF", 4, self.assert_pack(
1.539989614439558e-36, 4.063216068939723e-34) "F", "F;32NF", 4, 1.539989614439558e-36, 4.063216068939723e-34)
else: else:
self.assert_pack("F", "F", 4, self.assert_pack(
2.387939260590663e-38, 6.301941157072183e-36) "F", "F", 4, 2.387939260590663e-38, 6.301941157072183e-36)
self.assert_pack("F", "F;32NF", 4, self.assert_pack(
2.387939260590663e-38, 6.301941157072183e-36) "F", "F;32NF", 4, 2.387939260590663e-38, 6.301941157072183e-36)
class TestLibUnpack(PillowTestCase): class TestLibUnpack(PillowTestCase):
@ -258,9 +290,12 @@ class TestLibUnpack(PillowTestCase):
self.assert_unpack("RGB", "RGBX", 4, (1, 2, 3), (5, 6, 7), (9, 10, 11)) self.assert_unpack("RGB", "RGBX", 4, (1, 2, 3), (5, 6, 7), (9, 10, 11))
self.assert_unpack("RGB", "RGBX;L", 4, (1, 4, 7), (2, 5, 8), (3, 6, 9)) self.assert_unpack("RGB", "RGBX;L", 4, (1, 4, 7), (2, 5, 8), (3, 6, 9))
self.assert_unpack("RGB", "BGRX", 4, (3, 2, 1), (7, 6, 5), (11, 10, 9)) self.assert_unpack("RGB", "BGRX", 4, (3, 2, 1), (7, 6, 5), (11, 10, 9))
self.assert_unpack("RGB", "XRGB", 4, (2, 3, 4), (6, 7, 8), (10, 11, 12)) self.assert_unpack(
self.assert_unpack("RGB", "XBGR", 4, (4, 3, 2), (8, 7, 6), (12, 11, 10)) "RGB", "XRGB", 4, (2, 3, 4), (6, 7, 8), (10, 11, 12))
self.assert_unpack("RGB", "YCC;P", self.assert_unpack(
"RGB", "XBGR", 4, (4, 3, 2), (8, 7, 6), (12, 11, 10))
self.assert_unpack(
"RGB", "YCC;P",
b'D]\x9c\x82\x1a\x91\xfaOC\xe7J\x12', # random data b'D]\x9c\x82\x1a\x91\xfaOC\xe7J\x12', # random data
(127, 102, 0), (192, 227, 0), (213, 255, 170), (98, 255, 133)) (127, 102, 0), (192, 227, 0), (213, 255, 170), (98, 255, 133))
self.assert_unpack("RGB", "R", 1, (1, 0, 0), (2, 0, 0), (3, 0, 0)) self.assert_unpack("RGB", "R", 1, (1, 0, 0), (2, 0, 0), (3, 0, 0))
@ -296,118 +331,156 @@ class TestLibUnpack(PillowTestCase):
self.assert_unpack( self.assert_unpack(
"RGBA", "RGBa;16L", 8, "RGBA", "RGBa;16L", 8,
(63, 127, 191, 8), (159, 191, 223, 16), (191, 212, 233, 24)) (63, 127, 191, 8), (159, 191, 223, 16), (191, 212, 233, 24))
self.assert_unpack("RGBA", "RGBa;16L", self.assert_unpack(
"RGBA", "RGBa;16L",
b'\x88\x01\x88\x02\x88\x03\x88\x00' b'\x88\x01\x88\x02\x88\x03\x88\x00'
b'\x88\x10\x88\x20\x88\x30\x88\xff', b'\x88\x10\x88\x20\x88\x30\x88\xff',
(0, 0, 0, 0), (16, 32, 48, 255)) (0, 0, 0, 0), (16, 32, 48, 255))
self.assert_unpack("RGBA", "RGBa;16B", 8, self.assert_unpack(
"RGBA", "RGBa;16B", 8,
(36, 109, 182, 7), (153, 187, 221, 15), (188, 210, 232, 23)) (36, 109, 182, 7), (153, 187, 221, 15), (188, 210, 232, 23))
self.assert_unpack("RGBA", "RGBa;16B", self.assert_unpack(
"RGBA", "RGBa;16B",
b'\x01\x88\x02\x88\x03\x88\x00\x88' b'\x01\x88\x02\x88\x03\x88\x00\x88'
b'\x10\x88\x20\x88\x30\x88\xff\x88', b'\x10\x88\x20\x88\x30\x88\xff\x88',
(0, 0, 0, 0), (16, 32, 48, 255)) (0, 0, 0, 0), (16, 32, 48, 255))
self.assert_unpack("RGBA", "BGRa", 4, self.assert_unpack(
"RGBA", "BGRa", 4,
(191, 127, 63, 4), (223, 191, 159, 8), (233, 212, 191, 12)) (191, 127, 63, 4), (223, 191, 159, 8), (233, 212, 191, 12))
self.assert_unpack("RGBA", "BGRa", self.assert_unpack(
"RGBA", "BGRa",
b'\x01\x02\x03\x00\x10\x20\x30\xff', b'\x01\x02\x03\x00\x10\x20\x30\xff',
(0, 0, 0, 0), (48, 32, 16, 255)) (0, 0, 0, 0), (48, 32, 16, 255))
self.assert_unpack("RGBA", "RGBA;I", 4, self.assert_unpack(
"RGBA", "RGBA;I", 4,
(254, 253, 252, 4), (250, 249, 248, 8), (246, 245, 244, 12)) (254, 253, 252, 4), (250, 249, 248, 8), (246, 245, 244, 12))
self.assert_unpack("RGBA", "RGBA;L", 4, self.assert_unpack(
(1, 4, 7, 10), (2, 5, 8, 11), (3, 6, 9, 12)) "RGBA", "RGBA;L", 4, (1, 4, 7, 10), (2, 5, 8, 11), (3, 6, 9, 12))
self.assert_unpack("RGBA", "RGBA;15", 2, (8, 131, 0, 0), (24, 0, 8, 0)) self.assert_unpack("RGBA", "RGBA;15", 2, (8, 131, 0, 0), (24, 0, 8, 0))
self.assert_unpack("RGBA", "BGRA;15", 2, (0, 131, 8, 0), (8, 0, 24, 0)) self.assert_unpack("RGBA", "BGRA;15", 2, (0, 131, 8, 0), (8, 0, 24, 0))
self.assert_unpack("RGBA", "RGBA;4B", 2, (17, 0, 34, 0), (51, 0, 68, 0)) self.assert_unpack(
self.assert_unpack("RGBA", "RGBA;16L", 8, (2, 4, 6, 8), (10, 12, 14, 16)) "RGBA", "RGBA;4B", 2, (17, 0, 34, 0), (51, 0, 68, 0))
self.assert_unpack("RGBA", "RGBA;16B", 8, (1, 3, 5, 7), (9, 11, 13, 15)) self.assert_unpack(
self.assert_unpack("RGBA", "BGRA", 4, "RGBA", "RGBA;16L", 8, (2, 4, 6, 8), (10, 12, 14, 16))
(3, 2, 1, 4), (7, 6, 5, 8), (11, 10, 9, 12)) self.assert_unpack(
self.assert_unpack("RGBA", "ARGB", 4, "RGBA", "RGBA;16B", 8, (1, 3, 5, 7), (9, 11, 13, 15))
(2, 3, 4, 1), (6, 7, 8, 5), (10, 11, 12, 9)) self.assert_unpack(
self.assert_unpack("RGBA", "ABGR", 4, "RGBA", "BGRA", 4, (3, 2, 1, 4), (7, 6, 5, 8), (11, 10, 9, 12))
(4, 3, 2, 1), (8, 7, 6, 5), (12, 11, 10, 9)) self.assert_unpack(
self.assert_unpack("RGBA", "YCCA;P", "RGBA", "ARGB", 4, (2, 3, 4, 1), (6, 7, 8, 5), (10, 11, 12, 9))
self.assert_unpack(
"RGBA", "ABGR", 4, (4, 3, 2, 1), (8, 7, 6, 5), (12, 11, 10, 9))
self.assert_unpack(
"RGBA", "YCCA;P",
b']bE\x04\xdd\xbej\xed57T\xce\xac\xce:\x11', # random data b']bE\x04\xdd\xbej\xed57T\xce\xac\xce:\x11', # random data
(0, 161, 0, 4), (255, 255, 255, 237), (27, 158, 0, 206), (0, 118, 0, 17)) (0, 161, 0, 4), (255, 255, 255, 237), (27, 158, 0, 206), (0, 118, 0, 17))
self.assert_unpack("RGBA", "R", 1, (1, 0, 0, 0), (2, 0, 0, 0), (3, 0, 0, 0)) self.assert_unpack(
self.assert_unpack("RGBA", "G", 1, (0, 1, 0, 0), (0, 2, 0, 0), (0, 3, 0, 0)) "RGBA", "R", 1, (1, 0, 0, 0), (2, 0, 0, 0), (3, 0, 0, 0))
self.assert_unpack("RGBA", "B", 1, (0, 0, 1, 0), (0, 0, 2, 0), (0, 0, 3, 0)) self.assert_unpack(
self.assert_unpack("RGBA", "A", 1, (0, 0, 0, 1), (0, 0, 0, 2), (0, 0, 0, 3)) "RGBA", "G", 1, (0, 1, 0, 0), (0, 2, 0, 0), (0, 3, 0, 0))
self.assert_unpack(
"RGBA", "B", 1, (0, 0, 1, 0), (0, 0, 2, 0), (0, 0, 3, 0))
self.assert_unpack(
"RGBA", "A", 1, (0, 0, 0, 1), (0, 0, 0, 2), (0, 0, 0, 3))
def test_RGBa(self): def test_RGBa(self):
self.assert_unpack("RGBa", "RGBa", 4, self.assert_unpack(
(1, 2, 3, 4), (5, 6, 7, 8), (9, 10, 11, 12)) "RGBa", "RGBa", 4, (1, 2, 3, 4), (5, 6, 7, 8), (9, 10, 11, 12))
self.assert_unpack("RGBa", "BGRa", 4, self.assert_unpack(
(3, 2, 1, 4), (7, 6, 5, 8), (11, 10, 9, 12)) "RGBa", "BGRa", 4, (3, 2, 1, 4), (7, 6, 5, 8), (11, 10, 9, 12))
self.assert_unpack("RGBa", "aRGB", 4, self.assert_unpack(
(2, 3, 4, 1), (6, 7, 8, 5), (10, 11, 12, 9)) "RGBa", "aRGB", 4, (2, 3, 4, 1), (6, 7, 8, 5), (10, 11, 12, 9))
self.assert_unpack("RGBa", "aBGR", 4, self.assert_unpack(
(4, 3, 2, 1), (8, 7, 6, 5), (12, 11, 10, 9)) "RGBa", "aBGR", 4, (4, 3, 2, 1), (8, 7, 6, 5), (12, 11, 10, 9))
def test_RGBX(self): def test_RGBX(self):
self.assert_unpack("RGBX", "RGB", 3, (1, 2, 3, X), (4, 5, 6, X), (7, 8, 9, X)) self.assert_unpack("RGBX", "RGB", 3,
self.assert_unpack("RGBX", "RGB;L", 3, (1, 4, 7, X), (2, 5, 8, X), (3, 6, 9, X)) (1, 2, 3, X), (4, 5, 6, X), (7, 8, 9, X))
self.assert_unpack("RGBX", "RGB;L", 3,
(1, 4, 7, X), (2, 5, 8, X), (3, 6, 9, X))
self.assert_unpack("RGBX", "RGB;16B", 6, (1, 3, 5, X), (7, 9, 11, X)) self.assert_unpack("RGBX", "RGB;16B", 6, (1, 3, 5, X), (7, 9, 11, X))
self.assert_unpack("RGBX", "BGR", 3, (3, 2, 1, X), (6, 5, 4, X), (9, 8, 7, X)) self.assert_unpack("RGBX", "BGR", 3,
(3, 2, 1, X), (6, 5, 4, X), (9, 8, 7, X))
self.assert_unpack("RGBX", "RGB;15", 2, (8, 131, 0, X), (24, 0, 8, X)) self.assert_unpack("RGBX", "RGB;15", 2, (8, 131, 0, X), (24, 0, 8, X))
self.assert_unpack("RGBX", "BGR;15", 2, (0, 131, 8, X), (8, 0, 24, X)) self.assert_unpack("RGBX", "BGR;15", 2, (0, 131, 8, X), (8, 0, 24, X))
self.assert_unpack("RGBX", "RGB;4B", 2, (17, 0, 34, X), (51, 0, 68, X)) self.assert_unpack("RGBX", "RGB;4B", 2, (17, 0, 34, X), (51, 0, 68, X))
self.assert_unpack("RGBX", "RGBX", 4, self.assert_unpack(
(1, 2, 3, 4), (5, 6, 7, 8), (9, 10, 11, 12)) "RGBX", "RGBX", 4, (1, 2, 3, 4), (5, 6, 7, 8), (9, 10, 11, 12))
self.assert_unpack("RGBX", "RGBXX", 5, self.assert_unpack(
(1, 2, 3, 4), (6, 7, 8, 9), (11, 12, 13, 14)) "RGBX", "RGBXX", 5, (1, 2, 3, 4), (6, 7, 8, 9), (11, 12, 13, 14))
self.assert_unpack("RGBX", "RGBXXX", 6, self.assert_unpack(
(1, 2, 3, 4), (7, 8, 9, 10), (13, 14, 15, 16)) "RGBX", "RGBXXX", 6, (1, 2, 3, 4), (7, 8, 9, 10), (13, 14, 15, 16))
self.assert_unpack("RGBX", "RGBX;L", 4, self.assert_unpack(
(1, 4, 7, 10), (2, 5, 8, 11), (3, 6, 9, 12)) "RGBX", "RGBX;L", 4, (1, 4, 7, 10), (2, 5, 8, 11), (3, 6, 9, 12))
self.assert_unpack("RGBX", "RGBX;16L", 8, (2, 4, 6, 8), (10, 12, 14, 16)) self.assert_unpack("RGBX", "RGBX;16L", 8,
self.assert_unpack("RGBX", "RGBX;16B", 8, (1, 3, 5, 7), (9, 11, 13, 15)) (2, 4, 6, 8), (10, 12, 14, 16))
self.assert_unpack("RGBX", "BGRX", 4, (3, 2, 1, X), (7, 6, 5, X), (11, 10, 9, X)) self.assert_unpack("RGBX", "RGBX;16B", 8,
self.assert_unpack("RGBX", "XRGB", 4, (2, 3, 4, X), (6, 7, 8, X), (10, 11, 12, X)) (1, 3, 5, 7), (9, 11, 13, 15))
self.assert_unpack("RGBX", "XBGR", 4, (4, 3, 2, X), (8, 7, 6, X), (12, 11, 10, X)) self.assert_unpack("RGBX", "BGRX", 4,
self.assert_unpack("RGBX", "YCC;P", (3, 2, 1, X), (7, 6, 5, X), (11, 10, 9, X))
self.assert_unpack("RGBX", "XRGB", 4,
(2, 3, 4, X), (6, 7, 8, X), (10, 11, 12, X))
self.assert_unpack("RGBX", "XBGR", 4,
(4, 3, 2, X), (8, 7, 6, X), (12, 11, 10, X))
self.assert_unpack(
"RGBX", "YCC;P",
b'D]\x9c\x82\x1a\x91\xfaOC\xe7J\x12', # random data b'D]\x9c\x82\x1a\x91\xfaOC\xe7J\x12', # random data
(127, 102, 0, X), (192, 227, 0, X), (213, 255, 170, X), (98, 255, 133, X)) (127, 102, 0, X), (192, 227, 0, X), (213, 255, 170, X), (98, 255, 133, X))
self.assert_unpack("RGBX", "R", 1, (1, 0, 0, 0), (2, 0, 0, 0), (3, 0, 0, 0)) self.assert_unpack("RGBX", "R", 1,
self.assert_unpack("RGBX", "G", 1, (0, 1, 0, 0), (0, 2, 0, 0), (0, 3, 0, 0)) (1, 0, 0, 0), (2, 0, 0, 0), (3, 0, 0, 0))
self.assert_unpack("RGBX", "B", 1, (0, 0, 1, 0), (0, 0, 2, 0), (0, 0, 3, 0)) self.assert_unpack("RGBX", "G", 1,
self.assert_unpack("RGBX", "X", 1, (0, 0, 0, 1), (0, 0, 0, 2), (0, 0, 0, 3)) (0, 1, 0, 0), (0, 2, 0, 0), (0, 3, 0, 0))
self.assert_unpack("RGBX", "B", 1,
(0, 0, 1, 0), (0, 0, 2, 0), (0, 0, 3, 0))
self.assert_unpack("RGBX", "X", 1,
(0, 0, 0, 1), (0, 0, 0, 2), (0, 0, 0, 3))
def test_CMYK(self): def test_CMYK(self):
self.assert_unpack("CMYK", "CMYK", 4, self.assert_unpack(
(1, 2, 3, 4), (5, 6, 7, 8), (9, 10, 11, 12)) "CMYK", "CMYK", 4, (1, 2, 3, 4), (5, 6, 7, 8), (9, 10, 11, 12))
self.assert_unpack("CMYK", "CMYKX", 5, self.assert_unpack(
(1, 2, 3, 4), (6, 7, 8, 9), (11, 12, 13, 14)) "CMYK", "CMYKX", 5, (1, 2, 3, 4), (6, 7, 8, 9), (11, 12, 13, 14))
self.assert_unpack("CMYK", "CMYKXX", 6, self.assert_unpack(
(1, 2, 3, 4), (7, 8, 9, 10), (13, 14, 15, 16)) "CMYK", "CMYKXX", 6, (1, 2, 3, 4), (7, 8, 9, 10), (13, 14, 15, 16))
self.assert_unpack("CMYK", "CMYK;I", 4, self.assert_unpack(
"CMYK", "CMYK;I", 4,
(254, 253, 252, 251), (250, 249, 248, 247), (246, 245, 244, 243)) (254, 253, 252, 251), (250, 249, 248, 247), (246, 245, 244, 243))
self.assert_unpack("CMYK", "CMYK;L", 4, self.assert_unpack(
(1, 4, 7, 10), (2, 5, 8, 11), (3, 6, 9, 12)) "CMYK", "CMYK;L", 4, (1, 4, 7, 10), (2, 5, 8, 11), (3, 6, 9, 12))
self.assert_unpack("CMYK", "C", 1, (1, 0, 0, 0), (2, 0, 0, 0), (3, 0, 0, 0)) self.assert_unpack("CMYK", "C", 1,
self.assert_unpack("CMYK", "M", 1, (0, 1, 0, 0), (0, 2, 0, 0), (0, 3, 0, 0)) (1, 0, 0, 0), (2, 0, 0, 0), (3, 0, 0, 0))
self.assert_unpack("CMYK", "Y", 1, (0, 0, 1, 0), (0, 0, 2, 0), (0, 0, 3, 0)) self.assert_unpack("CMYK", "M", 1,
self.assert_unpack("CMYK", "K", 1, (0, 0, 0, 1), (0, 0, 0, 2), (0, 0, 0, 3)) (0, 1, 0, 0), (0, 2, 0, 0), (0, 3, 0, 0))
self.assert_unpack("CMYK", "C;I", 1, self.assert_unpack("CMYK", "Y", 1,
(254, 0, 0, 0), (253, 0, 0, 0), (252, 0, 0, 0)) (0, 0, 1, 0), (0, 0, 2, 0), (0, 0, 3, 0))
self.assert_unpack("CMYK", "M;I", 1, self.assert_unpack("CMYK", "K", 1,
(0, 254, 0, 0), (0, 253, 0, 0), (0, 252, 0, 0)) (0, 0, 0, 1), (0, 0, 0, 2), (0, 0, 0, 3))
self.assert_unpack("CMYK", "Y;I", 1, self.assert_unpack(
(0, 0, 254, 0), (0, 0, 253, 0), (0, 0, 252, 0)) "CMYK", "C;I", 1, (254, 0, 0, 0), (253, 0, 0, 0), (252, 0, 0, 0))
self.assert_unpack("CMYK", "K;I", 1, self.assert_unpack(
(0, 0, 0, 254), (0, 0, 0, 253), (0, 0, 0, 252)) "CMYK", "M;I", 1, (0, 254, 0, 0), (0, 253, 0, 0), (0, 252, 0, 0))
self.assert_unpack(
"CMYK", "Y;I", 1, (0, 0, 254, 0), (0, 0, 253, 0), (0, 0, 252, 0))
self.assert_unpack(
"CMYK", "K;I", 1, (0, 0, 0, 254), (0, 0, 0, 253), (0, 0, 0, 252))
def test_YCbCr(self): def test_YCbCr(self):
self.assert_unpack("YCbCr", "YCbCr", 3, (1, 2, 3), (4, 5, 6), (7, 8, 9)) self.assert_unpack(
self.assert_unpack("YCbCr", "YCbCr;L", 3, (1, 4, 7), (2, 5, 8), (3, 6, 9)) "YCbCr", "YCbCr", 3, (1, 2, 3), (4, 5, 6), (7, 8, 9))
self.assert_unpack("YCbCr", "YCbCrK", 4, (1, 2, 3), (5, 6, 7), (9, 10, 11)) self.assert_unpack(
self.assert_unpack("YCbCr", "YCbCrX", 4, (1, 2, 3), (5, 6, 7), (9, 10, 11)) "YCbCr", "YCbCr;L", 3, (1, 4, 7), (2, 5, 8), (3, 6, 9))
self.assert_unpack("YCbCr", "YCbCrXX", 5, (1, 2, 3), (6, 7, 8), (11, 12, 13)) self.assert_unpack(
self.assert_unpack("YCbCr", "YCbCrXXX", 6, (1, 2, 3), (7, 8, 9), (13, 14, 15)) "YCbCr", "YCbCrK", 4, (1, 2, 3), (5, 6, 7), (9, 10, 11))
self.assert_unpack(
"YCbCr", "YCbCrX", 4, (1, 2, 3), (5, 6, 7), (9, 10, 11))
self.assert_unpack(
"YCbCr", "YCbCrXX", 5, (1, 2, 3), (6, 7, 8), (11, 12, 13))
self.assert_unpack(
"YCbCr", "YCbCrXXX", 6, (1, 2, 3), (7, 8, 9), (13, 14, 15))
def test_LAB(self): def test_LAB(self):
self.assert_unpack("LAB", "LAB", 3, self.assert_unpack(
(1, 130, 131), (4, 133, 134), (7, 136, 137)) "LAB", "LAB", 3, (1, 130, 131), (4, 133, 134), (7, 136, 137))
self.assert_unpack("LAB", "L", 1, (1, 0, 0), (2, 0, 0), (3, 0, 0)) self.assert_unpack("LAB", "L", 1, (1, 0, 0), (2, 0, 0), (3, 0, 0))
self.assert_unpack("LAB", "A", 1, (0, 1, 0), (0, 2, 0), (0, 3, 0)) self.assert_unpack("LAB", "A", 1, (0, 1, 0), (0, 2, 0), (0, 3, 0))
self.assert_unpack("LAB", "B", 1, (0, 0, 1), (0, 0, 2), (0, 0, 3)) self.assert_unpack("LAB", "B", 1, (0, 0, 1), (0, 0, 2), (0, 0, 3))
@ -426,30 +499,32 @@ class TestLibUnpack(PillowTestCase):
self.assert_unpack("I", "I;16B", 2, 0x0102, 0x0304) self.assert_unpack("I", "I;16B", 2, 0x0102, 0x0304)
self.assert_unpack("I", "I;16BS", b'\x83\x01\x01\x83', -31999, 0x0183) self.assert_unpack("I", "I;16BS", b'\x83\x01\x01\x83', -31999, 0x0183)
self.assert_unpack("I", "I;32", 4, 0x04030201, 0x08070605) self.assert_unpack("I", "I;32", 4, 0x04030201, 0x08070605)
self.assert_unpack("I", "I;32S", self.assert_unpack(
b'\x83\x00\x00\x01\x01\x00\x00\x83', "I", "I;32S",
0x01000083, -2097151999) b'\x83\x00\x00\x01\x01\x00\x00\x83', 0x01000083, -2097151999)
self.assert_unpack("I", "I;32B", 4, 0x01020304, 0x05060708) self.assert_unpack("I", "I;32B", 4, 0x01020304, 0x05060708)
self.assert_unpack("I", "I;32BS", self.assert_unpack(
b'\x83\x00\x00\x01\x01\x00\x00\x83', "I", "I;32BS",
-2097151999, 0x01000083) b'\x83\x00\x00\x01\x01\x00\x00\x83', -2097151999, 0x01000083)
if sys.byteorder == 'little': if sys.byteorder == 'little':
self.assert_unpack("I", "I", 4, 0x04030201, 0x08070605) self.assert_unpack("I", "I", 4, 0x04030201, 0x08070605)
self.assert_unpack("I", "I;16N", 2, 0x0201, 0x0403) self.assert_unpack("I", "I;16N", 2, 0x0201, 0x0403)
self.assert_unpack("I", "I;16NS", b'\x83\x01\x01\x83', 0x0183, -31999) self.assert_unpack("I", "I;16NS",
b'\x83\x01\x01\x83', 0x0183, -31999)
self.assert_unpack("I", "I;32N", 4, 0x04030201, 0x08070605) self.assert_unpack("I", "I;32N", 4, 0x04030201, 0x08070605)
self.assert_unpack("I", "I;32NS", self.assert_unpack(
b'\x83\x00\x00\x01\x01\x00\x00\x83', "I", "I;32NS",
0x01000083, -2097151999) b'\x83\x00\x00\x01\x01\x00\x00\x83', 0x01000083, -2097151999)
else: else:
self.assert_unpack("I", "I", 4, 0x01020304, 0x05060708) self.assert_unpack("I", "I", 4, 0x01020304, 0x05060708)
self.assert_unpack("I", "I;16N", 2, 0x0102, 0x0304) self.assert_unpack("I", "I;16N", 2, 0x0102, 0x0304)
self.assert_unpack("I", "I;16NS", b'\x83\x01\x01\x83', -31999, 0x0183) self.assert_unpack("I", "I;16NS",
b'\x83\x01\x01\x83', -31999, 0x0183)
self.assert_unpack("I", "I;32N", 4, 0x01020304, 0x05060708) self.assert_unpack("I", "I;32N", 4, 0x01020304, 0x05060708)
self.assert_unpack("I", "I;32NS", self.assert_unpack(
b'\x83\x00\x00\x01\x01\x00\x00\x83', "I", "I;32NS",
-2097151999, 0x01000083) b'\x83\x00\x00\x01\x01\x00\x00\x83', -2097151999, 0x01000083)
def test_F_int(self): def test_F_int(self):
self.assert_unpack("F", "F;8", 1, 0x01, 0x02, 0x03, 0x04) self.assert_unpack("F", "F;8", 1, 0x01, 0x02, 0x03, 0x04)
@ -459,55 +534,70 @@ class TestLibUnpack(PillowTestCase):
self.assert_unpack("F", "F;16B", 2, 0x0102, 0x0304) self.assert_unpack("F", "F;16B", 2, 0x0102, 0x0304)
self.assert_unpack("F", "F;16BS", b'\x83\x01\x01\x83', -31999, 0x0183) self.assert_unpack("F", "F;16BS", b'\x83\x01\x01\x83', -31999, 0x0183)
self.assert_unpack("F", "F;32", 4, 67305984, 134678016) self.assert_unpack("F", "F;32", 4, 67305984, 134678016)
self.assert_unpack("F", "F;32S", self.assert_unpack(
b'\x83\x00\x00\x01\x01\x00\x00\x83', "F", "F;32S",
16777348, -2097152000) b'\x83\x00\x00\x01\x01\x00\x00\x83', 16777348, -2097152000)
self.assert_unpack("F", "F;32B", 4, 0x01020304, 0x05060708) self.assert_unpack("F", "F;32B", 4, 0x01020304, 0x05060708)
self.assert_unpack("F", "F;32BS", self.assert_unpack(
b'\x83\x00\x00\x01\x01\x00\x00\x83', "F", "F;32BS",
-2097152000, 16777348) b'\x83\x00\x00\x01\x01\x00\x00\x83', -2097152000, 16777348)
if sys.byteorder == 'little': if sys.byteorder == 'little':
self.assert_unpack("F", "F;16N", 2, 0x0201, 0x0403) self.assert_unpack("F", "F;16N", 2, 0x0201, 0x0403)
self.assert_unpack("F", "F;16NS", b'\x83\x01\x01\x83', 0x0183, -31999) self.assert_unpack(
"F", "F;16NS",
b'\x83\x01\x01\x83', 0x0183, -31999)
self.assert_unpack("F", "F;32N", 4, 67305984, 134678016) self.assert_unpack("F", "F;32N", 4, 67305984, 134678016)
self.assert_unpack("F", "F;32NS", self.assert_unpack(
b'\x83\x00\x00\x01\x01\x00\x00\x83', "F", "F;32NS",
16777348, -2097152000) b'\x83\x00\x00\x01\x01\x00\x00\x83', 16777348, -2097152000)
else: else:
self.assert_unpack("F", "F;16N", 2, 0x0102, 0x0304) self.assert_unpack("F", "F;16N", 2, 0x0102, 0x0304)
self.assert_unpack("F", "F;16NS", b'\x83\x01\x01\x83', -31999, 0x0183) self.assert_unpack(
"F", "F;16NS",
b'\x83\x01\x01\x83', -31999, 0x0183)
self.assert_unpack("F", "F;32N", 4, 0x01020304, 0x05060708) self.assert_unpack("F", "F;32N", 4, 0x01020304, 0x05060708)
self.assert_unpack("F", "F;32NS", self.assert_unpack(
"F", "F;32NS",
b'\x83\x00\x00\x01\x01\x00\x00\x83', b'\x83\x00\x00\x01\x01\x00\x00\x83',
-2097152000, 16777348) -2097152000, 16777348)
def test_F_float(self): def test_F_float(self):
self.assert_unpack("F", "F;32F", 4, self.assert_unpack(
"F", "F;32F", 4,
1.539989614439558e-36, 4.063216068939723e-34) 1.539989614439558e-36, 4.063216068939723e-34)
self.assert_unpack("F", "F;32BF", 4, self.assert_unpack(
"F", "F;32BF", 4,
2.387939260590663e-38, 6.301941157072183e-36) 2.387939260590663e-38, 6.301941157072183e-36)
self.assert_unpack("F", "F;64F", self.assert_unpack(
"F", "F;64F",
b'333333\xc3?\x00\x00\x00\x00\x00J\x93\xc0', # by struct.pack b'333333\xc3?\x00\x00\x00\x00\x00J\x93\xc0', # by struct.pack
0.15000000596046448, -1234.5) 0.15000000596046448, -1234.5)
self.assert_unpack("F", "F;64BF", self.assert_unpack(
"F", "F;64BF",
b'?\xc3333333\xc0\x93J\x00\x00\x00\x00\x00', # by struct.pack b'?\xc3333333\xc0\x93J\x00\x00\x00\x00\x00', # by struct.pack
0.15000000596046448, -1234.5) 0.15000000596046448, -1234.5)
if sys.byteorder == 'little': if sys.byteorder == 'little':
self.assert_unpack("F", "F", 4, self.assert_unpack(
"F", "F", 4,
1.539989614439558e-36, 4.063216068939723e-34) 1.539989614439558e-36, 4.063216068939723e-34)
self.assert_unpack("F", "F;32NF", 4, self.assert_unpack(
"F", "F;32NF", 4,
1.539989614439558e-36, 4.063216068939723e-34) 1.539989614439558e-36, 4.063216068939723e-34)
self.assert_unpack("F", "F;64NF", self.assert_unpack(
"F", "F;64NF",
b'333333\xc3?\x00\x00\x00\x00\x00J\x93\xc0', b'333333\xc3?\x00\x00\x00\x00\x00J\x93\xc0',
0.15000000596046448, -1234.5) 0.15000000596046448, -1234.5)
else: else:
self.assert_unpack("F", "F", 4, self.assert_unpack(
"F", "F", 4,
2.387939260590663e-38, 6.301941157072183e-36) 2.387939260590663e-38, 6.301941157072183e-36)
self.assert_unpack("F", "F;32NF", 4, self.assert_unpack(
"F", "F;32NF", 4,
2.387939260590663e-38, 6.301941157072183e-36) 2.387939260590663e-38, 6.301941157072183e-36)
self.assert_unpack("F", "F;64NF", self.assert_unpack(
"F", "F;64NF",
b'?\xc3333333\xc0\x93J\x00\x00\x00\x00\x00', b'?\xc3333333\xc0\x93J\x00\x00\x00\x00\x00',
0.15000000596046448, -1234.5) 0.15000000596046448, -1234.5)

View File

@ -29,7 +29,7 @@ class TestLocale(PillowTestCase):
Image.open(path) Image.open(path)
try: try:
locale.setlocale(locale.LC_ALL, "polish") locale.setlocale(locale.LC_ALL, "polish")
except: except locale.Error:
unittest.skip('Polish locale not available') unittest.skip('Polish locale not available')
Image.open(path) Image.open(path)

View File

@ -4,7 +4,8 @@ import sys
from PIL import Image from PIL import Image
@unittest.skipIf(sys.platform.startswith('win32'), "Win32 does not call map_buffer") @unittest.skipIf(sys.platform.startswith('win32'),
"Win32 does not call map_buffer")
class TestMap(PillowTestCase): class TestMap(PillowTestCase):
def test_overflow(self): def test_overflow(self):
# There is the potential to overflow comparisons in map.c # There is the potential to overflow comparisons in map.c

View File

@ -124,7 +124,9 @@ class TestNumpy(PillowTestCase):
def test_save_tiff_uint16(self): def test_save_tiff_uint16(self):
# Tests that we're getting the pixel value in the right byte order. # Tests that we're getting the pixel value in the right byte order.
pixel_value = 0x1234 pixel_value = 0x1234
a = numpy.array([pixel_value] * TEST_IMAGE_SIZE[0] * TEST_IMAGE_SIZE[1], dtype=numpy.uint16) a = numpy.array(
[pixel_value] * TEST_IMAGE_SIZE[0] * TEST_IMAGE_SIZE[1],
dtype=numpy.uint16)
a.shape = TEST_IMAGE_SIZE a.shape = TEST_IMAGE_SIZE
img = Image.fromarray(a) img = Image.fromarray(a)

View File

@ -1,6 +1,8 @@
from helper import unittest, PillowTestCase from helper import unittest, PillowTestCase
from PIL.PdfParser import IndirectObjectDef, IndirectReference, PdfBinary, PdfDict, PdfFormatError, PdfName, PdfParser, PdfStream, decode_text, encode_text, pdf_repr from PIL.PdfParser import IndirectObjectDef, IndirectReference, PdfBinary, \
PdfDict, PdfFormatError, PdfName, PdfParser, \
PdfStream, decode_text, encode_text, pdf_repr
class TestPdfParser(PillowTestCase): class TestPdfParser(PillowTestCase):
@ -22,23 +24,35 @@ class TestPdfParser(PillowTestCase):
self.assertNotEqual(IndirectObjectDef(1, 2), (1, 2)) self.assertNotEqual(IndirectObjectDef(1, 2), (1, 2))
def test_parsing(self): def test_parsing(self):
self.assertEqual(PdfParser.interpret_name(b"Name#23Hash"), b"Name#Hash") self.assertEqual(PdfParser.interpret_name(b"Name#23Hash"),
b"Name#Hash")
self.assertEqual(PdfParser.interpret_name(b"Name#23Hash", as_text=True), "Name#Hash") self.assertEqual(PdfParser.interpret_name(b"Name#23Hash", as_text=True), "Name#Hash")
self.assertEqual(PdfParser.get_value(b"1 2 R ", 0), (IndirectReference(1, 2), 5)) self.assertEqual(PdfParser.get_value(b"1 2 R ", 0),
(IndirectReference(1, 2), 5))
self.assertEqual(PdfParser.get_value(b"true[", 0), (True, 4)) self.assertEqual(PdfParser.get_value(b"true[", 0), (True, 4))
self.assertEqual(PdfParser.get_value(b"false%", 0), (False, 5)) self.assertEqual(PdfParser.get_value(b"false%", 0), (False, 5))
self.assertEqual(PdfParser.get_value(b"null<", 0), (None, 4)) self.assertEqual(PdfParser.get_value(b"null<", 0), (None, 4))
self.assertEqual(PdfParser.get_value(b"%cmt\n %cmt\n 123\n", 0), (123, 15)) self.assertEqual(PdfParser.get_value(b"%cmt\n %cmt\n 123\n", 0),
self.assertEqual(PdfParser.get_value(b"<901FA3>", 0), (b"\x90\x1F\xA3", 8)) (123, 15))
self.assertEqual(PdfParser.get_value(b"asd < 9 0 1 f A > qwe", 3), (b"\x90\x1F\xA0", 17)) self.assertEqual(PdfParser.get_value(b"<901FA3>", 0),
(b"\x90\x1F\xA3", 8))
self.assertEqual(PdfParser.get_value(b"asd < 9 0 1 f A > qwe", 3),
(b"\x90\x1F\xA0", 17))
self.assertEqual(PdfParser.get_value(b"(asd)", 0), (b"asd", 5)) self.assertEqual(PdfParser.get_value(b"(asd)", 0), (b"asd", 5))
self.assertEqual(PdfParser.get_value(b"(asd(qwe)zxc)zzz(aaa)", 0), (b"asd(qwe)zxc", 13)) self.assertEqual(PdfParser.get_value(b"(asd(qwe)zxc)zzz(aaa)", 0),
self.assertEqual(PdfParser.get_value(b"(Two \\\nwords.)", 0), (b"Two words.", 14)) (b"asd(qwe)zxc", 13))
self.assertEqual(PdfParser.get_value(b"(Two\nlines.)", 0), (b"Two\nlines.", 12)) self.assertEqual(PdfParser.get_value(b"(Two \\\nwords.)", 0),
self.assertEqual(PdfParser.get_value(b"(Two\r\nlines.)", 0), (b"Two\nlines.", 13)) (b"Two words.", 14))
self.assertEqual(PdfParser.get_value(b"(Two\\nlines.)", 0), (b"Two\nlines.", 13)) self.assertEqual(PdfParser.get_value(b"(Two\nlines.)", 0),
self.assertEqual(PdfParser.get_value(b"(One\\(paren).", 0), (b"One(paren", 12)) (b"Two\nlines.", 12))
self.assertEqual(PdfParser.get_value(b"(One\\)paren).", 0), (b"One)paren", 12)) self.assertEqual(PdfParser.get_value(b"(Two\r\nlines.)", 0),
(b"Two\nlines.", 13))
self.assertEqual(PdfParser.get_value(b"(Two\\nlines.)", 0),
(b"Two\nlines.", 13))
self.assertEqual(PdfParser.get_value(b"(One\\(paren).", 0),
(b"One(paren", 12))
self.assertEqual(PdfParser.get_value(b"(One\\)paren).", 0),
(b"One)paren", 12))
self.assertEqual(PdfParser.get_value(b"(\\0053)", 0), (b"\x053", 7)) self.assertEqual(PdfParser.get_value(b"(\\0053)", 0), (b"\x053", 7))
self.assertEqual(PdfParser.get_value(b"(\\053)", 0), (b"\x2B", 6)) self.assertEqual(PdfParser.get_value(b"(\\053)", 0), (b"\x2B", 6))
self.assertEqual(PdfParser.get_value(b"(\\53)", 0), (b"\x2B", 5)) self.assertEqual(PdfParser.get_value(b"(\\53)", 0), (b"\x2B", 5))
@ -65,23 +79,30 @@ class TestPdfParser(PillowTestCase):
def test_pdf_repr(self): def test_pdf_repr(self):
self.assertEqual(bytes(IndirectReference(1, 2)), b"1 2 R") self.assertEqual(bytes(IndirectReference(1, 2)), b"1 2 R")
self.assertEqual(bytes(IndirectObjectDef(*IndirectReference(1, 2))), b"1 2 obj") self.assertEqual(bytes(IndirectObjectDef(*IndirectReference(1, 2))),
b"1 2 obj")
self.assertEqual(bytes(PdfName(b"Name#Hash")), b"/Name#23Hash") self.assertEqual(bytes(PdfName(b"Name#Hash")), b"/Name#23Hash")
self.assertEqual(bytes(PdfName("Name#Hash")), b"/Name#23Hash") self.assertEqual(bytes(PdfName("Name#Hash")), b"/Name#23Hash")
self.assertEqual(bytes(PdfDict({b"Name": IndirectReference(1, 2)})), b"<<\n/Name 1 2 R\n>>") self.assertEqual(bytes(PdfDict({b"Name": IndirectReference(1, 2)})),
self.assertEqual(bytes(PdfDict({"Name": IndirectReference(1, 2)})), b"<<\n/Name 1 2 R\n>>") b"<<\n/Name 1 2 R\n>>")
self.assertEqual(bytes(PdfDict({"Name": IndirectReference(1, 2)})),
b"<<\n/Name 1 2 R\n>>")
self.assertEqual(pdf_repr(IndirectReference(1, 2)), b"1 2 R") self.assertEqual(pdf_repr(IndirectReference(1, 2)), b"1 2 R")
self.assertEqual(pdf_repr(IndirectObjectDef(*IndirectReference(1, 2))), b"1 2 obj") self.assertEqual(pdf_repr(IndirectObjectDef(*IndirectReference(1, 2))),
b"1 2 obj")
self.assertEqual(pdf_repr(PdfName(b"Name#Hash")), b"/Name#23Hash") self.assertEqual(pdf_repr(PdfName(b"Name#Hash")), b"/Name#23Hash")
self.assertEqual(pdf_repr(PdfName("Name#Hash")), b"/Name#23Hash") self.assertEqual(pdf_repr(PdfName("Name#Hash")), b"/Name#23Hash")
self.assertEqual(pdf_repr(PdfDict({b"Name": IndirectReference(1, 2)})), b"<<\n/Name 1 2 R\n>>") self.assertEqual(pdf_repr(PdfDict({b"Name": IndirectReference(1, 2)})),
self.assertEqual(pdf_repr(PdfDict({"Name": IndirectReference(1, 2)})), b"<<\n/Name 1 2 R\n>>") b"<<\n/Name 1 2 R\n>>")
self.assertEqual(pdf_repr(PdfDict({"Name": IndirectReference(1, 2)})),
b"<<\n/Name 1 2 R\n>>")
self.assertEqual(pdf_repr(123), b"123") self.assertEqual(pdf_repr(123), b"123")
self.assertEqual(pdf_repr(True), b"true") self.assertEqual(pdf_repr(True), b"true")
self.assertEqual(pdf_repr(False), b"false") self.assertEqual(pdf_repr(False), b"false")
self.assertEqual(pdf_repr(None), b"null") self.assertEqual(pdf_repr(None), b"null")
self.assertEqual(pdf_repr(b"a)/b\\(c"), br"(a\)/b\\\(c)") self.assertEqual(pdf_repr(b"a)/b\\(c"), br"(a\)/b\\\(c)")
self.assertEqual(pdf_repr([123, True, {"a": PdfName(b"b")}]), b"[ 123 true <<\n/a /b\n>> ]") self.assertEqual(pdf_repr([123, True, {"a": PdfName(b"b")}]),
b"[ 123 true <<\n/a /b\n>> ]")
self.assertEqual(pdf_repr(PdfBinary(b"\x90\x1F\xA0")), b"<901FA0>") self.assertEqual(pdf_repr(PdfBinary(b"\x90\x1F\xA0")), b"<901FA0>")

View File

@ -1,5 +1,5 @@
from helper import unittest, PillowTestCase, hopper from helper import unittest, PillowTestCase, hopper
from test_imageqt import PillowQtTestCase, PillowQPixmapTestCase from test_imageqt import PillowQPixmapTestCase
from PIL import ImageQt from PIL import ImageQt
@ -7,7 +7,6 @@ from PIL import ImageQt
class TestFromQPixmap(PillowQPixmapTestCase, PillowTestCase): class TestFromQPixmap(PillowQPixmapTestCase, PillowTestCase):
def roundtrip(self, expected): def roundtrip(self, expected):
PillowQtTestCase.setUp(self)
result = ImageQt.fromqpixmap(ImageQt.toqpixmap(expected)) result = ImageQt.fromqpixmap(ImageQt.toqpixmap(expected))
# 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'))

View File

@ -25,7 +25,6 @@ if ImageQt.qt_is_installed:
class TestToQImage(PillowQtTestCase, PillowTestCase): class TestToQImage(PillowQtTestCase, PillowTestCase):
def test_sanity(self): def test_sanity(self):
PillowQtTestCase.setUp(self)
for mode in ('RGB', 'RGBA', 'L', 'P', '1'): for mode in ('RGB', 'RGBA', 'L', 'P', '1'):
src = hopper(mode) src = hopper(mode)
data = ImageQt.toqimage(src) data = ImageQt.toqimage(src)
@ -43,8 +42,9 @@ class TestToQImage(PillowQtTestCase, PillowTestCase):
if mode == '1': if mode == '1':
# BW appears to not save correctly on QT4 and QT5 # BW appears to not save correctly on QT4 and QT5
# kicks out errors on console: # kicks out errors on console:
# libpng warning: Invalid color type/bit depth combination in IHDR # libpng warning: Invalid color type/bit depth combination
# libpng error: Invalid IHDR data # in IHDR
# libpng error: Invalid IHDR data
continue continue
# Test saving the file # Test saving the file
@ -60,8 +60,6 @@ class TestToQImage(PillowQtTestCase, PillowTestCase):
self.assert_image_equal(reloaded, src) self.assert_image_equal(reloaded, src)
def test_segfault(self): def test_segfault(self):
PillowQtTestCase.setUp(self)
app = QApplication([]) app = QApplication([])
ex = Example() ex = Example()
assert(app) # Silence warning assert(app) # Silence warning

View File

@ -1,5 +1,5 @@
from helper import unittest, PillowTestCase, hopper from helper import unittest, PillowTestCase, hopper
from test_imageqt import PillowQtTestCase, PillowQPixmapTestCase from test_imageqt import PillowQPixmapTestCase
from PIL import ImageQt from PIL import ImageQt
@ -10,8 +10,6 @@ if ImageQt.qt_is_installed:
class TestToQPixmap(PillowQPixmapTestCase, PillowTestCase): class TestToQPixmap(PillowQPixmapTestCase, PillowTestCase):
def test_sanity(self): def test_sanity(self):
PillowQtTestCase.setUp(self)
for mode in ('1', 'RGB', 'RGBA', 'L', 'P'): for mode in ('1', 'RGB', 'RGBA', 'L', 'P'):
data = ImageQt.toqpixmap(hopper(mode)) data = ImageQt.toqpixmap(hopper(mode))

View File

@ -1,7 +1,7 @@
#!/bin/bash #!/bin/bash
# install extra test images # install extra test images
rm -r test_images rm -rf test_images
# Use SVN to just fetch a single git subdirectory # Use SVN to just fetch a single git subdirectory
svn checkout https://github.com/python-pillow/pillow-depends/trunk/test_images svn checkout https://github.com/python-pillow/pillow-depends/trunk/test_images

View File

@ -1,7 +1,7 @@
#!/bin/bash #!/bin/bash
# install libimagequant # install libimagequant
archive=libimagequant-2.11.10 archive=libimagequant-2.12.1
./download-and-extract.sh $archive https://raw.githubusercontent.com/python-pillow/pillow-depends/master/$archive.tar.gz ./download-and-extract.sh $archive https://raw.githubusercontent.com/python-pillow/pillow-depends/master/$archive.tar.gz

View File

@ -62,8 +62,6 @@ can be found here.
:undoc-members: :undoc-members:
:show-inheritance: :show-inheritance:
.. intentionally skipped documenting this because it's deprecated
:mod:`ImageShow` Module :mod:`ImageShow` Module
----------------------- -----------------------

View File

@ -1,4 +1,4 @@
<h3>Need help?</h3> <h3>Need help?</h3>
<p> <p>
You can get help via IRC at <a href="irc://irc.freenode.net#pil">irc://irc.freenode.net#pil</a>, <a href="https://gitter.im/python-pillow/Pillow">Gitter</a> or Stack Overflow <a href="https://stackoverflow.com/questions/tagged/pillow">here</a> and <a href="https://stackoverflow.com/questions/tagged/python-imaging-library">here</a>. Please <a href="https://github.com/python-pillow/Pillow/issues/new">report issues on GitHub</a>. You can get help via IRC at <a href="irc://irc.freenode.net#pil">irc://irc.freenode.net#pil</a>, <a href="https://gitter.im/python-pillow/Pillow">Gitter</a> or <a href="https://stackoverflow.com/questions/tagged/python-imaging-library">Stack Overflow</a>. Please <a href="https://github.com/python-pillow/Pillow/issues/new">report issues on GitHub</a>.
</p> </p>

View File

@ -84,7 +84,14 @@ The :py:meth:`~PIL.Image.Image.open` method sets the following
of the GIF, in milliseconds. of the GIF, in milliseconds.
**loop** **loop**
May not be present. The number of times the GIF should loop. May not be present. The number of times the GIF should loop. 0 means that
it will loop forever.
**comment**
May not be present. A comment about the image.
**extension**
May not be present. Contains application specific information.
Reading sequences Reading sequences
~~~~~~~~~~~~~~~~~ ~~~~~~~~~~~~~~~~~
@ -115,25 +122,12 @@ are available::
It is also supported for ICNS. If images are passed in of relevant sizes, It is also supported for ICNS. If images are passed in of relevant sizes,
they will be used instead of scaling down the main image. they will be used instead of scaling down the main image.
**duration** **include_color_table**
The display duration of each frame of the multiframe gif, in Whether or not to include local color table.
milliseconds. Pass a single integer for a constant duration, or a
list or tuple to set the duration for each frame separately.
**loop** **interlace**
Integer number of times the GIF should loop. Whether or not the image is interlaced. By default, it is, unless the image
is less than 16 pixels in width or height.
**optimize**
If present and true, attempt to compress the palette by
eliminating unused colors. This is only useful if the palette can
be compressed to the next smaller power of 2 elements.
**palette**
Use the specified palette for the saved image. The palette should
be a bytes or bytearray object containing the palette entries in
RGBRGB... form. It should be no more than 768 bytes. Alternately,
the palette can be passed in as an
:py:class:`PIL.ImagePalette.ImagePalette` object.
**disposal** **disposal**
Indicates the way in which the graphic is to be treated after being displayed. Indicates the way in which the graphic is to be treated after being displayed.
@ -146,6 +140,38 @@ are available::
Pass a single integer for a constant disposal, or a list or tuple Pass a single integer for a constant disposal, or a list or tuple
to set the disposal for each frame separately. to set the disposal for each frame separately.
**palette**
Use the specified palette for the saved image. The palette should
be a bytes or bytearray object containing the palette entries in
RGBRGB... form. It should be no more than 768 bytes. Alternately,
the palette can be passed in as an
:py:class:`PIL.ImagePalette.ImagePalette` object.
**optimize**
If present and true, attempt to compress the palette by
eliminating unused colors. This is only useful if the palette can
be compressed to the next smaller power of 2 elements.
Note that if the image you are saving comes from an existing GIF, it may have
the following properties in its :py:attr:`~PIL.Image.Image.info` dictionary.
For these options, if you do not pass them in, they will default to
their :py:attr:`~PIL.Image.Image.info` values.
**transparency**
Transparency color index.
**duration**
The display duration of each frame of the multiframe gif, in
milliseconds. Pass a single integer for a constant duration, or a
list or tuple to set the duration for each frame separately.
**loop**
Integer number of times the GIF should loop. 0 means that it will loop
forever. By default, the image will not loop.
**comment**
A comment about the image.
Reading local images Reading local images
~~~~~~~~~~~~~~~~~~~~ ~~~~~~~~~~~~~~~~~~~~
@ -503,8 +529,8 @@ The :py:meth:`~PIL.Image.Image.save` method supports the following options:
.. note:: .. note::
To enable PNG support, you need to build and install the ZLIB compression To enable PNG support, you need to build and install the ZLIB compression
library before building the Python Imaging Library. See the installation library before building the Python Imaging Library. See the `installation
documentation for details. documentation <../installation.html>`_ for details.
PPM PPM
^^^ ^^^

View File

@ -171,7 +171,6 @@ The fields are used as follows:
stride defaults to 0. stride defaults to 0.
**orientation** **orientation**
Whether the first line in the image is the top line on the screen (1), or Whether the first line in the image is the top line on the screen (1), or
the bottom line (-1). If omitted, the orientation defaults to 1. the bottom line (-1). If omitted, the orientation defaults to 1.
@ -204,7 +203,7 @@ table describes some commonly used **raw modes**:
+-----------+-----------------------------------------------------------------+ +-----------+-----------------------------------------------------------------+
| ``RGBX`` | 24-bit true colour, stored as (red, green, blue, pad). | | ``RGBX`` | 24-bit true colour, stored as (red, green, blue, pad). |
+-----------+-----------------------------------------------------------------+ +-----------+-----------------------------------------------------------------+
| ``RGB;L`` | 24-bit true colour, line interleaved (first all red pixels, the | | ``RGB;L`` | 24-bit true colour, line interleaved (first all red pixels, then|
| | all green pixels, finally all blue pixels). | | | all green pixels, finally all blue pixels). |
+-----------+-----------------------------------------------------------------+ +-----------+-----------------------------------------------------------------+

View File

@ -13,15 +13,21 @@ Warnings
Notes Notes
----- -----
.. note:: Pillow < 2.0.0 supports Python versions 2.4, 2.5, 2.6, 2.7. .. note:: Pillow is supported on the following Python versions
.. note:: Pillow >= 2.0.0 < 4.0.0 supports Python versions 2.6, 2.7, 3.2, 3.3, 3.4, 3.5 +--------------------+-------+-------+-------+-------+-------+-------+-------+-------+-------+-------+
|**Python** |**2.4**|**2.5**|**2.6**|**2.7**|**3.2**|**3.3**|**3.4**|**3.5**|**3.6**|**3.7**|
.. note:: Pillow >= 4.0.0 < 5.0.0 supports Python versions 2.7, 3.3, 3.4, 3.5, 3.6 +--------------------+-------+-------+-------+-------+-------+-------+-------+-------+-------+-------+
|Pillow < 2.0.0 | Yes | Yes | Yes | Yes | | | | | | |
.. note:: Pillow >= 5.0.0 < 5.2.0 supports Python versions 2.7, 3.4, 3.5, 3.6 +--------------------+-------+-------+-------+-------+-------+-------+-------+-------+-------+-------+
|Pillow 2.x - 3.x | | | Yes | Yes | Yes | Yes | Yes | Yes | | |
.. note:: Pillow >= 5.2.0 supports Python versions 2.7, 3.4, 3.5, 3.6, 3.7 +--------------------+-------+-------+-------+-------+-------+-------+-------+-------+-------+-------+
|Pillow 4.x | | | | Yes | | Yes | Yes | Yes | Yes | |
+--------------------+-------+-------+-------+-------+-------+-------+-------+-------+-------+-------+
|Pillow 5.0.x - 5.1.x| | | | Yes | | | Yes | Yes | Yes | |
+--------------------+-------+-------+-------+-------+-------+-------+-------+-------+-------+-------+
|Pillow >= 5.2.0 | | | | Yes | | | Yes | Yes | Yes | Yes |
+--------------------+-------+-------+-------+-------+-------+-------+-------+-------+-------+-------+
Basic Installation Basic Installation
------------------ ------------------
@ -122,8 +128,8 @@ Many of Pillow's features require external libraries:
* **libjpeg** provides JPEG functionality. * **libjpeg** provides JPEG functionality.
* Pillow has been tested with libjpeg versions **6b**, **8**, **9**, **9a**, * Pillow has been tested with libjpeg versions **6b**, **8**, **9-9c** and
and **9b** and libjpeg-turbo version **8**. libjpeg-turbo version **8**.
* Starting with Pillow 3.0.0, libjpeg is required by default, but * Starting with Pillow 3.0.0, libjpeg is required by default, but
may be disabled with the ``--disable-jpeg`` flag. may be disabled with the ``--disable-jpeg`` flag.
@ -159,7 +165,7 @@ Many of Pillow's features require external libraries:
* **libimagequant** provides improved color quantization * **libimagequant** provides improved color quantization
* Pillow has been tested with libimagequant **2.6-2.11** * Pillow has been tested with libimagequant **2.6-2.12.1**
* Libimagequant is licensed GPLv3, which is more restrictive than * Libimagequant is licensed GPLv3, which is more restrictive than
the Pillow license, therefore we will not be distributing binaries the Pillow license, therefore we will not be distributing binaries
with libimagequant support enabled. with libimagequant support enabled.
@ -172,7 +178,7 @@ Many of Pillow's features require external libraries:
shaping (using HarfBuzz), and proper script itemization. As a shaping (using HarfBuzz), and proper script itemization. As a
result, Raqm can support most writing systems covered by Unicode. result, Raqm can support most writing systems covered by Unicode.
* libraqm depends on the following libraries: FreeType, HarfBuzz, * libraqm depends on the following libraries: FreeType, HarfBuzz,
FriBiDi, make sure that you install them before install libraqm FriBiDi, make sure that you install them before installing libraqm
if not available as package in your system. if not available as package in your system.
* setting text direction or font features is not supported without * setting text direction or font features is not supported without
libraqm. libraqm.
@ -209,17 +215,15 @@ Build Options
parallel building. parallel building.
* Build flags: ``--disable-zlib``, ``--disable-jpeg``, * Build flags: ``--disable-zlib``, ``--disable-jpeg``,
``--disable-tiff``, ``--disable-freetype``, ``--disable-tcl``, ``--disable-tiff``, ``--disable-freetype``, ``--disable-lcms``,
``--disable-tk``, ``--disable-lcms``, ``--disable-webp``, ``--disable-webp``, ``--disable-webpmux``, ``--disable-jpeg2000``,
``--disable-webpmux``, ``--disable-jpeg2000``,
``--disable-imagequant``. ``--disable-imagequant``.
Disable building the corresponding feature even if the development Disable building the corresponding feature even if the development
libraries are present on the building machine. libraries are present on the building machine.
* Build flags: ``--enable-zlib``, ``--enable-jpeg``, * Build flags: ``--enable-zlib``, ``--enable-jpeg``,
``--enable-tiff``, ``--enable-freetype``, ``--enable-tcl``, ``--enable-tiff``, ``--enable-freetype``, ``--enable-lcms``,
``--enable-tk``, ``--enable-lcms``, ``--enable-webp``, ``--enable-webp``, ``--enable-webpmux``, ``--enable-jpeg2000``,
``--enable-webpmux``, ``--enable-jpeg2000``,
``--enable-imagequant``. ``--enable-imagequant``.
Require that the corresponding feature is built. The build will raise Require that the corresponding feature is built. The build will raise
an exception if the libraries are not found. Webpmux (WebP metadata) an exception if the libraries are not found. Webpmux (WebP metadata)
@ -390,7 +394,7 @@ These platforms are built and tested for every change.
+----------------------------------+-------------------------------+-----------------------+ +----------------------------------+-------------------------------+-----------------------+
| Fedora 26 | 2.7 |x86-64 | | Fedora 26 | 2.7 |x86-64 |
+----------------------------------+-------------------------------+-----------------------+ +----------------------------------+-------------------------------+-----------------------+
| Mac OS X 10.10 Yosemite* | 2.7, 3.4, 3.5, 3.6 |x86-64 | | Mac OS X 10.10 Yosemite* | 2.7, 3.4, 3.5, 3.6, 3.7 |x86-64 |
+----------------------------------+-------------------------------+-----------------------+ +----------------------------------+-------------------------------+-----------------------+
| Ubuntu Linux 16.04 LTS | 2.7 |x86-64 | | Ubuntu Linux 16.04 LTS | 2.7 |x86-64 |
+----------------------------------+-------------------------------+-----------------------+ +----------------------------------+-------------------------------+-----------------------+

View File

@ -6,9 +6,10 @@ Added Complex Text Rendering
Pillow now supports complex text rendering for scripts requiring glyph Pillow now supports complex text rendering for scripts requiring glyph
composition and bidirectional flow. This optional feature adds three composition and bidirectional flow. This optional feature adds three
dependencies: harfbuzz, fribidi, and raqm. See the install dependencies: harfbuzz, fribidi, and raqm. See the `install
documentation for further details. This feature is tested and works on documentation <../installation.html>`_ for further details. This feature is
Unix and Mac, but has not yet been built on Windows platforms. tested and works on Unix and Mac, but has not yet been built on Windows
platforms.
New Optional Parameters New Optional Parameters
======================= =======================

View File

@ -0,0 +1,23 @@
5.3.0
-----
API Additions
=============
ImageOps.colorize
^^^^^^^^^^^^^^^^^
Previously ``ImageOps.colorize`` only supported two-color mapping with
``black`` and ``white`` arguments being mapped to 0 and 255 respectively.
Now it supports three-color mapping with the optional ``mid`` parameter, and
the positions for all three color arguments can each be optionally specified
(``blackpoint``, ``whitepoint`` and ``midpoint``).
For example, with all optional arguments::
ImageOps.colorize(im, black=(32, 37, 79), white='white', mid=(59, 101, 175),
blackpoint=15, whitepoint=240, midpoint=100)
Other Changes
=============

View File

@ -6,6 +6,7 @@ Release Notes
.. toctree:: .. toctree::
:maxdepth: 2 :maxdepth: 2
5.3.0
5.2.0 5.2.0
5.1.0 5.1.0
5.0.0 5.0.0

View File

@ -424,7 +424,8 @@ class pil_build_ext(build_ext):
best_path = None best_path = None
for name in os.listdir(program_files): for name in os.listdir(program_files):
if name.startswith('OpenJPEG '): if name.startswith('OpenJPEG '):
version = tuple(int(x) for x in name[9:].strip().split('.')) version = tuple(int(x) for x in
name[9:].strip().split('.'))
if version > best_version: if version > best_version:
best_version = version best_version = version
best_path = os.path.join(program_files, name) best_path = os.path.join(program_files, name)
@ -501,7 +502,8 @@ class pil_build_ext(build_ext):
# possible. # possible.
_add_directory(self.compiler.include_dirs, best_path, 0) _add_directory(self.compiler.include_dirs, best_path, 0)
feature.jpeg2000 = 'openjp2' feature.jpeg2000 = 'openjp2'
feature.openjpeg_version = '.'.join(str(x) for x in best_version) feature.openjpeg_version = '.'.join(str(x) for x in
best_version)
if feature.want('imagequant'): if feature.want('imagequant'):
_dbg('Looking for imagequant') _dbg('Looking for imagequant')
@ -516,7 +518,8 @@ class pil_build_ext(build_ext):
if _find_include_file(self, 'tiff.h'): if _find_include_file(self, 'tiff.h'):
if _find_library_file(self, "tiff"): if _find_library_file(self, "tiff"):
feature.tiff = "tiff" feature.tiff = "tiff"
if sys.platform == "win32" and _find_library_file(self, "libtiff"): if (sys.platform == "win32" and
_find_library_file(self, "libtiff")):
feature.tiff = "libtiff" feature.tiff = "libtiff"
if (sys.platform == "darwin" and if (sys.platform == "darwin" and
_find_library_file(self, "libtiff")): _find_library_file(self, "libtiff")):
@ -528,14 +531,16 @@ class pil_build_ext(build_ext):
# look for freetype2 include files # look for freetype2 include files
freetype_version = 0 freetype_version = 0
for subdir in self.compiler.include_dirs: for subdir in self.compiler.include_dirs:
_dbg('Checking for include file %s in %s', ("ft2build.h", subdir)) _dbg('Checking for include file %s in %s',
("ft2build.h", subdir))
if os.path.isfile(os.path.join(subdir, "ft2build.h")): if os.path.isfile(os.path.join(subdir, "ft2build.h")):
_dbg('Found %s in %s', ("ft2build.h", subdir)) _dbg('Found %s in %s', ("ft2build.h", subdir))
freetype_version = 21 freetype_version = 21
subdir = os.path.join(subdir, "freetype2") subdir = os.path.join(subdir, "freetype2")
break break
subdir = os.path.join(subdir, "freetype2") subdir = os.path.join(subdir, "freetype2")
_dbg('Checking for include file %s in %s', ("ft2build.h", subdir)) _dbg('Checking for include file %s in %s',
("ft2build.h", subdir))
if os.path.isfile(os.path.join(subdir, "ft2build.h")): if os.path.isfile(os.path.join(subdir, "ft2build.h")):
_dbg('Found %s in %s', ("ft2build.h", subdir)) _dbg('Found %s in %s', ("ft2build.h", subdir))
freetype_version = 21 freetype_version = 21

View File

@ -60,7 +60,14 @@ class BmpImageFile(ImageFile.ImageFile):
format_description = "Windows Bitmap" format_description = "Windows Bitmap"
format = "BMP" format = "BMP"
# --------------------------------------------------- BMP Compression values # --------------------------------------------------- BMP Compression values
COMPRESSIONS = {'RAW': 0, 'RLE8': 1, 'RLE4': 2, 'BITFIELDS': 3, 'JPEG': 4, 'PNG': 5} COMPRESSIONS = {
'RAW': 0,
'RLE8': 1,
'RLE4': 2,
'BITFIELDS': 3,
'JPEG': 4,
'PNG': 5
}
RAW, RLE8, RLE4, BITFIELDS, JPEG, PNG = 0, 1, 2, 3, 4, 5 RAW, RLE8, RLE4, BITFIELDS, JPEG, PNG = 0, 1, 2, 3, 4, 5
def _bitmap(self, header=0, offset=0): def _bitmap(self, header=0, offset=0):
@ -69,10 +76,13 @@ class BmpImageFile(ImageFile.ImageFile):
if header: if header:
seek(header) seek(header)
file_info = {} file_info = {}
file_info['header_size'] = i32(read(4)) # read bmp header size @offset 14 (this is part of the header size) # read bmp header size @offset 14 (this is part of the header size)
file_info['header_size'] = i32(read(4))
file_info['direction'] = -1 file_info['direction'] = -1
# --------------------- If requested, read header at a specific position # --------------------- If requested, read header at a specific position
header_data = ImageFile._safe_read(self.fp, file_info['header_size'] - 4) # read the rest of the bmp header, without its size # read the rest of the bmp header, without its size
header_data = ImageFile._safe_read(self.fp,
file_info['header_size'] - 4)
# --------------------------------------------------- IBM OS/2 Bitmap v1 # --------------------------------------------------- IBM OS/2 Bitmap v1
# ------ This format has different offsets because of width/height types # ------ This format has different offsets because of width/height types
if file_info['header_size'] == 12: if file_info['header_size'] == 12:
@ -88,12 +98,16 @@ class BmpImageFile(ImageFile.ImageFile):
file_info['y_flip'] = i8(header_data[7]) == 0xff file_info['y_flip'] = i8(header_data[7]) == 0xff
file_info['direction'] = 1 if file_info['y_flip'] else -1 file_info['direction'] = 1 if file_info['y_flip'] else -1
file_info['width'] = i32(header_data[0:4]) file_info['width'] = i32(header_data[0:4])
file_info['height'] = i32(header_data[4:8]) if not file_info['y_flip'] else 2**32 - i32(header_data[4:8]) file_info['height'] = (i32(header_data[4:8])
if not file_info['y_flip']
else 2**32 - i32(header_data[4:8]))
file_info['planes'] = i16(header_data[8:10]) file_info['planes'] = i16(header_data[8:10])
file_info['bits'] = i16(header_data[10:12]) file_info['bits'] = i16(header_data[10:12])
file_info['compression'] = i32(header_data[12:16]) file_info['compression'] = i32(header_data[12:16])
file_info['data_size'] = i32(header_data[16:20]) # byte size of pixel data # byte size of pixel data
file_info['pixels_per_meter'] = (i32(header_data[20:24]), i32(header_data[24:28])) file_info['data_size'] = i32(header_data[16:20])
file_info['pixels_per_meter'] = (i32(header_data[20:24]),
i32(header_data[24:28]))
file_info['colors'] = i32(header_data[28:32]) file_info['colors'] = i32(header_data[28:32])
file_info['palette_padding'] = 4 file_info['palette_padding'] = 4
self.info["dpi"] = tuple( self.info["dpi"] = tuple(
@ -101,21 +115,32 @@ class BmpImageFile(ImageFile.ImageFile):
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', 'g_mask', 'b_mask', 'a_mask']): for idx, mask in enumerate(['r_mask',
'g_mask',
'b_mask',
'a_mask']):
file_info[mask] = i32(header_data[36+idx*4:40+idx*4]) file_info[mask] = i32(header_data[36+idx*4:40+idx*4])
else: else:
# 40 byte headers only have the three components in the bitfields masks, # 40 byte headers only have the three components in the
# bitfields masks,
# ref: https://msdn.microsoft.com/en-us/library/windows/desktop/dd183376(v=vs.85).aspx # ref: https://msdn.microsoft.com/en-us/library/windows/desktop/dd183376(v=vs.85).aspx
# See also https://github.com/python-pillow/Pillow/issues/1293 # See also https://github.com/python-pillow/Pillow/issues/1293
# There is a 4th component in the RGBQuad, in the alpha location, but it # There is a 4th component in the RGBQuad, in the alpha
# is listed as a reserved component, and it is not generally an alpha channel # location, but it is listed as a reserved component,
# and it is not generally an alpha channel
file_info['a_mask'] = 0x0 file_info['a_mask'] = 0x0
for mask in ['r_mask', 'g_mask', 'b_mask']: for mask in ['r_mask', 'g_mask', 'b_mask']:
file_info[mask] = i32(read(4)) file_info[mask] = i32(read(4))
file_info['rgb_mask'] = (file_info['r_mask'], file_info['g_mask'], file_info['b_mask']) file_info['rgb_mask'] = (file_info['r_mask'],
file_info['rgba_mask'] = (file_info['r_mask'], file_info['g_mask'], file_info['b_mask'], file_info['a_mask']) file_info['g_mask'],
file_info['b_mask'])
file_info['rgba_mask'] = (file_info['r_mask'],
file_info['g_mask'],
file_info['b_mask'],
file_info['a_mask'])
else: else:
raise IOError("Unsupported BMP header type (%d)" % file_info['header_size']) raise IOError("Unsupported BMP header type (%d)" %
file_info['header_size'])
# ------------------ Special case : header is reported 40, which # ------------------ Special case : header is reported 40, which
# ---------------------- is shorter than real size for bpp >= 16 # ---------------------- is shorter than real size for bpp >= 16
self.size = file_info['width'], file_info['height'] self.size = file_info['width'], file_info['height']
@ -127,11 +152,15 @@ class BmpImageFile(ImageFile.ImageFile):
# ----------------------- Check bit depth for unusual unsupported values # ----------------------- Check bit depth for unusual unsupported values
self.mode, raw_mode = BIT2MODE.get(file_info['bits'], (None, None)) self.mode, raw_mode = BIT2MODE.get(file_info['bits'], (None, None))
if self.mode is None: if self.mode is None:
raise IOError("Unsupported BMP pixel depth (%d)" % file_info['bits']) raise IOError("Unsupported BMP pixel depth (%d)"
% file_info['bits'])
# ----------------- Process BMP with Bitfields compression (not palette) # ----------------- Process BMP with Bitfields compression (not palette)
if file_info['compression'] == self.BITFIELDS: if file_info['compression'] == self.BITFIELDS:
SUPPORTED = { SUPPORTED = {
32: [(0xff0000, 0xff00, 0xff, 0x0), (0xff0000, 0xff00, 0xff, 0xff000000), (0x0, 0x0, 0x0, 0x0), (0xff000000, 0xff0000, 0xff00, 0x0)], 32: [(0xff0000, 0xff00, 0xff, 0x0),
(0xff0000, 0xff00, 0xff, 0xff000000),
(0x0, 0x0, 0x0, 0x0),
(0xff000000, 0xff0000, 0xff00, 0x0)],
24: [(0xff0000, 0xff00, 0xff)], 24: [(0xff0000, 0xff00, 0xff)],
16: [(0xf800, 0x7e0, 0x1f), (0x7c00, 0x3e0, 0x1f)] 16: [(0xf800, 0x7e0, 0x1f), (0x7c00, 0x3e0, 0x1f)]
} }
@ -145,11 +174,15 @@ class BmpImageFile(ImageFile.ImageFile):
(16, (0x7c00, 0x3e0, 0x1f)): "BGR;15" (16, (0x7c00, 0x3e0, 0x1f)): "BGR;15"
} }
if file_info['bits'] in SUPPORTED: if file_info['bits'] in SUPPORTED:
if file_info['bits'] == 32 and file_info['rgba_mask'] in SUPPORTED[file_info['bits']]: if file_info['bits'] == 32 and \
file_info['rgba_mask'] in SUPPORTED[file_info['bits']]:
raw_mode = MASK_MODES[(file_info['bits'], file_info['rgba_mask'])] raw_mode = MASK_MODES[(file_info['bits'], file_info['rgba_mask'])]
self.mode = "RGBA" if raw_mode in ("BGRA",) else self.mode self.mode = "RGBA" if raw_mode in ("BGRA",) else self.mode
elif file_info['bits'] in (24, 16) and file_info['rgb_mask'] in SUPPORTED[file_info['bits']]: elif (file_info['bits'] in (24, 16) and
raw_mode = MASK_MODES[(file_info['bits'], file_info['rgb_mask'])] file_info['rgb_mask'] in SUPPORTED[file_info['bits']]):
raw_mode = MASK_MODES[
(file_info['bits'], file_info['rgb_mask'])
]
else: else:
raise IOError("Unsupported BMP bitfields layout") raise IOError("Unsupported BMP bitfields layout")
else: else:
@ -158,17 +191,20 @@ class BmpImageFile(ImageFile.ImageFile):
if file_info['bits'] == 32 and header == 22: # 32-bit .cur offset if file_info['bits'] == 32 and header == 22: # 32-bit .cur offset
raw_mode, self.mode = "BGRA", "RGBA" raw_mode, self.mode = "BGRA", "RGBA"
else: else:
raise IOError("Unsupported BMP compression (%d)" % file_info['compression']) raise IOError("Unsupported BMP compression (%d)" %
file_info['compression'])
# ---------------- Once the header is processed, process the palette/LUT # ---------------- Once the header is processed, process the palette/LUT
if self.mode == "P": # Paletted for 1, 4 and 8 bit images if self.mode == "P": # Paletted for 1, 4 and 8 bit images
# ----------------------------------------------------- 1-bit images # ----------------------------------------------------- 1-bit images
if not (0 < file_info['colors'] <= 65536): if not (0 < file_info['colors'] <= 65536):
raise IOError("Unsupported BMP Palette size (%d)" % file_info['colors']) raise IOError("Unsupported BMP Palette size (%d)" %
file_info['colors'])
else: else:
padding = file_info['palette_padding'] padding = file_info['palette_padding']
palette = read(padding * file_info['colors']) palette = read(padding * file_info['colors'])
greyscale = True greyscale = True
indices = (0, 255) if file_info['colors'] == 2 else list(range(file_info['colors'])) indices = (0, 255) if file_info['colors'] == 2 else \
list(range(file_info['colors']))
# ------------------ Check if greyscale and ignore palette if so # ------------------ Check if greyscale and ignore palette if so
for ind, val in enumerate(indices): for ind, val in enumerate(indices):
rgb = palette[ind*padding:ind*padding + 3] rgb = palette[ind*padding:ind*padding + 3]
@ -180,13 +216,19 @@ class BmpImageFile(ImageFile.ImageFile):
raw_mode = self.mode raw_mode = self.mode
else: else:
self.mode = "P" self.mode = "P"
self.palette = ImagePalette.raw("BGRX" if padding == 4 else "BGR", palette) self.palette = ImagePalette.raw(
"BGRX" if padding == 4 else "BGR", palette)
# ----------------------------- Finally set the tile data for the plugin # ----------------------------- Finally set the tile data for the plugin
self.info['compression'] = file_info['compression'] self.info['compression'] = file_info['compression']
self.tile = [('raw', (0, 0, file_info['width'], file_info['height']), offset or self.fp.tell(), self.tile = [
(raw_mode, ((file_info['width'] * file_info['bits'] + 31) >> 3) & (~3), file_info['direction']) ('raw',
)] (0, 0, file_info['width'], file_info['height']),
offset or self.fp.tell(),
(raw_mode,
((file_info['width'] * file_info['bits'] + 31) >> 3) & (~3),
file_info['direction']))
]
def _open(self): def _open(self):
""" Open file, check magic number and read header """ """ Open file, check magic number and read header """

View File

@ -107,10 +107,10 @@ class ContainerIO(object):
:returns: A list of 8-bit strings. :returns: A list of 8-bit strings.
""" """
l = [] lines = []
while True: while True:
s = self.readline() s = self.readline()
if not s: if not s:
break break
l.append(s) lines.append(s)
return l return lines

View File

@ -142,7 +142,8 @@ class DdsImageFile(ImageFile.ImageFile):
# ignoring flags which pertain to volume textures and cubemaps # ignoring flags which pertain to volume textures and cubemaps
dxt10 = BytesIO(self.fp.read(20)) dxt10 = BytesIO(self.fp.read(20))
dxgi_format, dimension = struct.unpack("<II", dxt10.read(8)) dxgi_format, dimension = struct.unpack("<II", dxt10.read(8))
if dxgi_format in (DXGI_FORMAT_BC7_TYPELESS, DXGI_FORMAT_BC7_UNORM): if dxgi_format in (DXGI_FORMAT_BC7_TYPELESS,
DXGI_FORMAT_BC7_UNORM):
self.pixel_format = "BC7" self.pixel_format = "BC7"
n = 7 n = 7
elif dxgi_format == DXGI_FORMAT_BC7_UNORM_SRGB: elif dxgi_format == DXGI_FORMAT_BC7_UNORM_SRGB:

View File

@ -26,7 +26,6 @@ import os
import sys import sys
from . import Image, ImageFile from . import Image, ImageFile
from ._binary import i32le as i32 from ._binary import i32le as i32
from ._util import py3
__version__ = "0.5" __version__ = "0.5"
@ -206,16 +205,7 @@ class EpsImageFile(ImageFile.ImageFile):
# Rewrap the open file pointer in something that will # Rewrap the open file pointer in something that will
# convert line endings and decode to latin-1. # convert line endings and decode to latin-1.
try: fp = PSFile(self.fp)
if py3:
# Python3, can use bare open command.
fp = open(self.fp.name, "Ur", encoding='latin-1')
else:
# Python2, no encoding conversion necessary
fp = open(self.fp.name, "Ur")
except:
# Expect this for bytesio/stringio
fp = PSFile(self.fp)
# go to offset - start of "%!PS" # go to offset - start of "%!PS"
fp.seek(offset) fp.seek(offset)

View File

@ -9,7 +9,8 @@ Full text of the CC0 license:
Independence War 2: Edge Of Chaos - Texture File Format - 16 October 2001 Independence War 2: Edge Of Chaos - Texture File Format - 16 October 2001
The textures used for 3D objects in Independence War 2: Edge Of Chaos are in a The textures used for 3D objects in Independence War 2: Edge Of Chaos are in a
packed custom format called FTEX. This file format uses file extensions FTC and FTU. packed custom format called FTEX. This file format uses file extensions FTC
and FTU.
* FTC files are compressed textures (using standard texture compression). * FTC files are compressed textures (using standard texture compression).
* FTU files are not compressed. * FTU files are not compressed.
Texture File Format Texture File Format
@ -24,18 +25,21 @@ Where:
* The "magic" number is "FTEX". * The "magic" number is "FTEX".
* "width" and "height" are the dimensions of the texture. * "width" and "height" are the dimensions of the texture.
* "mipmap_count" is the number of mipmaps in the texture. * "mipmap_count" is the number of mipmaps in the texture.
* "format_count" is the number of texture formats (different versions of the same texture) in this file. * "format_count" is the number of texture formats (different versions of the
same texture) in this file.
{format_directory} = format_count * { u32:format, u32:where } {format_directory} = format_count * { u32:format, u32:where }
The format value is 0 for DXT1 compressed textures and 1 for 24-bit RGB uncompressed textures. The format value is 0 for DXT1 compressed textures and 1 for 24-bit RGB
uncompressed textures.
The texture data for a format starts at the position "where" in the file. The texture data for a format starts at the position "where" in the file.
Each set of texture data in the file has the following structure: Each set of texture data in the file has the following structure:
{data} = format_count * { u32:mipmap_size, mipmap_size * { u8 } } {data} = format_count * { u32:mipmap_size, mipmap_size * { u8 } }
* "mipmap_size" is the number of bytes in that mip level. For compressed textures this is the * "mipmap_size" is the number of bytes in that mip level. For compressed
size of the texture data compressed with DXT1. For 24 bit uncompressed textures, this is 3 * width * height. textures this is the size of the texture data compressed with DXT1. For 24 bit
Following this are the image bytes for that mipmap level. uncompressed textures, this is 3 * width * height. Following this are the image
bytes for that mipmap level.
Note: All data is stored in little-Endian (Intel) byte order. Note: All data is stored in little-Endian (Intel) byte order.
""" """
@ -62,7 +66,8 @@ class FtexImageFile(ImageFile.ImageFile):
self.mode = "RGB" self.mode = "RGB"
# Only support single-format files. I don't know of any multi-format file. # Only support single-format files.
# I don't know of any multi-format file.
assert format_count == 1 assert format_count == 1
format, where = struct.unpack("<2i", self.fp.read(8)) format, where = struct.unpack("<2i", self.fp.read(8))
@ -77,7 +82,8 @@ class FtexImageFile(ImageFile.ImageFile):
elif format == FORMAT_UNCOMPRESSED: elif format == FORMAT_UNCOMPRESSED:
self.tile = [("raw", (0, 0) + self.size, 0, ('RGB', 0, 1))] self.tile = [("raw", (0, 0) + self.size, 0, ('RGB', 0, 1))]
else: else:
raise ValueError("Invalid texture compression format: %r" % (format)) raise ValueError(
"Invalid texture compression format: %r" % (format))
self.fp.close() self.fp.close()
self.fp = BytesIO(data) self.fp = BytesIO(data)

View File

@ -29,7 +29,8 @@ from ._binary import i32be as i32
def _accept(prefix): def _accept(prefix):
return len(prefix) >= 8 and i32(prefix[:4]) >= 20 and i32(prefix[4:8]) in (1, 2) return len(prefix) >= 8 and \
i32(prefix[:4]) >= 20 and i32(prefix[4:8]) in (1, 2)
## ##
@ -54,7 +55,8 @@ class GbrImageFile(ImageFile.ImageFile):
if width <= 0 or height <= 0: if width <= 0 or height <= 0:
raise SyntaxError("not a GIMP brush") raise SyntaxError("not a GIMP brush")
if color_depth not in (1, 4): if color_depth not in (1, 4):
raise SyntaxError("Unsupported GIMP brush color depth: %s" % color_depth) raise SyntaxError(
"Unsupported GIMP brush color depth: %s" % color_depth)
if version == 1: if version == 1:
comment_length = header_size-20 comment_length = header_size-20

View File

@ -397,7 +397,8 @@ def _write_multiple_frames(im, fp, palette):
im_frames = [] im_frames = []
frame_count = 0 frame_count = 0
for imSequence in itertools.chain([im], im.encoderinfo.get("append_images", [])): for imSequence in itertools.chain([im],
im.encoderinfo.get("append_images", [])):
for im_frame in ImageSequence.Iterator(imSequence): for im_frame in ImageSequence.Iterator(imSequence):
# a copy is required here since seek can still mutate the image # a copy is required here since seek can still mutate the image
im_frame = _normalize_mode(im_frame.copy()) im_frame = _normalize_mode(im_frame.copy())
@ -413,17 +414,19 @@ def _write_multiple_frames(im, fp, palette):
if im_frames: if im_frames:
# delta frame # delta frame
previous = im_frames[-1] previous = im_frames[-1]
if _get_palette_bytes(im_frame) == _get_palette_bytes(previous['im']): if _get_palette_bytes(im_frame) == \
_get_palette_bytes(previous['im']):
delta = ImageChops.subtract_modulo(im_frame, delta = ImageChops.subtract_modulo(im_frame,
previous['im']) previous['im'])
else: else:
delta = ImageChops.subtract_modulo(im_frame.convert('RGB'), delta = ImageChops.subtract_modulo(
previous['im'].convert('RGB')) im_frame.convert('RGB'), previous['im'].convert('RGB'))
bbox = delta.getbbox() bbox = delta.getbbox()
if not bbox: if not bbox:
# This frame is identical to the previous frame # This frame is identical to the previous frame
if duration: if duration:
previous['encoderinfo']['duration'] += encoderinfo['duration'] previous['encoderinfo']['duration'] += \
encoderinfo['duration']
continue continue
else: else:
bbox = None bbox = None
@ -525,7 +528,8 @@ def _write_local_header(fp, im, offset, flags):
o8(transparency) + # transparency index o8(transparency) + # transparency index
o8(0)) o8(0))
if "comment" in im.encoderinfo and 1 <= len(im.encoderinfo["comment"]) <= 255: if "comment" in im.encoderinfo and \
1 <= len(im.encoderinfo["comment"]) <= 255:
fp.write(b"!" + fp.write(b"!" +
o8(254) + # extension intro o8(254) + # extension intro
o8(len(im.encoderinfo["comment"])) + o8(len(im.encoderinfo["comment"])) +
@ -543,7 +547,6 @@ def _write_local_header(fp, im, offset, flags):
o8(0)) o8(0))
include_color_table = im.encoderinfo.get('include_color_table') include_color_table = im.encoderinfo.get('include_color_table')
if include_color_table: if include_color_table:
palette = im.encoderinfo.get("palette", None)
palette_bytes = _get_palette_bytes(im) palette_bytes = _get_palette_bytes(im)
color_table_size = _get_color_table_size(palette_bytes) color_table_size = _get_color_table_size(palette_bytes)
if color_table_size: if color_table_size:
@ -692,7 +695,8 @@ def _get_global_header(im, info):
for extensionKey in ["transparency", "duration", "loop", "comment"]: for extensionKey in ["transparency", "duration", "loop", "comment"]:
if info and extensionKey in info: if info and extensionKey in info:
if ((extensionKey == "duration" and info[extensionKey] == 0) or if ((extensionKey == "duration" and info[extensionKey] == 0) or
(extensionKey == "comment" and not (1 <= len(info[extensionKey]) <= 255))): (extensionKey == "comment" and
not (1 <= len(info[extensionKey]) <= 255))):
continue continue
version = b"89a" version = b"89a"
break break

View File

@ -2440,9 +2440,20 @@ def fromarray(obj, mode=None):
Creates an image memory from an object exporting the array interface Creates an image memory from an object exporting the array interface
(using the buffer protocol). (using the buffer protocol).
If obj is not contiguous, then the tobytes method is called If **obj** is not contiguous, then the tobytes method is called
and :py:func:`~PIL.Image.frombuffer` is used. and :py:func:`~PIL.Image.frombuffer` is used.
If you have an image in NumPy::
from PIL import Image
import numpy as np
im = Image.open('hopper.jpg')
a = numpy.asarray(im)
Then this can be used to convert it to a Pillow image::
im = Image.fromarray(a)
:param obj: Object with array interface :param obj: Object with array interface
:param mode: Mode to use (will be determined from type if None) :param mode: Mode to use (will be determined from type if None)
See: :ref:`concept-modes`. See: :ref:`concept-modes`.

View File

@ -305,10 +305,10 @@ def profileToProfile(
:param renderingIntent: Integer (0-3) specifying the rendering intent you :param renderingIntent: Integer (0-3) specifying the rendering intent you
wish to use for the transform wish to use for the transform
INTENT_PERCEPTUAL = 0 (DEFAULT) (ImageCms.INTENT_PERCEPTUAL) ImageCms.INTENT_PERCEPTUAL = 0 (DEFAULT)
INTENT_RELATIVE_COLORIMETRIC = 1 (ImageCms.INTENT_RELATIVE_COLORIMETRIC) ImageCms.INTENT_RELATIVE_COLORIMETRIC = 1
INTENT_SATURATION = 2 (ImageCms.INTENT_SATURATION) ImageCms.INTENT_SATURATION = 2
INTENT_ABSOLUTE_COLORIMETRIC = 3 (ImageCms.INTENT_ABSOLUTE_COLORIMETRIC) ImageCms.INTENT_ABSOLUTE_COLORIMETRIC = 3
see the pyCMS documentation for details on rendering intents and what see the pyCMS documentation for details on rendering intents and what
they do. they do.
@ -424,10 +424,10 @@ def buildTransform(
:param renderingIntent: Integer (0-3) specifying the rendering intent you :param renderingIntent: Integer (0-3) specifying the rendering intent you
wish to use for the transform wish to use for the transform
INTENT_PERCEPTUAL = 0 (DEFAULT) (ImageCms.INTENT_PERCEPTUAL) ImageCms.INTENT_PERCEPTUAL = 0 (DEFAULT)
INTENT_RELATIVE_COLORIMETRIC = 1 (ImageCms.INTENT_RELATIVE_COLORIMETRIC) ImageCms.INTENT_RELATIVE_COLORIMETRIC = 1
INTENT_SATURATION = 2 (ImageCms.INTENT_SATURATION) ImageCms.INTENT_SATURATION = 2
INTENT_ABSOLUTE_COLORIMETRIC = 3 (ImageCms.INTENT_ABSOLUTE_COLORIMETRIC) ImageCms.INTENT_ABSOLUTE_COLORIMETRIC = 3
see the pyCMS documentation for details on rendering intents and what see the pyCMS documentation for details on rendering intents and what
they do. they do.
@ -512,20 +512,20 @@ def buildProofTransform(
:param renderingIntent: Integer (0-3) specifying the rendering intent you :param renderingIntent: Integer (0-3) specifying the rendering intent you
wish to use for the input->proof (simulated) transform wish to use for the input->proof (simulated) transform
INTENT_PERCEPTUAL = 0 (DEFAULT) (ImageCms.INTENT_PERCEPTUAL) ImageCms.INTENT_PERCEPTUAL = 0 (DEFAULT)
INTENT_RELATIVE_COLORIMETRIC = 1 (ImageCms.INTENT_RELATIVE_COLORIMETRIC) ImageCms.INTENT_RELATIVE_COLORIMETRIC = 1
INTENT_SATURATION = 2 (ImageCms.INTENT_SATURATION) ImageCms.INTENT_SATURATION = 2
INTENT_ABSOLUTE_COLORIMETRIC = 3 (ImageCms.INTENT_ABSOLUTE_COLORIMETRIC) ImageCms.INTENT_ABSOLUTE_COLORIMETRIC = 3
see the pyCMS documentation for details on rendering intents and what see the pyCMS documentation for details on rendering intents and what
they do. they do.
:param proofRenderingIntent: Integer (0-3) specifying the rendering intent you :param proofRenderingIntent: Integer (0-3) specifying the rendering intent
wish to use for proof->output transform you wish to use for proof->output transform
INTENT_PERCEPTUAL = 0 (DEFAULT) (ImageCms.INTENT_PERCEPTUAL) ImageCms.INTENT_PERCEPTUAL = 0 (DEFAULT)
INTENT_RELATIVE_COLORIMETRIC = 1 (ImageCms.INTENT_RELATIVE_COLORIMETRIC) ImageCms.INTENT_RELATIVE_COLORIMETRIC = 1
INTENT_SATURATION = 2 (ImageCms.INTENT_SATURATION) ImageCms.INTENT_SATURATION = 2
INTENT_ABSOLUTE_COLORIMETRIC = 3 (ImageCms.INTENT_ABSOLUTE_COLORIMETRIC) ImageCms.INTENT_ABSOLUTE_COLORIMETRIC = 3
see the pyCMS documentation for details on rendering intents and what see the pyCMS documentation for details on rendering intents and what
they do. they do.
@ -875,10 +875,10 @@ def getDefaultIntent(profile):
:returns: Integer 0-3 specifying the default rendering intent for this :returns: Integer 0-3 specifying the default rendering intent for this
profile. profile.
INTENT_PERCEPTUAL = 0 (DEFAULT) (ImageCms.INTENT_PERCEPTUAL) ImageCms.INTENT_PERCEPTUAL = 0 (DEFAULT)
INTENT_RELATIVE_COLORIMETRIC = 1 (ImageCms.INTENT_RELATIVE_COLORIMETRIC) ImageCms.INTENT_RELATIVE_COLORIMETRIC = 1
INTENT_SATURATION = 2 (ImageCms.INTENT_SATURATION) ImageCms.INTENT_SATURATION = 2
INTENT_ABSOLUTE_COLORIMETRIC = 3 (ImageCms.INTENT_ABSOLUTE_COLORIMETRIC) ImageCms.INTENT_ABSOLUTE_COLORIMETRIC = 3
see the pyCMS documentation for details on rendering intents and what see the pyCMS documentation for details on rendering intents and what
they do. they do.
@ -913,15 +913,15 @@ def isIntentSupported(profile, intent, direction):
:param intent: Integer (0-3) specifying the rendering intent you wish to :param intent: Integer (0-3) specifying the rendering intent you wish to
use with this profile use with this profile
INTENT_PERCEPTUAL = 0 (DEFAULT) (ImageCms.INTENT_PERCEPTUAL) ImageCms.INTENT_PERCEPTUAL = 0 (DEFAULT)
INTENT_RELATIVE_COLORIMETRIC = 1 (ImageCms.INTENT_RELATIVE_COLORIMETRIC) ImageCms.INTENT_RELATIVE_COLORIMETRIC = 1
INTENT_SATURATION = 2 (ImageCms.INTENT_SATURATION) ImageCms.INTENT_SATURATION = 2
INTENT_ABSOLUTE_COLORIMETRIC = 3 (ImageCms.INTENT_ABSOLUTE_COLORIMETRIC) ImageCms.INTENT_ABSOLUTE_COLORIMETRIC = 3
see the pyCMS documentation for details on rendering intents and what see the pyCMS documentation for details on rendering intents and what
they do. they do.
:param direction: Integer specifying if the profile is to be used for input, :param direction: Integer specifying if the profile is to be used for
output, or proof input, output, or proof
INPUT = 0 (or use ImageCms.DIRECTION_INPUT) INPUT = 0 (or use ImageCms.DIRECTION_INPUT)
OUTPUT = 1 (or use ImageCms.DIRECTION_OUTPUT) OUTPUT = 1 (or use ImageCms.DIRECTION_OUTPUT)

View File

@ -138,7 +138,7 @@ class ImageDraw(object):
ink, fill = self._getink(outline, fill) ink, fill = self._getink(outline, fill)
if fill is not None: if fill is not None:
self.draw.draw_chord(xy, start, end, fill, 1) self.draw.draw_chord(xy, start, end, fill, 1)
if ink is not None: if ink is not None and ink != fill:
self.draw.draw_chord(xy, start, end, ink, 0) self.draw.draw_chord(xy, start, end, ink, 0)
def ellipse(self, xy, fill=None, outline=None): def ellipse(self, xy, fill=None, outline=None):
@ -146,7 +146,7 @@ class ImageDraw(object):
ink, fill = self._getink(outline, fill) ink, fill = self._getink(outline, fill)
if fill is not None: if fill is not None:
self.draw.draw_ellipse(xy, fill, 1) self.draw.draw_ellipse(xy, fill, 1)
if ink is not None: if ink is not None and ink != fill:
self.draw.draw_ellipse(xy, ink, 0) self.draw.draw_ellipse(xy, ink, 0)
def line(self, xy, fill=None, width=0): def line(self, xy, fill=None, width=0):
@ -161,7 +161,7 @@ class ImageDraw(object):
ink, fill = self._getink(outline, fill) ink, fill = self._getink(outline, fill)
if fill is not None: if fill is not None:
self.draw.draw_outline(shape, fill, 1) self.draw.draw_outline(shape, fill, 1)
if ink is not None: if ink is not None and ink != fill:
self.draw.draw_outline(shape, ink, 0) self.draw.draw_outline(shape, ink, 0)
def pieslice(self, xy, start, end, fill=None, outline=None): def pieslice(self, xy, start, end, fill=None, outline=None):
@ -169,7 +169,7 @@ class ImageDraw(object):
ink, fill = self._getink(outline, fill) ink, fill = self._getink(outline, fill)
if fill is not None: if fill is not None:
self.draw.draw_pieslice(xy, start, end, fill, 1) self.draw.draw_pieslice(xy, start, end, fill, 1)
if ink is not None: if ink is not None and ink != fill:
self.draw.draw_pieslice(xy, start, end, ink, 0) self.draw.draw_pieslice(xy, start, end, ink, 0)
def point(self, xy, fill=None): def point(self, xy, fill=None):
@ -183,7 +183,7 @@ class ImageDraw(object):
ink, fill = self._getink(outline, fill) ink, fill = self._getink(outline, fill)
if fill is not None: if fill is not None:
self.draw.draw_polygon(xy, fill, 1) self.draw.draw_polygon(xy, fill, 1)
if ink is not None: if ink is not None and ink != fill:
self.draw.draw_polygon(xy, ink, 0) self.draw.draw_polygon(xy, ink, 0)
def rectangle(self, xy, fill=None, outline=None): def rectangle(self, xy, fill=None, outline=None):
@ -191,7 +191,7 @@ class ImageDraw(object):
ink, fill = self._getink(outline, fill) ink, fill = self._getink(outline, fill)
if fill is not None: if fill is not None:
self.draw.draw_rectangle(xy, fill, 1) self.draw.draw_rectangle(xy, fill, 1)
if ink is not None: if ink is not None and ink != fill:
self.draw.draw_rectangle(xy, ink, 0) self.draw.draw_rectangle(xy, ink, 0)
def _multiline_check(self, text): def _multiline_check(self, text):
@ -217,7 +217,8 @@ class ImageDraw(object):
ink = fill ink = fill
if ink is not None: if ink is not None:
try: try:
mask, offset = font.getmask2(text, self.fontmode, *args, **kwargs) mask, offset = font.getmask2(text, self.fontmode,
*args, **kwargs)
xy = xy[0] + offset[0], xy[1] + offset[1] xy = xy[0] + offset[0], xy[1] + offset[1]
except AttributeError: except AttributeError:
try: try:
@ -350,38 +351,27 @@ def floodfill(image, xy, value, border=None, thresh=0):
except (ValueError, IndexError): except (ValueError, IndexError):
return # seed point outside image return # seed point outside image
edge = [(x, y)] edge = [(x, y)]
if border is None: while edge:
while edge: newedge = []
newedge = [] for (x, y) in edge:
for (x, y) in edge: for (s, t) in ((x+1, y), (x-1, y), (x, y+1), (x, y-1)):
for (s, t) in ((x+1, y), (x-1, y), (x, y+1), (x, y-1)): try:
try: p = pixel[s, t]
p = pixel[s, t] except (ValueError, IndexError):
except IndexError: pass
pass else:
if border is None:
fill = _color_diff(p, background) <= thresh
else: else:
if _color_diff(p, background) <= thresh: fill = p != value and p != border
pixel[s, t] = value if fill:
newedge.append((s, t)) pixel[s, t] = value
edge = newedge newedge.append((s, t))
else: edge = newedge
while edge:
newedge = []
for (x, y) in edge:
for (s, t) in ((x+1, y), (x-1, y), (x, y+1), (x, y-1)):
try:
p = pixel[s, t]
except IndexError:
pass
else:
if p != value and p != border:
pixel[s, t] = value
newedge.append((s, t))
edge = newedge
def _color_diff(rgb1, rgb2): def _color_diff(rgb1, rgb2):
""" """
Uses 1-norm distance to calculate difference between two rgb values. Uses 1-norm distance to calculate difference between two rgb values.
""" """
return abs(rgb1[0]-rgb2[0]) + abs(rgb1[1]-rgb2[1]) + abs(rgb1[2]-rgb2[2]) return abs(rgb1[0]-rgb2[0]) + abs(rgb1[1]-rgb2[1]) + abs(rgb1[2]-rgb2[2])

View File

@ -51,7 +51,8 @@ class Color(_Enhance):
if 'A' in image.getbands(): if 'A' in image.getbands():
self.intermediate_mode = 'LA' self.intermediate_mode = 'LA'
self.degenerate = image.convert(self.intermediate_mode).convert(image.mode) self.degenerate = image.convert(
self.intermediate_mode).convert(image.mode)
class Contrast(_Enhance): class Contrast(_Enhance):

View File

@ -166,8 +166,9 @@ class ImageFile(Image.Image):
if use_mmap: if use_mmap:
# try memory mapping # try memory mapping
decoder_name, extents, offset, args = self.tile[0] decoder_name, extents, offset, args = self.tile[0]
if decoder_name == "raw" and len(args) >= 3 and args[0] == self.mode \ if decoder_name == "raw" and len(args) >= 3 and \
and args[0] in Image._MAPMODES: args[0] == self.mode and \
args[0] in Image._MAPMODES:
try: try:
if hasattr(Image.core, "map"): if hasattr(Image.core, "map"):
# use built-in mapper WIN32 only # use built-in mapper WIN32 only
@ -180,12 +181,14 @@ class ImageFile(Image.Image):
# use mmap, if possible # use mmap, if possible
import mmap import mmap
with open(self.filename, "r") as fp: with open(self.filename, "r") as fp:
self.map = mmap.mmap(fp.fileno(), 0, access=mmap.ACCESS_READ) self.map = mmap.mmap(fp.fileno(), 0,
access=mmap.ACCESS_READ)
self.im = Image.core.map_buffer( self.im = Image.core.map_buffer(
self.map, self.size, decoder_name, extents, offset, args self.map, self.size, decoder_name, extents,
) offset, args)
readonly = 1 readonly = 1
# After trashing self.im, we might need to reload the palette data. # After trashing self.im,
# we might need to reload the palette data.
if self.palette: if self.palette:
self.palette.dirty = 1 self.palette.dirty = 1
except (AttributeError, EnvironmentError, ImportError): except (AttributeError, EnvironmentError, ImportError):
@ -217,7 +220,8 @@ class ImageFile(Image.Image):
while True: while True:
try: try:
s = read(self.decodermaxblock) s = read(self.decodermaxblock)
except (IndexError, struct.error): # truncated png/gif except (IndexError, struct.error):
# truncated png/gif
if LOAD_TRUNCATED_IMAGES: if LOAD_TRUNCATED_IMAGES:
break break
else: else:
@ -229,7 +233,8 @@ class ImageFile(Image.Image):
else: else:
self.tile = [] self.tile = []
raise IOError("image file is truncated " raise IOError("image file is truncated "
"(%d bytes not processed)" % len(b)) "(%d bytes not processed)" %
len(b))
b = b + s b = b + s
n, err_code = decoder.decode(b) n, err_code = decoder.decode(b)
@ -588,10 +593,12 @@ class PyDecoder(object):
""" """
Override to perform the decoding process. Override to perform the decoding process.
:param buffer: A bytes object with the data to be decoded. If `handles_eof` :param buffer: A bytes object with the data to be decoded.
is set, then `buffer` will be empty and `self.fd` will be set. If `handles_eof` is set, then `buffer` will be empty and `self.fd`
:returns: A tuple of (bytes consumed, errcode). If finished with decoding will be set.
return <0 for the bytes consumed. Err codes are from `ERRORS` :returns: A tuple of (bytes consumed, errcode).
If finished with decoding return <0 for the bytes consumed.
Err codes are from `ERRORS`
""" """
raise NotImplementedError() raise NotImplementedError()
@ -650,8 +657,8 @@ class PyDecoder(object):
Convenience method to set the internal image from a stream of raw data Convenience method to set the internal image from a stream of raw data
:param data: Bytes to be set :param data: Bytes to be set
:param rawmode: The rawmode to be used for the decoder. If not specified, :param rawmode: The rawmode to be used for the decoder.
it will default to the mode of the image If not specified, it will default to the mode of the image
:returns: None :returns: None
""" """

View File

@ -33,7 +33,14 @@ class MultibandFilter(Filter):
pass pass
class Kernel(MultibandFilter): class BuiltinFilter(MultibandFilter):
def filter(self, image):
if image.mode == "P":
raise ValueError("cannot filter palette images")
return image.filter(*self.filterargs)
class Kernel(BuiltinFilter):
""" """
Create a convolution kernel. The current version only Create a convolution kernel. The current version only
supports 3x3 and 5x5 integer and floating point kernels. supports 3x3 and 5x5 integer and floating point kernels.
@ -60,16 +67,6 @@ class Kernel(MultibandFilter):
raise ValueError("not enough coefficients in kernel") raise ValueError("not enough coefficients in kernel")
self.filterargs = size, scale, offset, kernel self.filterargs = size, scale, offset, kernel
def filter(self, image):
if image.mode == "P":
raise ValueError("cannot filter palette images")
return image.filter(*self.filterargs)
class BuiltinFilter(Kernel):
def __init__(self):
pass
class RankFilter(Filter): class RankFilter(Filter):
""" """

View File

@ -141,7 +141,8 @@ class FreeTypeFont(object):
self.layout_engine = layout_engine self.layout_engine = layout_engine
if isPath(font): if isPath(font):
self.font = core.getfont(font, size, index, encoding, layout_engine=layout_engine) self.font = core.getfont(font, size, index, encoding,
layout_engine=layout_engine)
else: else:
self.font_bytes = font.read() self.font_bytes = font.read()
self.font = core.getfont( self.font = core.getfont(
@ -175,9 +176,11 @@ class FreeTypeFont(object):
return self.font.getsize(text)[1] return self.font.getsize(text)[1]
def getmask(self, text, mode="", direction=None, features=None): def getmask(self, text, mode="", direction=None, features=None):
return self.getmask2(text, mode, direction=direction, features=features)[0] return self.getmask2(text, mode, direction=direction,
features=features)[0]
def getmask2(self, text, mode="", fill=Image.core.fill, direction=None, features=None, *args, **kwargs): def getmask2(self, text, mode="", fill=Image.core.fill, direction=None,
features=None, *args, **kwargs):
size, offset = self.font.getsize(text, direction, features) size, offset = self.font.getsize(text, direction, features)
im = fill("L", size, 0) im = fill("L", size, 0)
self.font.render(text, im.id, mode == "1", direction, features) self.font.render(text, im.id, mode == "1", direction, features)
@ -194,12 +197,13 @@ class FreeTypeFont(object):
:return: A FreeTypeFont object. :return: A FreeTypeFont object.
""" """
return FreeTypeFont(font=self.path if font is None else font, return FreeTypeFont(
size=self.size if size is None else size, font=self.path if font is None else font,
index=self.index if index is None else index, size=self.size if size is None else size,
encoding=self.encoding if encoding is None else encoding, index=self.index if index is None else index,
layout_engine=self.layout_engine if layout_engine is None else layout_engine encoding=self.encoding if encoding is None else encoding,
) layout_engine=self.layout_engine if layout_engine is None else layout_engine
)
class TransposedFont(object): class TransposedFont(object):
@ -303,12 +307,16 @@ def truetype(font=None, size=10, index=0, encoding="",
for walkfilename in walkfilenames: for walkfilename in walkfilenames:
if ext and walkfilename == ttf_filename: if ext and walkfilename == ttf_filename:
fontpath = os.path.join(walkroot, walkfilename) fontpath = os.path.join(walkroot, walkfilename)
return FreeTypeFont(fontpath, size, index, encoding, layout_engine) return FreeTypeFont(fontpath, size, index,
elif not ext and os.path.splitext(walkfilename)[0] == ttf_filename: encoding, layout_engine)
elif (not ext and
os.path.splitext(walkfilename)[0] == ttf_filename):
fontpath = os.path.join(walkroot, walkfilename) fontpath = os.path.join(walkroot, walkfilename)
if os.path.splitext(fontpath)[1] == '.ttf': if os.path.splitext(fontpath)[1] == '.ttf':
return FreeTypeFont(fontpath, size, index, encoding, layout_engine) return FreeTypeFont(fontpath, size, index,
if not ext and first_font_with_a_different_extension is None: encoding, layout_engine)
if not ext \
and first_font_with_a_different_extension is None:
first_font_with_a_different_extension = fontpath first_font_with_a_different_extension = fontpath
if first_font_with_a_different_extension: if first_font_with_a_different_extension:
return FreeTypeFont(first_font_with_a_different_extension, size, return FreeTypeFont(first_font_with_a_different_extension, size,

View File

@ -42,7 +42,8 @@ def getmode(mode):
for m, (basemode, basetype, bands) in Image._MODEINFO.items(): for m, (basemode, basetype, bands) in Image._MODEINFO.items():
modes[m] = ModeDescriptor(m, bands, basemode, basetype) modes[m] = ModeDescriptor(m, bands, basemode, basetype)
# extra experimental modes # extra experimental modes
modes["RGBa"] = ModeDescriptor("RGBa", ("R", "G", "B", "a"), "RGB", "L") modes["RGBa"] = ModeDescriptor("RGBa",
("R", "G", "B", "a"), "RGB", "L")
modes["LA"] = ModeDescriptor("LA", ("L", "A"), "L", "L") modes["LA"] = ModeDescriptor("LA", ("L", "A"), "L", "L")
modes["La"] = ModeDescriptor("La", ("L", "a"), "L", "L") modes["La"] = ModeDescriptor("La", ("L", "a"), "L", "L")
modes["PA"] = ModeDescriptor("PA", ("P", "A"), "RGB", "L") modes["PA"] = ModeDescriptor("PA", ("P", "A"), "RGB", "L")

View File

@ -136,28 +136,87 @@ def autocontrast(image, cutoff=0, ignore=None):
return _lut(image, lut) return _lut(image, lut)
def colorize(image, black, white): def colorize(image, black, white, mid=None, blackpoint=0,
whitepoint=255, midpoint=127):
""" """
Colorize grayscale image. The **black** and **white** Colorize grayscale image.
arguments should be RGB tuples; this function calculates a color This function calculates a color wedge which maps all black pixels in
wedge mapping all black pixels in the source image to the first the source image to the first color and all white pixels to the
color, and all white pixels to the second color. second color. If **mid** is specified, it uses three-color mapping.
The **black** and **white** arguments should be RGB tuples or color names;
optionally you can use three-color mapping by also specifying **mid**.
Mapping positions for any of the colors can be specified
(e.g. **blackpoint**), where these parameters are the integer
value corresponding to where the corresponding color should be mapped.
These parameters must have logical order, such that
**blackpoint** <= **midpoint** <= **whitepoint** (if **mid** is specified).
:param image: The image to colorize. :param image: The image to colorize.
:param black: The color to use for black input pixels. :param black: The color to use for black input pixels.
:param white: The color to use for white input pixels. :param white: The color to use for white input pixels.
:param mid: The color to use for midtone input pixels.
:param blackpoint: an int value [0, 255] for the black mapping.
:param whitepoint: an int value [0, 255] for the white mapping.
:param midpoint: an int value [0, 255] for the midtone mapping.
:return: An image. :return: An image.
""" """
# Initial asserts
assert image.mode == "L" assert image.mode == "L"
if mid is None:
assert 0 <= blackpoint <= whitepoint <= 255
else:
assert 0 <= blackpoint <= midpoint <= whitepoint <= 255
# Define colors from arguments
black = _color(black, "RGB") black = _color(black, "RGB")
white = _color(white, "RGB") white = _color(white, "RGB")
if mid is not None:
mid = _color(mid, "RGB")
# Empty lists for the mapping
red = [] red = []
green = [] green = []
blue = [] blue = []
for i in range(256):
red.append(black[0]+i*(white[0]-black[0])//255) # Create the low-end values
green.append(black[1]+i*(white[1]-black[1])//255) for i in range(0, blackpoint):
blue.append(black[2]+i*(white[2]-black[2])//255) red.append(black[0])
green.append(black[1])
blue.append(black[2])
# Create the mapping (2-color)
if mid is None:
range_map = range(0, whitepoint - blackpoint)
for i in range_map:
red.append(black[0] + i * (white[0] - black[0]) // len(range_map))
green.append(black[1] + i * (white[1] - black[1]) // len(range_map))
blue.append(black[2] + i * (white[2] - black[2]) // len(range_map))
# Create the mapping (3-color)
else:
range_map1 = range(0, midpoint - blackpoint)
range_map2 = range(0, whitepoint - midpoint)
for i in range_map1:
red.append(black[0] + i * (mid[0] - black[0]) // len(range_map1))
green.append(black[1] + i * (mid[1] - black[1]) // len(range_map1))
blue.append(black[2] + i * (mid[2] - black[2]) // len(range_map1))
for i in range_map2:
red.append(mid[0] + i * (white[0] - mid[0]) // len(range_map2))
green.append(mid[1] + i * (white[1] - mid[1]) // len(range_map2))
blue.append(mid[2] + i * (white[2] - mid[2]) // len(range_map2))
# Create the high-end values
for i in range(0, 256 - whitepoint):
red.append(white[0])
green.append(white[1])
blue.append(white[2])
# Return converted image
image = image.convert("RGB") image = image.convert("RGB")
return _lut(image, red + green + blue) return _lut(image, red + green + blue)

View File

@ -59,7 +59,7 @@ class ImagePalette(object):
def getdata(self): def getdata(self):
""" """
Get palette contents in format suitable # for the low-level Get palette contents in format suitable for the low-level
``im.putpalette`` primitive. ``im.putpalette`` primitive.
.. warning:: This method is experimental. .. warning:: This method is experimental.

View File

@ -119,7 +119,8 @@ def align8to32(bytes, width, mode):
new_data = [] new_data = []
for i in range(len(bytes) // bytes_per_line): for i in range(len(bytes) // bytes_per_line):
new_data.append(bytes[i*bytes_per_line:(i+1)*bytes_per_line] + b'\x00' * extra_padding) new_data.append(bytes[i*bytes_per_line:(i+1)*bytes_per_line]
+ b'\x00' * extra_padding)
return b''.join(new_data) return b''.join(new_data)

View File

@ -110,11 +110,11 @@ class Stat(object):
v = [] v = []
for i in self.bands: for i in self.bands:
s = 0 s = 0
l = self.count[i]//2 half = self.count[i]//2
b = i * 256 b = i * 256
for j in range(256): for j in range(256):
s = s + self.h[b+j] s = s + self.h[b+j]
if s > l: if s > half:
break break
v.append(j) v.append(j)
return v return v

View File

@ -195,7 +195,8 @@ class PhotoImage(object):
# Pypy is using a ffi cdata element # Pypy is using a ffi cdata element
# (Pdb) self.tk.interp # (Pdb) self.tk.interp
# <cdata 'Tcl_Interp *' 0x3061b50> # <cdata 'Tcl_Interp *' 0x3061b50>
_imagingtk.tkinit(int(ffi.cast("uintptr_t", tk.interp)), 1) _imagingtk.tkinit(
int(ffi.cast("uintptr_t", tk.interp)), 1)
else: else:
_imagingtk.tkinit(tk.interpaddr(), 1) _imagingtk.tkinit(tk.interpaddr(), 1)
except AttributeError: except AttributeError:

View File

@ -270,7 +270,8 @@ def _save(im, fp, filename):
Image.register_open(Jpeg2KImageFile.format, Jpeg2KImageFile, _accept) Image.register_open(Jpeg2KImageFile.format, Jpeg2KImageFile, _accept)
Image.register_save(Jpeg2KImageFile.format, _save) Image.register_save(Jpeg2KImageFile.format, _save)
Image.register_extensions(Jpeg2KImageFile.format, [".jp2", ".j2k", ".jpc", ".jpf", ".jpx", ".j2c"]) Image.register_extensions(Jpeg2KImageFile.format,
[".jp2", ".j2k", ".jpc", ".jpf", ".jpx", ".j2c"])
Image.register_mime(Jpeg2KImageFile.format, 'image/jp2') Image.register_mime(Jpeg2KImageFile.format, 'image/jp2')
Image.register_mime(Jpeg2KImageFile.format, 'image/jpx') Image.register_mime(Jpeg2KImageFile.format, 'image/jpx')

View File

@ -793,12 +793,13 @@ def jpeg_factory(fp=None, filename=None):
return im return im
# -------------------------------------------------------------------q- # ---------------------------------------------------------------------
# Registry stuff # Registry stuff
Image.register_open(JpegImageFile.format, jpeg_factory, _accept) Image.register_open(JpegImageFile.format, jpeg_factory, _accept)
Image.register_save(JpegImageFile.format, _save) Image.register_save(JpegImageFile.format, _save)
Image.register_extensions(JpegImageFile.format, [".jfif", ".jpe", ".jpg", ".jpeg"]) Image.register_extensions(JpegImageFile.format,
[".jfif", ".jpe", ".jpg", ".jpeg"])
Image.register_mime(JpegImageFile.format, "image/jpeg") Image.register_mime(JpegImageFile.format, "image/jpeg")

View File

@ -85,7 +85,7 @@ class MpoImageFile(JpegImagePlugin.JpegImageFile):
return self.__frame return self.__frame
# -------------------------------------------------------------------q- # ---------------------------------------------------------------------
# Registry stuff # Registry stuff
# Note that since MPO shares a factory with JPEG, we do not need to do a # Note that since MPO shares a factory with JPEG, we do not need to do a

View File

@ -126,8 +126,9 @@ class MspDecoder(ImageFile.PyDecoder):
continue continue
row = self.fd.read(rowlen) row = self.fd.read(rowlen)
if len(row) != rowlen: if len(row) != rowlen:
raise IOError("Truncated MSP file, expected %d bytes on row %s", raise IOError(
(rowlen, x)) "Truncated MSP file, expected %d bytes on row %s",
(rowlen, x))
idx = 0 idx = 0
while idx < rowlen: while idx < rowlen:
runtype = i8(row[idx]) runtype = i8(row[idx])

View File

@ -98,7 +98,8 @@ def _save(im, fp, filename, save_all=False):
try: try:
im_numberOfPages = im.n_frames im_numberOfPages = im.n_frames
except AttributeError: except AttributeError:
# Image format does not have n_frames. It is a single frame image # Image format does not have n_frames.
# It is a single frame image
pass pass
numberOfPages += im_numberOfPages numberOfPages += im_numberOfPages
for i in range(im_numberOfPages): for i in range(im_numberOfPages):
@ -115,9 +116,9 @@ def _save(im, fp, filename, save_all=False):
for imSequence in ims: for imSequence in ims:
im_pages = ImageSequence.Iterator(imSequence) if save_all else [imSequence] im_pages = ImageSequence.Iterator(imSequence) if save_all else [imSequence]
for im in im_pages: for im in im_pages:
# FIXME: Should replace ASCIIHexDecode with RunLengthDecode (packbits) # FIXME: Should replace ASCIIHexDecode with RunLengthDecode
# or LZWDecode (tiff/lzw compression). Note that PDF 1.2 also supports # (packbits) or LZWDecode (tiff/lzw compression). Note that
# Flatedecode (zip compression). # PDF 1.2 also supports Flatedecode (zip compression).
bits = 8 bits = 8
params = None params = None
@ -135,7 +136,12 @@ def _save(im, fp, filename, save_all=False):
elif im.mode == "P": elif im.mode == "P":
filter = "ASCIIHexDecode" filter = "ASCIIHexDecode"
palette = im.im.getpalette("RGB") palette = im.im.getpalette("RGB")
colorspace = [PdfParser.PdfName("Indexed"), PdfParser.PdfName("DeviceRGB"), 255, PdfParser.PdfBinary(palette)] colorspace = [
PdfParser.PdfName("Indexed"),
PdfParser.PdfName("DeviceRGB"),
255,
PdfParser.PdfBinary(palette)
]
procset = "ImageI" # indexed color procset = "ImageI" # indexed color
elif im.mode == "RGB": elif im.mode == "RGB":
filter = "DCTDecode" filter = "DCTDecode"
@ -166,7 +172,8 @@ def _save(im, fp, filename, save_all=False):
elif filter == "FlateDecode": elif filter == "FlateDecode":
ImageFile._save(im, op, [("zip", (0, 0)+im.size, 0, im.mode)]) ImageFile._save(im, op, [("zip", (0, 0)+im.size, 0, im.mode)])
elif filter == "RunLengthDecode": elif filter == "RunLengthDecode":
ImageFile._save(im, op, [("packbits", (0, 0)+im.size, 0, im.mode)]) ImageFile._save(im, op,
[("packbits", (0, 0)+im.size, 0, im.mode)])
else: else:
raise ValueError("unsupported PDF filter (%s)" % filter) raise ValueError("unsupported PDF filter (%s)" % filter)
@ -175,26 +182,37 @@ def _save(im, fp, filename, save_all=False):
width, height = im.size width, height = im.size
existing_pdf.write_obj(image_refs[pageNumber], stream=op.getvalue(), existing_pdf.write_obj(image_refs[pageNumber],
Type=PdfParser.PdfName("XObject"), stream=op.getvalue(),
Subtype=PdfParser.PdfName("Image"), Type=PdfParser.PdfName("XObject"),
Width=width, # * 72.0 / resolution, Subtype=PdfParser.PdfName("Image"),
Height=height, # * 72.0 / resolution, Width=width, # * 72.0 / resolution,
Filter=PdfParser.PdfName(filter), Height=height, # * 72.0 / resolution,
BitsPerComponent=bits, Filter=PdfParser.PdfName(filter),
DecodeParams=params, BitsPerComponent=bits,
ColorSpace=colorspace) DecodeParams=params,
ColorSpace=colorspace)
# #
# page # page
existing_pdf.write_page(page_refs[pageNumber], existing_pdf.write_page(page_refs[pageNumber],
Resources=PdfParser.PdfDict( Resources=PdfParser.PdfDict(
ProcSet=[PdfParser.PdfName("PDF"), PdfParser.PdfName(procset)], ProcSet=[
XObject=PdfParser.PdfDict(image=image_refs[pageNumber])), PdfParser.PdfName("PDF"),
MediaBox=[0, 0, int(width * 72.0 / resolution), int(height * 72.0 / resolution)], PdfParser.PdfName(procset)
Contents=contents_refs[pageNumber] ],
) XObject=PdfParser.PdfDict(
image=image_refs[pageNumber]
)
),
MediaBox=[
0,
0,
int(width * 72.0 / resolution),
int(height * 72.0 / resolution)
],
Contents=contents_refs[pageNumber])
# #
# page contents # page contents
@ -204,7 +222,8 @@ def _save(im, fp, filename, save_all=False):
int(width * 72.0 / resolution), int(width * 72.0 / resolution),
int(height * 72.0 / resolution))) int(height * 72.0 / resolution)))
existing_pdf.write_obj(contents_refs[pageNumber], stream=page_contents) existing_pdf.write_obj(contents_refs[pageNumber],
stream=page_contents)
pageNumber += 1 pageNumber += 1

View File

@ -20,7 +20,8 @@ else: # Python 2.x
return s # pragma: no cover return s # pragma: no cover
# see 7.9.2.2 Text String Type on page 86 and D.3 PDFDocEncoding Character Set on page 656 # see 7.9.2.2 Text String Type on page 86 and D.3 PDFDocEncoding Character Set
# on page 656
def encode_text(s): def encode_text(s):
return codecs.BOM_UTF16_BE + s.encode("utf_16_be") return codecs.BOM_UTF16_BE + s.encode("utf_16_be")
@ -80,7 +81,8 @@ def decode_text(b):
class PdfFormatError(RuntimeError): class PdfFormatError(RuntimeError):
"""An error that probably indicates a syntactic or semantic error in the PDF file structure""" """An error that probably indicates a syntactic or semantic error in the
PDF file structure"""
pass pass
@ -89,7 +91,8 @@ def check_format_condition(condition, error_message):
raise PdfFormatError(error_message) raise PdfFormatError(error_message)
class IndirectReference(collections.namedtuple("IndirectReferenceTuple", ["object_id", "generation"])): class IndirectReference(collections.namedtuple("IndirectReferenceTuple",
["object_id", "generation"])):
def __str__(self): def __str__(self):
return "%s %s R" % self return "%s %s R" % self
@ -97,7 +100,9 @@ class IndirectReference(collections.namedtuple("IndirectReferenceTuple", ["objec
return self.__str__().encode("us-ascii") return self.__str__().encode("us-ascii")
def __eq__(self, other): def __eq__(self, other):
return other.__class__ is self.__class__ and other.object_id == self.object_id and other.generation == self.generation return other.__class__ is self.__class__ and \
other.object_id == self.object_id and \
other.generation == self.generation
def __ne__(self, other): def __ne__(self, other):
return not (self == other) return not (self == other)
@ -143,19 +148,26 @@ class XrefTable:
elif key in self.deleted_entries: elif key in self.deleted_entries:
generation = self.deleted_entries[key] generation = self.deleted_entries[key]
else: else:
raise IndexError("object ID " + str(key) + " cannot be deleted because it doesn't exist") raise IndexError("object ID " + str(key) +
" cannot be deleted because it doesn't exist")
def __contains__(self, key): def __contains__(self, key):
return key in self.existing_entries or key in self.new_entries return key in self.existing_entries or key in self.new_entries
def __len__(self): def __len__(self):
return len(set(self.existing_entries.keys()) | set(self.new_entries.keys()) | set(self.deleted_entries.keys())) return len(set(self.existing_entries.keys()) |
set(self.new_entries.keys()) |
set(self.deleted_entries.keys()))
def keys(self): def keys(self):
return (set(self.existing_entries.keys()) - set(self.deleted_entries.keys())) | set(self.new_entries.keys()) return (
set(self.existing_entries.keys()) -
set(self.deleted_entries.keys())
) | set(self.new_entries.keys())
def write(self, f): def write(self, f):
keys = sorted(set(self.new_entries.keys()) | set(self.deleted_entries.keys())) keys = sorted(set(self.new_entries.keys()) |
set(self.deleted_entries.keys()))
deleted_keys = sorted(set(self.deleted_entries.keys())) deleted_keys = sorted(set(self.deleted_entries.keys()))
startxref = f.tell() startxref = f.tell()
f.write(b"xref\n") f.write(b"xref\n")
@ -172,10 +184,12 @@ class XrefTable:
else: else:
contiguous_keys = keys contiguous_keys = keys
keys = None keys = None
f.write(make_bytes("%d %d\n" % (contiguous_keys[0], len(contiguous_keys)))) f.write(make_bytes("%d %d\n" %
(contiguous_keys[0], len(contiguous_keys))))
for object_id in contiguous_keys: for object_id in contiguous_keys:
if object_id in self.new_entries: if object_id in self.new_entries:
f.write(make_bytes("%010d %05d n \n" % self.new_entries[object_id])) f.write(make_bytes("%010d %05d n \n" %
self.new_entries[object_id]))
else: else:
this_deleted_object_id = deleted_keys.pop(0) this_deleted_object_id = deleted_keys.pop(0)
check_format_condition(object_id == this_deleted_object_id, check_format_condition(object_id == this_deleted_object_id,
@ -186,7 +200,9 @@ class XrefTable:
next_in_linked_list = deleted_keys[0] next_in_linked_list = deleted_keys[0]
except IndexError: except IndexError:
next_in_linked_list = 0 next_in_linked_list = 0
f.write(make_bytes("%010d %05d f \n" % (next_in_linked_list, self.deleted_entries[object_id]))) f.write(make_bytes("%010d %05d f \n" %
(next_in_linked_list,
self.deleted_entries[object_id])))
return startxref return startxref
@ -203,7 +219,8 @@ class PdfName:
return self.name.decode("us-ascii") return self.name.decode("us-ascii")
def __eq__(self, other): def __eq__(self, other):
return (isinstance(other, PdfName) and other.name == self.name) or other == self.name return (isinstance(other, PdfName) and other.name == self.name) or \
other == self.name
def __hash__(self): def __hash__(self):
return hash(self.name) return hash(self.name)
@ -313,7 +330,9 @@ class PdfStream:
expected_length = self.dictionary.Length expected_length = self.dictionary.Length
return zlib.decompress(self.buf, bufsize=int(expected_length)) return zlib.decompress(self.buf, bufsize=int(expected_length))
else: else:
raise NotImplementedError("stream filter %s unknown/unsupported" % repr(self.dictionary.Filter)) raise NotImplementedError(
"stream filter %s unknown/unsupported" %
repr(self.dictionary.Filter))
def pdf_repr(x): def pdf_repr(x):
@ -323,7 +342,8 @@ 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 isinstance(x, PdfArray) or isinstance(x, PdfBinary): elif (isinstance(x, PdfName) or isinstance(x, PdfDict) or
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")
@ -331,10 +351,15 @@ def pdf_repr(x):
return bytes(PdfDict(x)) return bytes(PdfDict(x))
elif isinstance(x, list): elif isinstance(x, list):
return bytes(PdfArray(x)) return bytes(PdfArray(x))
elif (py3 and isinstance(x, str)) or (not py3 and isinstance(x, unicode)): elif ((py3 and isinstance(x, str)) or
(not py3 and isinstance(x, unicode))):
return pdf_repr(encode_text(x)) return pdf_repr(encode_text(x))
elif isinstance(x, bytes): elif isinstance(x, bytes):
return b"(" + x.replace(b"\\", b"\\\\").replace(b"(", b"\\(").replace(b")", b"\\)") + b")" # XXX escape more chars? handle binary garbage # XXX escape more chars? handle binary garbage
x = x.replace(b"\\", b"\\\\")
x = x.replace(b"(", b"\\(")
x = x.replace(b")", b"\\)")
return b"(" + x + b")"
else: else:
return bytes(x) return bytes(x)
@ -344,10 +369,13 @@ class PdfParser:
Supports PDF up to 1.4 Supports PDF up to 1.4
""" """
def __init__(self, filename=None, f=None, buf=None, start_offset=0, mode="rb"): def __init__(self, filename=None, f=None,
# type: (PdfParser, str, file, Union[bytes, bytearray], int, str) -> None buf=None, start_offset=0, mode="rb"):
# type: (PdfParser, str, file, Union[bytes, bytearray], int, str)
# -> None
if buf and f: if buf and f:
raise RuntimeError("specify buf or f or filename, but not both buf and f") raise RuntimeError(
"specify buf or f or filename, but not both buf and f")
self.filename = filename self.filename = filename
self.buf = buf self.buf = buf
self.f = f self.f = f
@ -473,7 +501,8 @@ class PdfParser:
if self.info: if self.info:
trailer_dict[b"Info"] = self.info_ref trailer_dict[b"Info"] = self.info_ref
self.last_xref_section_offset = start_xref self.last_xref_section_offset = start_xref
self.f.write(b"trailer\n" + bytes(PdfDict(trailer_dict)) + make_bytes("\nstartxref\n%d\n%%%%EOF" % start_xref)) self.f.write(b"trailer\n" + bytes(PdfDict(trailer_dict)) +
make_bytes("\nstartxref\n%d\n%%%%EOF" % start_xref))
def write_page(self, ref, *objs, **dict_obj): def write_page(self, ref, *objs, **dict_obj):
if isinstance(ref, int): if isinstance(ref, int):
@ -535,13 +564,18 @@ class PdfParser:
else: else:
self.info = PdfDict(self.read_indirect(self.info_ref)) self.info = PdfDict(self.read_indirect(self.info_ref))
check_format_condition(b"Type" in self.root, "/Type missing in Root") check_format_condition(b"Type" in self.root, "/Type missing in Root")
check_format_condition(self.root[b"Type"] == b"Catalog", "/Type in Root is not /Catalog") check_format_condition(self.root[b"Type"] == b"Catalog",
"/Type in Root is not /Catalog")
check_format_condition(b"Pages" in self.root, "/Pages missing in Root") check_format_condition(b"Pages" in self.root, "/Pages missing in Root")
check_format_condition(isinstance(self.root[b"Pages"], IndirectReference), "/Pages in Root is not an indirect reference") check_format_condition(isinstance(self.root[b"Pages"],
IndirectReference),
"/Pages in Root is not an indirect reference")
self.pages_ref = self.root[b"Pages"] self.pages_ref = self.root[b"Pages"]
self.page_tree_root = self.read_indirect(self.pages_ref) self.page_tree_root = self.read_indirect(self.pages_ref)
self.pages = self.linearize_page_tree(self.page_tree_root) self.pages = self.linearize_page_tree(self.page_tree_root)
# save the original list of page references in case the user modifies, adds or deletes some pages and we need to rewrite the pages and their list # save the original list of page references
# in case the user modifies, adds or deletes some pages
# and we need to rewrite the pages and their list
self.orig_pages = self.pages[:] self.orig_pages = self.pages[:]
def next_object_id(self, offset=None): def next_object_id(self, offset=None):
@ -562,10 +596,14 @@ class PdfParser:
whitespace_mandatory = whitespace + b"+" whitespace_mandatory = whitespace + b"+"
newline_only = br"[\r\n]+" newline_only = br"[\r\n]+"
newline = whitespace_optional + newline_only + whitespace_optional newline = whitespace_optional + newline_only + whitespace_optional
re_trailer_end = re.compile(whitespace_mandatory + br"trailer" + whitespace_optional + br"\<\<(.*\>\>)" + newline re_trailer_end = re.compile(
+ br"startxref" + newline + br"([0-9]+)" + newline + br"%%EOF" + whitespace_optional + br"$", re.DOTALL) whitespace_mandatory + br"trailer" + whitespace_optional +
re_trailer_prev = re.compile(whitespace_optional + br"trailer" + whitespace_optional + br"\<\<(.*?\>\>)" + newline br"\<\<(.*\>\>)" + newline + br"startxref" + newline + br"([0-9]+)" +
+ br"startxref" + newline + br"([0-9]+)" + newline + br"%%EOF" + whitespace_optional, re.DOTALL) newline + br"%%EOF" + whitespace_optional + br"$", re.DOTALL)
re_trailer_prev = re.compile(
whitespace_optional + br"trailer" + whitespace_optional +
br"\<\<(.*?\>\>)" + newline + br"startxref" + newline + br"([0-9]+)" +
newline + br"%%EOF" + whitespace_optional, re.DOTALL)
def read_trailer(self): def read_trailer(self):
search_start_offset = len(self.buf) - 16384 search_start_offset = len(self.buf) - 16384
@ -589,19 +627,26 @@ class PdfParser:
self.read_prev_trailer(self.trailer_dict[b"Prev"]) self.read_prev_trailer(self.trailer_dict[b"Prev"])
def read_prev_trailer(self, xref_section_offset): def read_prev_trailer(self, xref_section_offset):
trailer_offset = self.read_xref_table(xref_section_offset=xref_section_offset) trailer_offset = self.read_xref_table(
m = self.re_trailer_prev.search(self.buf[trailer_offset:trailer_offset+16384]) xref_section_offset=xref_section_offset)
m = self.re_trailer_prev.search(
self.buf[trailer_offset:trailer_offset+16384])
check_format_condition(m, "previous trailer not found") check_format_condition(m, "previous trailer not found")
trailer_data = m.group(1) trailer_data = m.group(1)
check_format_condition(int(m.group(2)) == xref_section_offset, "xref section offset in previous trailer doesn't match what was expected") check_format_condition(int(m.group(2)) == xref_section_offset,
"xref section offset in previous trailer "
"doesn't match what was expected")
trailer_dict = self.interpret_trailer(trailer_data) trailer_dict = self.interpret_trailer(trailer_data)
if b"Prev" in trailer_dict: if b"Prev" in trailer_dict:
self.read_prev_trailer(trailer_dict[b"Prev"]) self.read_prev_trailer(trailer_dict[b"Prev"])
re_whitespace_optional = re.compile(whitespace_optional) re_whitespace_optional = re.compile(whitespace_optional)
re_name = re.compile(whitespace_optional + br"/([!-$&'*-.0-;=?-Z\\^-z|~]+)(?=" + delimiter_or_ws + br")") re_name = re.compile(
whitespace_optional + br"/([!-$&'*-.0-;=?-Z\\^-z|~]+)(?=" +
delimiter_or_ws + br")")
re_dict_start = re.compile(whitespace_optional + br"\<\<") re_dict_start = re.compile(whitespace_optional + br"\<\<")
re_dict_end = re.compile(whitespace_optional + br"\>\>" + whitespace_optional) re_dict_end = re.compile(
whitespace_optional + br"\>\>" + whitespace_optional)
@classmethod @classmethod
def interpret_trailer(cls, trailer_data): def interpret_trailer(cls, trailer_data):
@ -611,13 +656,21 @@ class PdfParser:
m = cls.re_name.match(trailer_data, offset) m = cls.re_name.match(trailer_data, offset)
if not m: if not m:
m = cls.re_dict_end.match(trailer_data, offset) m = cls.re_dict_end.match(trailer_data, offset)
check_format_condition(m and m.end() == len(trailer_data), "name not found in trailer, remaining data: " + repr(trailer_data[offset:])) check_format_condition(
m and m.end() == len(trailer_data),
"name not found in trailer, remaining data: " +
repr(trailer_data[offset:]))
break break
key = cls.interpret_name(m.group(1)) key = cls.interpret_name(m.group(1))
value, offset = cls.get_value(trailer_data, m.end()) value, offset = cls.get_value(trailer_data, m.end())
trailer[key] = value trailer[key] = value
check_format_condition(b"Size" in trailer and isinstance(trailer[b"Size"], int), "/Size not in trailer or not an integer") check_format_condition(
check_format_condition(b"Root" in trailer and isinstance(trailer[b"Root"], IndirectReference), "/Root not in trailer or not an indirect reference") b"Size" in trailer and isinstance(trailer[b"Size"], int),
"/Size not in trailer or not an integer")
check_format_condition(
b"Root" in trailer and
isinstance(trailer[b"Root"], IndirectReference),
"/Root not in trailer or not an indirect reference")
return trailer return trailer
re_hashes_in_name = re.compile(br"([^#]*)(#([0-9a-fA-F]{2}))?") re_hashes_in_name = re.compile(br"([^#]*)(#([0-9a-fA-F]{2}))?")
@ -627,7 +680,8 @@ class PdfParser:
name = b"" name = b""
for m in cls.re_hashes_in_name.finditer(raw): for m in cls.re_hashes_in_name.finditer(raw):
if m.group(3): if m.group(3):
name += m.group(1) + bytearray.fromhex(m.group(3).decode("us-ascii")) name += m.group(1) + \
bytearray.fromhex(m.group(3).decode("us-ascii"))
else: else:
name += m.group(1) name += m.group(1)
if as_text: if as_text:
@ -635,21 +689,37 @@ class PdfParser:
else: else:
return bytes(name) return bytes(name)
re_null = re.compile(whitespace_optional + br"null(?=" + delimiter_or_ws + br")") re_null = re.compile(
re_true = re.compile(whitespace_optional + br"true(?=" + delimiter_or_ws + br")") whitespace_optional + br"null(?=" + delimiter_or_ws + br")")
re_false = re.compile(whitespace_optional + br"false(?=" + delimiter_or_ws + br")") re_true = re.compile(
re_int = re.compile(whitespace_optional + br"([-+]?[0-9]+)(?=" + delimiter_or_ws + br")") whitespace_optional + br"true(?=" + delimiter_or_ws + br")")
re_real = re.compile(whitespace_optional + br"([-+]?([0-9]+\.[0-9]*|[0-9]*\.[0-9]+))(?=" + delimiter_or_ws + br")") re_false = re.compile(
whitespace_optional + br"false(?=" + delimiter_or_ws + br")")
re_int = re.compile(
whitespace_optional + br"([-+]?[0-9]+)(?=" + delimiter_or_ws + br")")
re_real = re.compile(
whitespace_optional + br"([-+]?([0-9]+\.[0-9]*|[0-9]*\.[0-9]+))(?=" +
delimiter_or_ws + br")")
re_array_start = re.compile(whitespace_optional + br"\[") re_array_start = re.compile(whitespace_optional + br"\[")
re_array_end = re.compile(whitespace_optional + br"]") re_array_end = re.compile(whitespace_optional + br"]")
re_string_hex = re.compile(whitespace_optional + br"\<(" + whitespace_or_hex + br"*)\>") re_string_hex = re.compile(
whitespace_optional + br"\<(" + whitespace_or_hex + br"*)\>")
re_string_lit = re.compile(whitespace_optional + br"\(") re_string_lit = re.compile(whitespace_optional + br"\(")
re_indirect_reference = re.compile(whitespace_optional + br"([-+]?[0-9]+)" + whitespace_mandatory + br"([-+]?[0-9]+)" + whitespace_mandatory + br"R(?=" + delimiter_or_ws + br")") re_indirect_reference = re.compile(
re_indirect_def_start = re.compile(whitespace_optional + br"([-+]?[0-9]+)" + whitespace_mandatory + br"([-+]?[0-9]+)" + whitespace_mandatory + br"obj(?=" + delimiter_or_ws + br")") whitespace_optional + br"([-+]?[0-9]+)" + whitespace_mandatory +
re_indirect_def_end = re.compile(whitespace_optional + br"endobj(?=" + delimiter_or_ws + br")") br"([-+]?[0-9]+)" + whitespace_mandatory + br"R(?=" + delimiter_or_ws +
re_comment = re.compile(br"(" + whitespace_optional + br"%[^\r\n]*" + newline + br")*") br")")
re_indirect_def_start = re.compile(
whitespace_optional + br"([-+]?[0-9]+)" + whitespace_mandatory +
br"([-+]?[0-9]+)" + whitespace_mandatory + br"obj(?=" +
delimiter_or_ws + br")")
re_indirect_def_end = re.compile(
whitespace_optional + br"endobj(?=" + delimiter_or_ws + br")")
re_comment = re.compile(
br"(" + whitespace_optional + br"%[^\r\n]*" + newline + br")*")
re_stream_start = re.compile(whitespace_optional + br"stream\r?\n") re_stream_start = re.compile(whitespace_optional + br"stream\r?\n")
re_stream_end = re.compile(whitespace_optional + br"endstream(?=" + delimiter_or_ws + br")") re_stream_end = re.compile(
whitespace_optional + br"endstream(?=" + delimiter_or_ws + br")")
@classmethod @classmethod
def get_value(cls, data, offset, expect_indirect=None, max_nesting=-1): def get_value(cls, data, offset, expect_indirect=None, max_nesting=-1):
@ -660,21 +730,34 @@ class PdfParser:
offset = m.end() offset = m.end()
m = cls.re_indirect_def_start.match(data, offset) m = cls.re_indirect_def_start.match(data, offset)
if m: if m:
check_format_condition(int(m.group(1)) > 0, "indirect object definition: object ID must be greater than 0") check_format_condition(
check_format_condition(int(m.group(2)) >= 0, "indirect object definition: generation must be non-negative") int(m.group(1)) > 0,
check_format_condition(expect_indirect is None or expect_indirect == IndirectReference(int(m.group(1)), int(m.group(2))), "indirect object definition: object ID must be greater than 0")
check_format_condition(
int(m.group(2)) >= 0,
"indirect object definition: generation must be non-negative")
check_format_condition(
expect_indirect is None or expect_indirect ==
IndirectReference(int(m.group(1)), int(m.group(2))),
"indirect object definition different than expected") "indirect object definition different than expected")
object, offset = cls.get_value(data, m.end(), max_nesting=max_nesting-1) object, offset = cls.get_value(
data, m.end(), max_nesting=max_nesting-1)
if offset is None: if offset is None:
return object, None return object, None
m = cls.re_indirect_def_end.match(data, offset) m = cls.re_indirect_def_end.match(data, offset)
check_format_condition(m, "indirect object definition end not found") check_format_condition(
m, "indirect object definition end not found")
return object, m.end() return object, m.end()
check_format_condition(not expect_indirect, "indirect object definition not found") check_format_condition(
not expect_indirect, "indirect object definition not found")
m = cls.re_indirect_reference.match(data, offset) m = cls.re_indirect_reference.match(data, offset)
if m: if m:
check_format_condition(int(m.group(1)) > 0, "indirect object reference: object ID must be greater than 0") check_format_condition(
check_format_condition(int(m.group(2)) >= 0, "indirect object reference: generation must be non-negative") int(m.group(1)) > 0,
"indirect object reference: object ID must be greater than 0")
check_format_condition(
int(m.group(2)) >= 0,
"indirect object reference: generation must be non-negative")
return IndirectReference(int(m.group(1)), int(m.group(2))), m.end() return IndirectReference(int(m.group(1)), int(m.group(2))), m.end()
m = cls.re_dict_start.match(data, offset) m = cls.re_dict_start.match(data, offset)
if m: if m:
@ -682,10 +765,12 @@ class PdfParser:
result = {} result = {}
m = cls.re_dict_end.match(data, offset) m = cls.re_dict_end.match(data, offset)
while not m: while not m:
key, offset = cls.get_value(data, offset, max_nesting=max_nesting-1) key, offset = cls.get_value(
data, offset, max_nesting=max_nesting-1)
if offset is None: if offset is None:
return result, None return result, None
value, offset = cls.get_value(data, offset, max_nesting=max_nesting-1) value, offset = cls.get_value(
data, offset, max_nesting=max_nesting-1)
result[key] = value result[key] = value
if offset is None: if offset is None:
return result, None return result, None
@ -696,7 +781,9 @@ class PdfParser:
try: try:
stream_len = int(result[b"Length"]) stream_len = int(result[b"Length"])
except (TypeError, KeyError, ValueError): except (TypeError, KeyError, ValueError):
raise PdfFormatError("bad or missing Length in stream dict (%r)" % result.get(b"Length", None)) raise PdfFormatError(
"bad or missing Length in stream dict (%r)" %
result.get(b"Length", None))
stream_data = data[m.end():m.end() + stream_len] stream_data = data[m.end():m.end() + stream_len]
m = cls.re_stream_end.match(data, m.end() + stream_len) m = cls.re_stream_end.match(data, m.end() + stream_len)
check_format_condition(m, "stream end not found") check_format_condition(m, "stream end not found")
@ -711,7 +798,8 @@ class PdfParser:
result = [] result = []
m = cls.re_array_end.match(data, offset) m = cls.re_array_end.match(data, offset)
while not m: while not m:
value, offset = cls.get_value(data, offset, max_nesting=max_nesting-1) value, offset = cls.get_value(
data, offset, max_nesting=max_nesting-1)
result.append(value) result.append(value)
if offset is None: if offset is None:
return result, None return result, None
@ -734,18 +822,25 @@ class PdfParser:
return int(m.group(1)), m.end() return int(m.group(1)), m.end()
m = cls.re_real.match(data, offset) m = cls.re_real.match(data, offset)
if m: if m:
return float(m.group(1)), m.end() # XXX Decimal instead of float??? # XXX Decimal instead of float???
return float(m.group(1)), m.end()
m = cls.re_string_hex.match(data, offset) m = cls.re_string_hex.match(data, offset)
if m: if m:
hex_string = bytearray([b for b in m.group(1) if b in b"0123456789abcdefABCDEF"]) # filter out whitespace # filter out whitespace
hex_string = bytearray([
b for b in m.group(1)
if b in b"0123456789abcdefABCDEF"
])
if len(hex_string) % 2 == 1: if len(hex_string) % 2 == 1:
hex_string.append(ord(b"0")) # append a 0 if the length is not even - yes, at the end # append a 0 if the length is not even - yes, at the end
hex_string.append(ord(b"0"))
return bytearray.fromhex(hex_string.decode("us-ascii")), m.end() return bytearray.fromhex(hex_string.decode("us-ascii")), m.end()
m = cls.re_string_lit.match(data, offset) m = cls.re_string_lit.match(data, offset)
if m: if m:
return cls.get_literal_string(data, m.end()) return cls.get_literal_string(data, m.end())
#return None, offset # fallback (only for debugging) # return None, offset # fallback (only for debugging)
raise PdfFormatError("unrecognized object: " + repr(data[offset:offset+32])) raise PdfFormatError(
"unrecognized object: " + repr(data[offset:offset+32]))
re_lit_str_token = re.compile(br"(\\[nrtbf()\\])|(\\[0-9]{1,3})|(\\(\r\n|\r|\n))|(\r\n|\r|\n)|(\()|(\))") re_lit_str_token = re.compile(br"(\\[nrtbf()\\])|(\\[0-9]{1,3})|(\\(\r\n|\r|\n))|(\r\n|\r|\n)|(\()|(\))")
escaped_chars = { escaped_chars = {
@ -792,19 +887,24 @@ class PdfParser:
offset = m.end() offset = m.end()
raise PdfFormatError("unfinished literal string") raise PdfFormatError("unfinished literal string")
re_xref_section_start = re.compile(whitespace_optional + br"xref" + newline) re_xref_section_start = re.compile(
re_xref_subsection_start = re.compile(whitespace_optional + br"([0-9]+)" + whitespace_mandatory + br"([0-9]+)" + whitespace_optional + newline_only) whitespace_optional + br"xref" + newline)
re_xref_subsection_start = re.compile(
whitespace_optional + br"([0-9]+)" + whitespace_mandatory +
br"([0-9]+)" + whitespace_optional + newline_only)
re_xref_entry = re.compile(br"([0-9]{10}) ([0-9]{5}) ([fn])( \r| \n|\r\n)") re_xref_entry = re.compile(br"([0-9]{10}) ([0-9]{5}) ([fn])( \r| \n|\r\n)")
def read_xref_table(self, xref_section_offset): def read_xref_table(self, xref_section_offset):
subsection_found = False subsection_found = False
m = self.re_xref_section_start.match(self.buf, xref_section_offset + self.start_offset) m = self.re_xref_section_start.match(
self.buf, xref_section_offset + self.start_offset)
check_format_condition(m, "xref section start not found") check_format_condition(m, "xref section start not found")
offset = m.end() offset = m.end()
while True: while True:
m = self.re_xref_subsection_start.match(self.buf, offset) m = self.re_xref_subsection_start.match(self.buf, offset)
if not m: if not m:
check_format_condition(subsection_found, "xref subsection start not found") check_format_condition(
subsection_found, "xref subsection start not found")
break break
subsection_found = True subsection_found = True
offset = m.end() offset = m.end()
@ -818,22 +918,31 @@ class PdfParser:
generation = int(m.group(2)) generation = int(m.group(2))
if not is_free: if not is_free:
new_entry = (int(m.group(1)), generation) new_entry = (int(m.group(1)), generation)
check_format_condition(i not in self.xref_table or self.xref_table[i] == new_entry, "xref entry duplicated (and not identical)") check_format_condition(
i not in self.xref_table or
self.xref_table[i] == new_entry,
"xref entry duplicated (and not identical)")
self.xref_table[i] = new_entry self.xref_table[i] = new_entry
return offset return offset
def read_indirect(self, ref, max_nesting=-1): def read_indirect(self, ref, max_nesting=-1):
offset, generation = self.xref_table[ref[0]] offset, generation = self.xref_table[ref[0]]
check_format_condition(generation == ref[1], "expected to find generation %s for object ID %s in xref table, instead found generation %s at offset %s" \ check_format_condition(
generation == ref[1],
"expected to find generation %s for object ID %s in xref table, "
"instead found generation %s at offset %s"
% (ref[1], ref[0], generation, offset)) % (ref[1], ref[0], generation, offset))
value = self.get_value(self.buf, offset + self.start_offset, expect_indirect=IndirectReference(*ref), max_nesting=max_nesting)[0] value = self.get_value(self.buf, offset + self.start_offset,
expect_indirect=IndirectReference(*ref),
max_nesting=max_nesting)[0]
self.cached_objects[ref] = value self.cached_objects[ref] = value
return value return value
def linearize_page_tree(self, node=None): def linearize_page_tree(self, node=None):
if node is None: if node is None:
node = self.page_tree_root node = self.page_tree_root
check_format_condition(node[b"Type"] == b"Pages", "/Type of page tree node is not /Pages") check_format_condition(
node[b"Type"] == b"Pages", "/Type of page tree node is not /Pages")
pages = [] pages = []
for kid in node[b"Kids"]: for kid in node[b"Kids"]:
kid_object = self.read_indirect(kid) kid_object = self.read_indirect(kid)

View File

@ -142,7 +142,8 @@ class ChunkStream(object):
def crc(self, cid, data): def crc(self, cid, data):
"Read and verify checksum" "Read and verify checksum"
# Skip CRC checks for ancillary chunks if allowed to load truncated images # Skip CRC checks for ancillary chunks if allowed to load truncated
# images
# 5th byte of first char is 1 [specs, section 5.4] # 5th byte of first char is 1 [specs, section 5.4]
if ImageFile.LOAD_TRUNCATED_IMAGES and (i8(cid[0]) >> 5 & 1): if ImageFile.LOAD_TRUNCATED_IMAGES and (i8(cid[0]) >> 5 & 1):
self.crc_skip(cid, data) self.crc_skip(cid, data)
@ -301,8 +302,8 @@ class PngStream(ChunkStream):
def check_text_memory(self, chunklen): def check_text_memory(self, chunklen):
self.text_memory += chunklen self.text_memory += chunklen
if self.text_memory > MAX_TEXT_MEMORY: if self.text_memory > MAX_TEXT_MEMORY:
raise ValueError("Too much memory used in text chunks: %s>MAX_TEXT_MEMORY" % raise ValueError("Too much memory used in text chunks: "
self.text_memory) "%s>MAX_TEXT_MEMORY" % self.text_memory)
def chunk_iCCP(self, pos, length): def chunk_iCCP(self, pos, length):

View File

@ -83,7 +83,8 @@ class PpmImageFile(ImageFile.ImageFile):
if s not in b_whitespace: if s not in b_whitespace:
break break
if s == b"": if s == b"":
raise ValueError("File does not extend beyond magic number") raise ValueError(
"File does not extend beyond magic number")
if s != b"#": if s != b"#":
break break
s = self.fp.readline() s = self.fp.readline()

View File

@ -222,6 +222,7 @@ Image.register_save(SgiImageFile.format, _save)
Image.register_mime(SgiImageFile.format, "image/sgi") Image.register_mime(SgiImageFile.format, "image/sgi")
Image.register_mime(SgiImageFile.format, "image/rgb") Image.register_mime(SgiImageFile.format, "image/rgb")
Image.register_extensions(SgiImageFile.format, [".bw", ".rgb", ".rgba", ".sgi"]) Image.register_extensions(SgiImageFile.format,
[".bw", ".rgb", ".rgba", ".sgi"])
# End of file # End of file

Some files were not shown because too many files have changed in this diff Show More