Merge branch 'master' into imagecms-deprecations

This commit is contained in:
Hugo 2019-03-27 12:03:54 +02:00 committed by GitHub
commit 90886b1888
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
88 changed files with 1466 additions and 748 deletions

View File

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

View File

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

View File

@ -34,6 +34,6 @@ The best reproductions are self-contained scripts with minimal dependencies. If
## Security vulnerabilities ## Security vulnerabilities
To report sensitive vulnerability information, email security@python-pillow.org. To report sensitive vulnerability information, please use the [Tidelift security contact](https://tidelift.com/security). Tidelift will coordinate the fix and disclosure.
If your organisation/employer is a distributor of Pillow and would like advance notification of security-related bugs, please let us know your preferred contact method. If your organisation/employer is a distributor of Pillow and would like advance notification of security-related bugs, please let us know your preferred contact method.

View File

@ -5,6 +5,78 @@ Changelog (Pillow)
6.0.0 (unreleased) 6.0.0 (unreleased)
------------------ ------------------
- Python 2.7 support will be removed in Pillow 7.0.0 #3682
[hugovk]
- Added transparency for all PNG greyscale modes #3744
[radarhere]
- Fix deprecation warnings in Python 3.8 #3749
[radarhere]
- Fixed GIF bug when rewinding to a non-zero frame #3716
[radarhere]
- Only close original fp in __del__ and __exit__ if original fp is exclusive #3683
[radarhere]
- Fix BytesWarning in Tests/test_numpy.py #3725
[jdufresne]
- Add missing MIME types and extensions #3520
[pirate486743186]
- Add I;16 PNG save #3566
[radarhere]
- Add support for BMP RGBA bitfield compression #3705
[radarhere]
- Added ability to set language for text rendering #3693
[iwsfutcmd]
- Only close exclusive fp on Image __exit__ #3698
[radarhere]
- Changed EPS subprocess stdout from devnull to None #3635
[radarhere]
- Add reading old-JPEG compressed TIFFs #3489
[kkopachev]
- Add EXIF support for PNG #3674
[radarhere]
- Add option to set dither param on quantize #3699
[glasnt]
- Add reading of DDS uncompressed RGB data #3673
[radarhere]
- Correct length of Tiff BYTE tags #3672
[radarhere]
- Add DIB saving and loading through Image open #3691
[radarhere]
- Removed deprecated VERSION #3624
[hugovk]
- Fix 'BytesWarning: Comparison between bytes and string' in PdfDict #3580
[jdufresne]
- Do not resize in Image.thumbnail if already the destination size #3632
[radarhere]
- Replace .seek() magic numbers with io.SEEK_* constants #3572
[jdufresne]
- Make ContainerIO.isatty() return a bool, not int #3568
[jdufresne]
- Add support to all transpose operations for I;16 modes #3563, #3741
[radarhere]
- Deprecate support for PyQt4 and PySide #3655 - Deprecate support for PyQt4 and PySide #3655
[hugovk, radarhere] [hugovk, radarhere]

View File

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

View File

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

View File

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

BIN
Tests/images/1_trns.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 612 B

BIN
Tests/images/exif.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 175 KiB

BIN
Tests/images/hopper.pnm Normal file

Binary file not shown.

BIN
Tests/images/i_trns.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 MiB

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 32 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 777 B

BIN
Tests/images/uncompressed_rgb.dds Executable file

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 143 KiB

Binary file not shown.

Binary file not shown.

View File

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

View File

@ -16,6 +16,7 @@ class TestFileBmp(PillowTestCase):
self.assertEqual(im.mode, reloaded.mode) self.assertEqual(im.mode, reloaded.mode)
self.assertEqual(im.size, reloaded.size) self.assertEqual(im.size, reloaded.size)
self.assertEqual(reloaded.format, "BMP") self.assertEqual(reloaded.format, "BMP")
self.assertEqual(reloaded.get_format_mimetype(), "image/bmp")
def test_sanity(self): def test_sanity(self):
self.roundtrip(hopper()) self.roundtrip(hopper())
@ -72,6 +73,32 @@ class TestFileBmp(PillowTestCase):
def test_load_dib(self): def test_load_dib(self):
# test for #1293, Imagegrab returning Unsupported Bitfields Format # test for #1293, Imagegrab returning Unsupported Bitfields Format
im = BmpImagePlugin.DibImageFile('Tests/images/clipboard.dib') im = Image.open('Tests/images/clipboard.dib')
self.assertEqual(im.format, "DIB")
self.assertEqual(im.get_format_mimetype(), "image/bmp")
target = Image.open('Tests/images/clipboard_target.png') target = Image.open('Tests/images/clipboard_target.png')
self.assert_image_equal(im, target) self.assert_image_equal(im, target)
def test_save_dib(self):
outfile = self.tempfile("temp.dib")
im = Image.open('Tests/images/clipboard.dib')
im.save(outfile)
reloaded = Image.open(outfile)
self.assertEqual(reloaded.format, "DIB")
self.assertEqual(reloaded.get_format_mimetype(), "image/bmp")
self.assert_image_equal(im, reloaded)
def test_rgba_bitfields(self):
# This test image has been manually hexedited
# to change the bitfield compression in the header from XBGR to RGBA
im = Image.open("Tests/images/rgb32bf-rgba.bmp")
# So before the comparing the image, swap the channels
b, g, r = im.split()[1:]
im = Image.merge("RGB", (r, g, b))
target = Image.open("Tests/images/bmp/q/rgb32bf-xbgr.bmp")
self.assert_image_equal(im, target)

View File

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

View File

@ -7,6 +7,7 @@ TEST_FILE_DXT1 = "Tests/images/dxt1-rgb-4bbp-noalpha_MipMaps-1.dds"
TEST_FILE_DXT3 = "Tests/images/dxt3-argb-8bbp-explicitalpha_MipMaps-1.dds" TEST_FILE_DXT3 = "Tests/images/dxt3-argb-8bbp-explicitalpha_MipMaps-1.dds"
TEST_FILE_DXT5 = "Tests/images/dxt5-argb-8bbp-interpolatedalpha_MipMaps-1.dds" TEST_FILE_DXT5 = "Tests/images/dxt5-argb-8bbp-interpolatedalpha_MipMaps-1.dds"
TEST_FILE_DX10_BC7 = "Tests/images/bc7-argb-8bpp_MipMaps-1.dds" TEST_FILE_DX10_BC7 = "Tests/images/bc7-argb-8bpp_MipMaps-1.dds"
TEST_FILE_UNCOMPRESSED_RGB = "Tests/images/uncompressed_rgb.dds"
class TestFileDds(PillowTestCase): class TestFileDds(PillowTestCase):
@ -67,6 +68,24 @@ class TestFileDds(PillowTestCase):
self.assert_image_equal(target, im) self.assert_image_equal(target, im)
def test_unimplemented_dxgi_format(self):
self.assertRaises(NotImplementedError, Image.open,
"Tests/images/unimplemented_dxgi_format.dds")
def test_uncompressed_rgb(self):
"""Check uncompressed RGB images can be opened"""
target = Image.open(TEST_FILE_UNCOMPRESSED_RGB.replace('.dds', '.png'))
im = Image.open(TEST_FILE_UNCOMPRESSED_RGB)
im.load()
self.assertEqual(im.format, "DDS")
self.assertEqual(im.mode, "RGBA")
self.assertEqual(im.size, (800, 600))
self.assert_image_equal(target, im)
def test__validate_true(self): def test__validate_true(self):
"""Check valid prefix""" """Check valid prefix"""
# Arrange # Arrange
@ -110,3 +129,7 @@ class TestFileDds(PillowTestCase):
im.load() im.load()
self.assertRaises(IOError, short_file) self.assertRaises(IOError, short_file)
def test_unimplemented_pixel_format(self):
self.assertRaises(NotImplementedError, Image.open,
"Tests/images/unimplemented_pixel_format.dds")

View File

@ -230,6 +230,15 @@ class TestFileGif(PillowTestCase):
self.assertEqual(im.info, info) self.assertEqual(im.info, info)
def test_seek_rewind(self):
im = Image.open("Tests/images/iss634.gif")
im.seek(2)
im.seek(1)
expected = Image.open("Tests/images/iss634.gif")
expected.seek(1)
self.assert_image_equal(im, expected)
def test_n_frames(self): def test_n_frames(self):
for path, n_frames in [ for path, n_frames in [
[TEST_GIF, 1], [TEST_GIF, 1],

View File

@ -14,6 +14,7 @@ class TestFileIco(PillowTestCase):
self.assertEqual(im.mode, "RGBA") self.assertEqual(im.mode, "RGBA")
self.assertEqual(im.size, (16, 16)) self.assertEqual(im.size, (16, 16))
self.assertEqual(im.format, "ICO") self.assertEqual(im.format, "ICO")
self.assertEqual(im.get_format_mimetype(), "image/x-icon")
def test_invalid_file(self): def test_invalid_file(self):
with open("Tests/images/flower.jpg", "rb") as fp: with open("Tests/images/flower.jpg", "rb") as fp:

View File

@ -234,11 +234,11 @@ class TestFileLibTiff(LibTiffTestCase):
def test_custom_metadata(self): def test_custom_metadata(self):
custom = { custom = {
37000: 4, 37000: [4, TiffTags.SHORT],
37001: 4.2, 37001: [4.2, TiffTags.RATIONAL],
37002: 'custom tag value', 37002: ['custom tag value', TiffTags.ASCII],
37003: u'custom tag value', 37003: [u'custom tag value', TiffTags.ASCII],
37004: b'custom tag value' 37004: [b'custom tag value', TiffTags.BYTE]
} }
libtiff_version = TiffImagePlugin._libtiff_version() libtiff_version = TiffImagePlugin._libtiff_version()
@ -251,17 +251,33 @@ class TestFileLibTiff(LibTiffTestCase):
for libtiff in libtiffs: for libtiff in libtiffs:
TiffImagePlugin.WRITE_LIBTIFF = libtiff TiffImagePlugin.WRITE_LIBTIFF = libtiff
im = hopper() def check_tags(tiffinfo):
im = hopper()
out = self.tempfile("temp.tif") out = self.tempfile("temp.tif")
im.save(out, tiffinfo=custom) im.save(out, tiffinfo=tiffinfo)
TiffImagePlugin.WRITE_LIBTIFF = False
reloaded = Image.open(out) reloaded = Image.open(out)
for tag, value in custom.items(): for tag, value in tiffinfo.items():
if libtiff and isinstance(value, bytes): reloaded_value = reloaded.tag_v2[tag]
value = value.decode() if isinstance(reloaded_value, TiffImagePlugin.IFDRational):
self.assertEqual(reloaded.tag_v2[tag], value) reloaded_value = float(reloaded_value)
if libtiff and isinstance(value, bytes):
value = value.decode()
self.assertEqual(reloaded_value, value)
# Test with types
ifd = TiffImagePlugin.ImageFileDirectory_v2()
for tag, tagdata in custom.items():
ifd[tag] = tagdata[0]
ifd.tagtype[tag] = tagdata[1]
check_tags(ifd)
# Test without types
check_tags({tag: tagdata[0] for tag, tagdata in custom.items()})
TiffImagePlugin.WRITE_LIBTIFF = False
def test_int_dpi(self): def test_int_dpi(self):
# issue #1765 # issue #1765
@ -700,3 +716,10 @@ class TestFileLibTiff(LibTiffTestCase):
im = Image.open(infile) im = Image.open(infile)
self.assert_image_similar_tofile(im, "Tests/images/flower.jpg", 0.5) self.assert_image_similar_tofile(im, "Tests/images/flower.jpg", 0.5)
def test_old_style_jpeg(self):
infile = "Tests/images/old-style-jpeg-compression.tif"
im = Image.open(infile)
self.assert_image_equal_tofile(im,
"Tests/images/old-style-jpeg-compression.png")

View File

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

View File

@ -13,6 +13,7 @@ class TestFilePcx(PillowTestCase):
self.assertEqual(im2.mode, im.mode) self.assertEqual(im2.mode, im.mode)
self.assertEqual(im2.size, im.size) self.assertEqual(im2.size, im.size)
self.assertEqual(im2.format, "PCX") self.assertEqual(im2.format, "PCX")
self.assertEqual(im2.get_format_mimetype(), "image/x-pcx")
self.assert_image_equal(im2, im) self.assert_image_equal(im2, im)
def test_sanity(self): def test_sanity(self):

View File

@ -88,20 +88,13 @@ class TestFilePng(PillowTestCase):
self.assertEqual(im.format, "PNG") self.assertEqual(im.format, "PNG")
self.assertEqual(im.get_format_mimetype(), 'image/png') self.assertEqual(im.get_format_mimetype(), 'image/png')
hopper("1").save(test_file) for mode in ["1", "L", "P", "RGB", "I", "I;16"]:
Image.open(test_file) im = hopper(mode)
im.save(test_file)
hopper("L").save(test_file) reloaded = Image.open(test_file)
Image.open(test_file) if mode == "I;16":
reloaded = reloaded.convert(mode)
hopper("P").save(test_file) self.assert_image_equal(reloaded, im)
Image.open(test_file)
hopper("RGB").save(test_file)
Image.open(test_file)
hopper("I").save(test_file)
Image.open(test_file)
def test_invalid_file(self): def test_invalid_file(self):
invalid_file = "Tests/images/flower.jpg" invalid_file = "Tests/images/flower.jpg"
@ -298,30 +291,32 @@ class TestFilePng(PillowTestCase):
self.assert_image(im, "RGBA", (10, 10)) self.assert_image(im, "RGBA", (10, 10))
self.assertEqual(im.getcolors(), [(100, (0, 0, 0, 0))]) self.assertEqual(im.getcolors(), [(100, (0, 0, 0, 0))])
def test_save_l_transparency(self): def test_save_greyscale_transparency(self):
# There are 559 transparent pixels in l_trns.png. for mode, num_transparent in {
num_transparent = 559 "1": 1994,
"L": 559,
"I": 559,
}.items():
in_file = "Tests/images/"+mode.lower()+"_trns.png"
im = Image.open(in_file)
self.assertEqual(im.mode, mode)
self.assertEqual(im.info["transparency"], 255)
in_file = "Tests/images/l_trns.png" im_rgba = im.convert('RGBA')
im = Image.open(in_file) self.assertEqual(
self.assertEqual(im.mode, "L") im_rgba.getchannel("A").getcolors()[0][0], num_transparent)
self.assertEqual(im.info["transparency"], 255)
im_rgba = im.convert('RGBA') test_file = self.tempfile("temp.png")
self.assertEqual( im.save(test_file)
im_rgba.getchannel("A").getcolors()[0][0], num_transparent)
test_file = self.tempfile("temp.png") test_im = Image.open(test_file)
im.save(test_file) self.assertEqual(test_im.mode, mode)
self.assertEqual(test_im.info["transparency"], 255)
self.assert_image_equal(im, test_im)
test_im = Image.open(test_file) test_im_rgba = test_im.convert('RGBA')
self.assertEqual(test_im.mode, "L") self.assertEqual(
self.assertEqual(test_im.info["transparency"], 255) test_im_rgba.getchannel('A').getcolors()[0][0], num_transparent)
self.assert_image_equal(im, test_im)
test_im_rgba = test_im.convert('RGBA')
self.assertEqual(
test_im_rgba.getchannel('A').getcolors()[0][0], num_transparent)
def test_save_rgb_single_transparency(self): def test_save_rgb_single_transparency(self):
in_file = "Tests/images/caption_6_33_22.png" in_file = "Tests/images/caption_6_33_22.png"
@ -590,6 +585,40 @@ class TestFilePng(PillowTestCase):
im = Image.open("Tests/images/hopper_idat_after_image_end.png") im = Image.open("Tests/images/hopper_idat_after_image_end.png")
self.assertEqual(im.text, {'TXT': 'VALUE', 'ZIP': 'VALUE'}) self.assertEqual(im.text, {'TXT': 'VALUE', 'ZIP': 'VALUE'})
def test_exif(self):
im = Image.open("Tests/images/exif.png")
exif = im._getexif()
self.assertEqual(exif[274], 1)
def test_exif_save(self):
im = Image.open("Tests/images/exif.png")
test_file = self.tempfile("temp.png")
im.save(test_file)
reloaded = Image.open(test_file)
exif = reloaded._getexif()
self.assertEqual(exif[274], 1)
def test_exif_from_jpg(self):
im = Image.open("Tests/images/pil_sample_rgb.jpg")
test_file = self.tempfile("temp.png")
im.save(test_file)
reloaded = Image.open(test_file)
exif = reloaded._getexif()
self.assertEqual(exif[305], "Adobe Photoshop CS Macintosh")
def test_exif_argument(self):
im = Image.open(TEST_PNG_FILE)
test_file = self.tempfile("temp.png")
im.save(test_file, exif=b"exifstring")
reloaded = Image.open(test_file)
self.assertEqual(reloaded.info["exif"], b"Exif\x00\x00exifstring")
@unittest.skipUnless(HAVE_WEBP and _webp.HAVE_WEBPANIM, @unittest.skipUnless(HAVE_WEBP and _webp.HAVE_WEBPANIM,
"WebP support not installed with animation") "WebP support not installed with animation")
def test_apng(self): def test_apng(self):

View File

@ -1,4 +1,4 @@
from .helper import PillowTestCase from .helper import PillowTestCase, hopper
from PIL import Image from PIL import Image
@ -14,12 +14,14 @@ class TestFilePpm(PillowTestCase):
self.assertEqual(im.mode, "RGB") self.assertEqual(im.mode, "RGB")
self.assertEqual(im.size, (128, 128)) self.assertEqual(im.size, (128, 128))
self.assertEqual(im.format, "PPM") self.assertEqual(im.format, "PPM")
self.assertEqual(im.get_format_mimetype(), "image/x-portable-pixmap")
def test_16bit_pgm(self): def test_16bit_pgm(self):
im = Image.open('Tests/images/16_bit_binary.pgm') im = Image.open('Tests/images/16_bit_binary.pgm')
im.load() im.load()
self.assertEqual(im.mode, 'I') self.assertEqual(im.mode, 'I')
self.assertEqual(im.size, (20, 100)) self.assertEqual(im.size, (20, 100))
self.assertEqual(im.get_format_mimetype(), "image/x-portable-graymap")
tgt = Image.open('Tests/images/16_bit_binary_pgm.png') tgt = Image.open('Tests/images/16_bit_binary_pgm.png')
self.assert_image_equal(im, tgt) self.assert_image_equal(im, tgt)
@ -34,6 +36,16 @@ class TestFilePpm(PillowTestCase):
reloaded = Image.open(f) reloaded = Image.open(f)
self.assert_image_equal(im, reloaded) self.assert_image_equal(im, reloaded)
def test_pnm(self):
im = Image.open('Tests/images/hopper.pnm')
self.assert_image_similar(im, hopper(), 0.0001)
f = self.tempfile('temp.pnm')
im.save(f)
reloaded = Image.open(f)
self.assert_image_equal(im, reloaded)
def test_truncated_file(self): def test_truncated_file(self):
path = self.tempfile('temp.pgm') path = self.tempfile('temp.pgm')
with open(path, 'w') as f: with open(path, 'w') as f:
@ -49,3 +61,16 @@ class TestFilePpm(PillowTestCase):
with self.assertRaises(IOError): with self.assertRaises(IOError):
Image.open('Tests/images/negative_size.ppm') Image.open('Tests/images/negative_size.ppm')
def test_mimetypes(self):
path = self.tempfile('temp.pgm')
with open(path, 'w') as f:
f.write("P4\n128 128\n255")
im = Image.open(path)
self.assertEqual(im.get_format_mimetype(), "image/x-portable-bitmap")
with open(path, 'w') as f:
f.write("PyCMYK\n128 128\n255")
im = Image.open(path)
self.assertEqual(im.get_format_mimetype(), "image/x-portable-anymap")

View File

@ -37,6 +37,8 @@ class TestFileTga(PillowTestCase):
path_no_ext, origin, "rle" if rle else "raw") path_no_ext, origin, "rle" if rle else "raw")
original_im = Image.open(tga_path) original_im = Image.open(tga_path)
self.assertEqual(original_im.format, "TGA")
self.assertEqual(original_im.get_format_mimetype(), "image/x-tga")
if rle: if rle:
self.assertEqual( self.assertEqual(
original_im.info["compression"], "tga_rle") original_im.info["compression"], "tga_rle")

View File

@ -532,6 +532,18 @@ class TestImage(PillowTestCase):
with Image.open(test_file) as im: with Image.open(test_file) as im:
self.assert_warning(None, im.save, temp_file) self.assert_warning(None, im.save, temp_file)
def test_load_on_nonexclusive_multiframe(self):
with open("Tests/images/frozenpond.mpo", "rb") as fp:
def act(fp):
im = Image.open(fp)
im.load()
act(fp)
with Image.open(fp) as im:
im.load()
self.assertFalse(fp.closed)
class MockEncoder(object): class MockEncoder(object):
pass pass

View File

@ -28,3 +28,10 @@ class TestImageLoad(PillowTestCase):
os.fstat(fn) os.fstat(fn)
self.assertRaises(OSError, os.fstat, fn) self.assertRaises(OSError, os.fstat, fn)
def test_contextmanager_non_exclusive_fp(self):
with open("Tests/images/hopper.gif", "rb") as fp:
with Image.open(fp):
pass
self.assertFalse(fp.closed)

View File

@ -46,3 +46,19 @@ class TestImageQuantize(PillowTestCase):
converted = image.quantize() converted = image.quantize()
self.assert_image(converted, 'P', converted.size) self.assert_image(converted, 'P', converted.size)
self.assert_image_similar(converted.convert('RGB'), image, 1) self.assert_image_similar(converted.convert('RGB'), image, 1)
def test_quantize_no_dither(self):
image = hopper()
palette = Image.open('Tests/images/caption_6_33_22.png').convert('P')
converted = image.quantize(dither=0, palette=palette)
self.assert_image(converted, 'P', converted.size)
def test_quantize_dither_diff(self):
image = hopper()
palette = Image.open('Tests/images/caption_6_33_22.png').convert('P')
dither = image.quantize(dither=1, palette=palette)
nodither = image.quantize(dither=0, palette=palette)
self.assertNotEqual(dither.tobytes(), nodither.tobytes())

View File

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

View File

@ -7,10 +7,9 @@ from PIL.Image import (FLIP_LEFT_RIGHT, FLIP_TOP_BOTTOM, ROTATE_90, ROTATE_180,
class TestImageTranspose(PillowTestCase): class TestImageTranspose(PillowTestCase):
hopper = { hopper = {mode: helper.hopper(mode).crop((0, 0, 121, 127)).copy() for mode in [
'L': helper.hopper('L').crop((0, 0, 121, 127)).copy(), 'L', 'RGB', 'I;16', 'I;16L', 'I;16B'
'RGB': helper.hopper('RGB').crop((0, 0, 121, 127)).copy(), ]}
}
def test_flip_left_right(self): def test_flip_left_right(self):
def transpose(mode): def transpose(mode):
@ -25,7 +24,7 @@ class TestImageTranspose(PillowTestCase):
self.assertEqual(im.getpixel((1, y-2)), out.getpixel((x-2, y-2))) self.assertEqual(im.getpixel((1, y-2)), out.getpixel((x-2, y-2)))
self.assertEqual(im.getpixel((x-2, y-2)), out.getpixel((1, y-2))) self.assertEqual(im.getpixel((x-2, y-2)), out.getpixel((1, y-2)))
for mode in ("L", "RGB"): for mode in self.hopper:
transpose(mode) transpose(mode)
def test_flip_top_bottom(self): def test_flip_top_bottom(self):
@ -41,7 +40,7 @@ class TestImageTranspose(PillowTestCase):
self.assertEqual(im.getpixel((1, y-2)), out.getpixel((1, 1))) self.assertEqual(im.getpixel((1, y-2)), out.getpixel((1, 1)))
self.assertEqual(im.getpixel((x-2, y-2)), out.getpixel((x-2, 1))) self.assertEqual(im.getpixel((x-2, y-2)), out.getpixel((x-2, 1)))
for mode in ("L", "RGB"): for mode in self.hopper:
transpose(mode) transpose(mode)
def test_rotate_90(self): def test_rotate_90(self):
@ -57,7 +56,7 @@ class TestImageTranspose(PillowTestCase):
self.assertEqual(im.getpixel((1, y-2)), out.getpixel((y-2, x-2))) self.assertEqual(im.getpixel((1, y-2)), out.getpixel((y-2, x-2)))
self.assertEqual(im.getpixel((x-2, y-2)), out.getpixel((y-2, 1))) self.assertEqual(im.getpixel((x-2, y-2)), out.getpixel((y-2, 1)))
for mode in ("L", "RGB"): for mode in self.hopper:
transpose(mode) transpose(mode)
def test_rotate_180(self): def test_rotate_180(self):
@ -73,7 +72,7 @@ class TestImageTranspose(PillowTestCase):
self.assertEqual(im.getpixel((1, y-2)), out.getpixel((x-2, 1))) self.assertEqual(im.getpixel((1, y-2)), out.getpixel((x-2, 1)))
self.assertEqual(im.getpixel((x-2, y-2)), out.getpixel((1, 1))) self.assertEqual(im.getpixel((x-2, y-2)), out.getpixel((1, 1)))
for mode in ("L", "RGB"): for mode in self.hopper:
transpose(mode) transpose(mode)
def test_rotate_270(self): def test_rotate_270(self):
@ -89,7 +88,7 @@ class TestImageTranspose(PillowTestCase):
self.assertEqual(im.getpixel((1, y-2)), out.getpixel((1, 1))) self.assertEqual(im.getpixel((1, y-2)), out.getpixel((1, 1)))
self.assertEqual(im.getpixel((x-2, y-2)), out.getpixel((1, x-2))) self.assertEqual(im.getpixel((x-2, y-2)), out.getpixel((1, x-2)))
for mode in ("L", "RGB"): for mode in self.hopper:
transpose(mode) transpose(mode)
def test_transpose(self): def test_transpose(self):
@ -105,7 +104,7 @@ class TestImageTranspose(PillowTestCase):
self.assertEqual(im.getpixel((1, y-2)), out.getpixel((y-2, 1))) self.assertEqual(im.getpixel((1, y-2)), out.getpixel((y-2, 1)))
self.assertEqual(im.getpixel((x-2, y-2)), out.getpixel((y-2, x-2))) self.assertEqual(im.getpixel((x-2, y-2)), out.getpixel((y-2, x-2)))
for mode in ("L", "RGB"): for mode in self.hopper:
transpose(mode) transpose(mode)
def test_tranverse(self): def test_tranverse(self):
@ -121,28 +120,29 @@ class TestImageTranspose(PillowTestCase):
self.assertEqual(im.getpixel((1, y-2)), out.getpixel((1, x-2))) self.assertEqual(im.getpixel((1, y-2)), out.getpixel((1, x-2)))
self.assertEqual(im.getpixel((x-2, y-2)), out.getpixel((1, 1))) self.assertEqual(im.getpixel((x-2, y-2)), out.getpixel((1, 1)))
for mode in ("L", "RGB"): for mode in self.hopper:
transpose(mode) transpose(mode)
def test_roundtrip(self): def test_roundtrip(self):
im = self.hopper['L'] for mode in self.hopper:
im = self.hopper[mode]
def transpose(first, second): def transpose(first, second):
return im.transpose(first).transpose(second) return im.transpose(first).transpose(second)
self.assert_image_equal( self.assert_image_equal(
im, transpose(FLIP_LEFT_RIGHT, FLIP_LEFT_RIGHT)) im, transpose(FLIP_LEFT_RIGHT, FLIP_LEFT_RIGHT))
self.assert_image_equal( self.assert_image_equal(
im, transpose(FLIP_TOP_BOTTOM, FLIP_TOP_BOTTOM)) im, transpose(FLIP_TOP_BOTTOM, FLIP_TOP_BOTTOM))
self.assert_image_equal(im, transpose(ROTATE_90, ROTATE_270)) self.assert_image_equal(im, transpose(ROTATE_90, ROTATE_270))
self.assert_image_equal(im, transpose(ROTATE_180, ROTATE_180)) self.assert_image_equal(im, transpose(ROTATE_180, ROTATE_180))
self.assert_image_equal( self.assert_image_equal(
im.transpose(TRANSPOSE), transpose(ROTATE_90, FLIP_TOP_BOTTOM)) im.transpose(TRANSPOSE), transpose(ROTATE_90, FLIP_TOP_BOTTOM))
self.assert_image_equal( self.assert_image_equal(
im.transpose(TRANSPOSE), transpose(ROTATE_270, FLIP_LEFT_RIGHT)) im.transpose(TRANSPOSE), transpose(ROTATE_270, FLIP_LEFT_RIGHT))
self.assert_image_equal( self.assert_image_equal(
im.transpose(TRANSVERSE), transpose(ROTATE_90, FLIP_LEFT_RIGHT)) im.transpose(TRANSVERSE), transpose(ROTATE_90, FLIP_LEFT_RIGHT))
self.assert_image_equal( self.assert_image_equal(
im.transpose(TRANSVERSE), transpose(ROTATE_270, FLIP_TOP_BOTTOM)) im.transpose(TRANSVERSE), transpose(ROTATE_270, FLIP_TOP_BOTTOM))
self.assert_image_equal( self.assert_image_equal(
im.transpose(TRANSVERSE), transpose(ROTATE_180, TRANSPOSE)) im.transpose(TRANSVERSE), transpose(ROTATE_180, TRANSPOSE))

View File

@ -6,6 +6,8 @@ from io import BytesIO
import os import os
import sys import sys
import copy import copy
import re
import distutils.version
FONT_PATH = "Tests/fonts/FreeMono.ttf" FONT_PATH = "Tests/fonts/FreeMono.ttf"
FONT_SIZE = 20 FONT_SIZE = 20
@ -49,29 +51,40 @@ class TestImageFont(PillowTestCase):
# Freetype has different metrics depending on the version. # Freetype has different metrics depending on the version.
# (and, other things, but first things first) # (and, other things, but first things first)
METRICS = { METRICS = {
('2', '3'): {'multiline': 30, ('>=2.3', '<2.4'): {
'textsize': 12, 'multiline': 30,
'getters': (13, 16)}, 'textsize': 12,
('2', '7'): {'multiline': 6.2, 'getters': (13, 16)},
'textsize': 2.5, ('>=2.7',): {
'getters': (12, 16)}, 'multiline': 6.2,
('2', '8'): {'multiline': 6.2, 'textsize': 2.5,
'textsize': 2.5, 'getters': (12, 16)},
'getters': (12, 16)}, 'Default': {
('2', '9'): {'multiline': 6.2, 'multiline': 0.5,
'textsize': 2.5, 'textsize': 0.5,
'getters': (12, 16)}, 'getters': (12, 16)},
'Default': {'multiline': 0.5,
'textsize': 0.5,
'getters': (12, 16)},
} }
def setUp(self): def setUp(self):
freetype_version = tuple( freetype = distutils.version.StrictVersion(ImageFont.core.freetype2_version)
ImageFont.core.freetype2_version.split('.')
)[:2] self.metrics = self.METRICS['Default']
self.metrics = self.METRICS.get(freetype_version, for conditions, metrics in self.METRICS.items():
self.METRICS['Default']) if not isinstance(conditions, tuple):
continue
for condition in conditions:
version = re.sub('[<=>]', '', condition)
if (condition.startswith('>=') and freetype >= version) or \
(condition.startswith('<') and freetype < version):
# Condition was met
continue
# Condition failed
break
else:
# All conditions were met
self.metrics = metrics
def get_font(self): def get_font(self):
return ImageFont.truetype(FONT_PATH, FONT_SIZE, return ImageFont.truetype(FONT_PATH, FONT_SIZE,
@ -525,6 +538,15 @@ class TestImageFont(PillowTestCase):
self.assertEqual(t.getsize_multiline('ABC\nA'), (36, 36)) self.assertEqual(t.getsize_multiline('ABC\nA'), (36, 36))
self.assertEqual(t.getsize_multiline('ABC\nAaaa'), (48, 36)) self.assertEqual(t.getsize_multiline('ABC\nAaaa'), (48, 36))
def test_complex_font_settings(self):
# Arrange
t = self.get_font()
# Act / Assert
if t.layout_engine == ImageFont.LAYOUT_BASIC:
self.assertRaises(KeyError, t.getmask, 'абвг', direction='rtl')
self.assertRaises(KeyError, t.getmask, 'абвг', features=['-kern'])
self.assertRaises(KeyError, t.getmask, 'абвг', language='sr')
@unittest.skipUnless(HAS_RAQM, "Raqm not Available") @unittest.skipUnless(HAS_RAQM, "Raqm not Available")
class TestImageFont_RaqmLayout(TestImageFont): class TestImageFont_RaqmLayout(TestImageFont):

View File

@ -130,3 +130,16 @@ class TestImagecomplextext(PillowTestCase):
target_img = Image.open(target) target_img = Image.open(target)
self.assert_image_similar(im, target_img, .5) self.assert_image_similar(im, target_img, .5)
def test_language(self):
ttf = ImageFont.truetype(FONT_PATH, FONT_SIZE)
im = Image.new(mode='RGB', size=(300, 100))
draw = ImageDraw.Draw(im)
draw.text((0, 0), 'абвг', font=ttf, fill=500,
language='sr')
target = 'Tests/images/test_language.png'
target_img = Image.open(target)
self.assert_image_similar(im, target_img, .5)

View File

@ -115,7 +115,7 @@ class TestNumpy(PillowTestCase):
arr_back = numpy.array(img) arr_back = numpy.array(img)
# numpy 1.8 and earlier return this as a boolean. (trusty/precise) # numpy 1.8 and earlier return this as a boolean. (trusty/precise)
if arr_back.dtype == numpy.bool: if arr_back.dtype == numpy.bool:
arr_bool = numpy.array([[1, 0, 0, 1, 0], [0, 1, 0, 0, 0]], 'bool') arr_bool = numpy.array([[1, 0, 0, 1, 0], [0, 1, 0, 0, 0]], numpy.bool)
numpy.testing.assert_array_equal(arr_bool, arr_back) numpy.testing.assert_array_equal(arr_bool, arr_back)
else: else:
numpy.testing.assert_array_equal(arr, arr_back) numpy.testing.assert_array_equal(arr, arr_back)
@ -143,21 +143,21 @@ class TestNumpy(PillowTestCase):
np_img = numpy.array(img) np_img = numpy.array(img)
self._test_img_equals_nparray(img, np_img) self._test_img_equals_nparray(img, np_img)
self.assertEqual(np_img.dtype, numpy.dtype(dtype)) self.assertEqual(np_img.dtype, dtype)
modes = [("L", 'uint8'), modes = [("L", numpy.uint8),
("I", 'int32'), ("I", numpy.int32),
("F", 'float32'), ("F", numpy.float32),
("LA", 'uint8'), ("LA", numpy.uint8),
("RGB", 'uint8'), ("RGB", numpy.uint8),
("RGBA", 'uint8'), ("RGBA", numpy.uint8),
("RGBX", 'uint8'), ("RGBX", numpy.uint8),
("CMYK", 'uint8'), ("CMYK", numpy.uint8),
("YCbCr", 'uint8'), ("YCbCr", numpy.uint8),
("I;16", '<u2'), ("I;16", '<u2'),
("I;16B", '>u2'), ("I;16B", '>u2'),
("I;16L", '<u2'), ("I;16L", '<u2'),
("HSV", 'uint8'), ("HSV", numpy.uint8),
] ]
for mode in modes: for mode in modes:
@ -167,7 +167,7 @@ class TestNumpy(PillowTestCase):
# see https://github.com/python-pillow/Pillow/issues/439 # see https://github.com/python-pillow/Pillow/issues/439
data = list(range(256))*3 data = list(range(256))*3
lut = numpy.array(data, dtype='uint8') lut = numpy.array(data, dtype=numpy.uint8)
im = hopper() im = hopper()

67
azure-pipelines.yml Normal file
View File

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

View File

@ -12,6 +12,16 @@ Deprecated features
Below are features which are considered deprecated. Where appropriate, Below are features which are considered deprecated. Where appropriate,
a ``DeprecationWarning`` is issued. a ``DeprecationWarning`` is issued.
Python 2.7
~~~~~~~~~~
.. deprecated:: 6.0.0
Python 2.7 reaches end-of-life on 2020-01-01.
Pillow 7.0.0 will be released on 2020-01-01 and will drop support for Python 2.7, making
Pillow 6.x the last series to support Python 2.
PyQt4 and PySide PyQt4 and PySide
~~~~~~~~~~~~~~~~ ~~~~~~~~~~~~~~~~
@ -61,13 +71,12 @@ a ``DeprecationWarning``:
Setting the size of a TIFF image directly is deprecated, and will Setting the size of a TIFF image directly is deprecated, and will
be removed in a future version. Use the resize method instead. be removed in a future version. Use the resize method instead.
PILLOW_VERSION and VERSION constants PILLOW_VERSION constant
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ ~~~~~~~~~~~~~~~~~~~~~~~
.. deprecated:: 5.2.0 .. deprecated:: 5.2.0
Two version constants ``VERSION`` (the old PIL version, always 1.1.7) and ``PILLOW_VERSION`` has been deprecated and will be removed in the next
``PILLOW_VERSION`` have been deprecated and will be removed in the next
major release. Use ``__version__`` instead. major release. Use ``__version__`` instead.
ImageCms.CmsProfile attributes ImageCms.CmsProfile attributes
@ -96,6 +105,14 @@ Removed features
Deprecated features are only removed in major releases after an appropriate Deprecated features are only removed in major releases after an appropriate
period of deprecation has passed. period of deprecation has passed.
VERSION constant
~~~~~~~~~~~~~~~~
*Removed in version 6.0.0.*
``VERSION`` (the old PIL version, always 1.1.7) has been removed. Use
``__version__`` instead.
Undocumented ImageOps functions Undocumented ImageOps functions
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

View File

@ -21,7 +21,7 @@ Fully supported formats
BMP BMP
^^^ ^^^
PIL reads and writes Windows and OS/2 BMP files containing ``1``, ``L``, ``P``, Pillow reads and writes Windows and OS/2 BMP files containing ``1``, ``L``, ``P``,
or ``RGB`` data. 16-colour images are read as ``P`` images. Run-length encoding or ``RGB`` data. 16-colour images are read as ``P`` images. Run-length encoding
is not supported. is not supported.
@ -31,13 +31,21 @@ The :py:meth:`~PIL.Image.Image.open` method sets the following
**compression** **compression**
Set to ``bmp_rle`` if the file is run-length encoded. Set to ``bmp_rle`` if the file is run-length encoded.
DIB
^^^
Pillow reads and writes DIB files. DIB files are similar to BMP files, so see
above for more information.
.. versionadded:: 6.0.0
EPS EPS
^^^ ^^^
PIL identifies EPS files containing image data, and can read files that contain Pillow identifies EPS files containing image data, and can read files that
embedded raster images (ImageData descriptors). If Ghostscript is available, contain embedded raster images (ImageData descriptors). If Ghostscript is
other EPS files can be read as well. The EPS driver can also write EPS available, other EPS files can be read as well. The EPS driver can also write
images. The EPS driver can read EPS images in ``L``, ``LAB``, ``RGB`` and EPS images. The EPS driver can read EPS images in ``L``, ``LAB``, ``RGB`` and
``CMYK`` mode, but Ghostscript may convert the images to ``RGB`` mode rather ``CMYK`` mode, but Ghostscript may convert the images to ``RGB`` mode rather
than leaving them in the original color space. The EPS driver can write images than leaving them in the original color space. The EPS driver can write images
in ``L``, ``RGB`` and ``CMYK`` modes. in ``L``, ``RGB`` and ``CMYK`` modes.
@ -59,8 +67,8 @@ method with the following parameter to affect how Ghostscript renders the EPS
GIF GIF
^^^ ^^^
PIL reads GIF87a and GIF89a versions of the GIF file format. The library writes Pillow reads GIF87a and GIF89a versions of the GIF file format. The library
run-length encoded files in GIF87a by default, unless GIF89a features writes run-length encoded files in GIF87a by default, unless GIF89a features
are used or GIF89a is already in use. are used or GIF89a is already in use.
Note that GIF files are always read as grayscale (``L``) Note that GIF files are always read as grayscale (``L``)
@ -97,8 +105,8 @@ Reading sequences
~~~~~~~~~~~~~~~~~ ~~~~~~~~~~~~~~~~~
The GIF loader supports the :py:meth:`~file.seek` and :py:meth:`~file.tell` The GIF loader supports the :py:meth:`~file.seek` and :py:meth:`~file.tell`
methods. You can seek to the next frame (``im.seek(im.tell() + 1)``), or rewind methods. You can combine these methods to seek to the next frame
the file by seeking to the first frame. Random access is not supported. (``im.seek(im.tell() + 1)``).
``im.seek()`` raises an ``EOFError`` if you try to seek after the last frame. ``im.seek()`` raises an ``EOFError`` if you try to seek after the last frame.
@ -192,7 +200,7 @@ attributes before loading the file::
ICNS ICNS
^^^^ ^^^^
PIL reads and (macOS only) writes macOS ``.icns`` files. By default, the Pillow reads and (macOS only) writes macOS ``.icns`` files. By default, the
largest available icon is read, though you can override this by setting the largest available icon is read, though you can override this by setting the
:py:attr:`~PIL.Image.Image.size` property before calling :py:attr:`~PIL.Image.Image.size` property before calling
:py:meth:`~PIL.Image.Image.load`. The :py:meth:`~PIL.Image.Image.open` method :py:meth:`~PIL.Image.Image.load`. The :py:meth:`~PIL.Image.Image.open` method
@ -237,12 +245,12 @@ IM is a format used by LabEye and other applications based on the IFUNC image
processing library. The library reads and writes most uncompressed interchange processing library. The library reads and writes most uncompressed interchange
versions of this format. versions of this format.
IM is the only format that can store all internal PIL formats. IM is the only format that can store all internal Pillow formats.
JPEG JPEG
^^^^ ^^^^
PIL reads JPEG, JFIF, and Adobe JPEG files containing ``L``, ``RGB``, or Pillow reads JPEG, JFIF, and Adobe JPEG files containing ``L``, ``RGB``, or
``CMYK`` data. It writes standard and progressive JFIF files. ``CMYK`` data. It writes standard and progressive JFIF files.
Using the :py:meth:`~PIL.Image.Image.draft` method, you can speed things up by Using the :py:meth:`~PIL.Image.Image.draft` method, you can speed things up by
@ -354,15 +362,15 @@ JPEG 2000
.. versionadded:: 2.4.0 .. versionadded:: 2.4.0
PIL reads and writes JPEG 2000 files containing ``L``, ``LA``, ``RGB`` or Pillow reads and writes JPEG 2000 files containing ``L``, ``LA``, ``RGB`` or
``RGBA`` data. It can also read files containing ``YCbCr`` data, which it ``RGBA`` data. It can also read files containing ``YCbCr`` data, which it
converts on read into ``RGB`` or ``RGBA`` depending on whether or not there is converts on read into ``RGB`` or ``RGBA`` depending on whether or not there is
an alpha channel. PIL supports JPEG 2000 raw codestreams (``.j2k`` files), as an alpha channel. Pillow supports JPEG 2000 raw codestreams (``.j2k`` files),
well as boxed JPEG 2000 files (``.j2p`` or ``.jpx`` files). PIL does *not* as well as boxed JPEG 2000 files (``.j2p`` or ``.jpx`` files). Pillow does
support files whose components have different sampling frequencies. *not* support files whose components have different sampling frequencies.
When loading, if you set the ``mode`` on the image prior to the When loading, if you set the ``mode`` on the image prior to the
:py:meth:`~PIL.Image.Image.load` method being invoked, you can ask PIL to :py:meth:`~PIL.Image.Image.load` method being invoked, you can ask Pillow to
convert the image to either ``RGB`` or ``RGBA`` rather than choosing for convert the image to either ``RGB`` or ``RGBA`` rather than choosing for
itself. It is also possible to set ``reduce`` to the number of resolutions to itself. It is also possible to set ``reduce`` to the number of resolutions to
discard (each one reduces the size of the resulting image by a factor of 2), discard (each one reduces the size of the resulting image by a factor of 2),
@ -433,26 +441,31 @@ The :py:meth:`~PIL.Image.Image.save` method supports the following options:
Library. Library.
Windows users can install the OpenJPEG binaries available on the Windows users can install the OpenJPEG binaries available on the
OpenJPEG website, but must add them to their PATH in order to use PIL (if OpenJPEG website, but must add them to their PATH in order to use Pillow (if
you fail to do this, you will get errors about not being able to load the you fail to do this, you will get errors about not being able to load the
``_imaging`` DLL). ``_imaging`` DLL).
MSP MSP
^^^ ^^^
PIL identifies and reads MSP files from Windows 1 and 2. The library writes Pillow identifies and reads MSP files from Windows 1 and 2. The library writes
uncompressed (Windows 1) versions of this format. uncompressed (Windows 1) versions of this format.
PCX PCX
^^^ ^^^
PIL reads and writes PCX files containing ``1``, ``L``, ``P``, or ``RGB`` data. Pillow reads and writes PCX files containing ``1``, ``L``, ``P``, or ``RGB`` data.
PNG PNG
^^^ ^^^
PIL identifies, reads, and writes PNG files containing ``1``, ``L``, ``P``, Pillow identifies, reads, and writes PNG files containing ``1``, ``L``, ``LA``,
``RGB``, or ``RGBA`` data. Interlaced files are supported as of v1.1.7. ``I``, ``P``, ``RGB`` or ``RGBA`` data. Interlaced files are supported as of
v1.1.7.
As of Pillow 6.0, EXIF data can be read from PNG images. However, unlike other
image formats, EXIF data is not guaranteed to have been read until
:py:meth:`~PIL.Image.Image.load` has been called.
The :py:meth:`~PIL.Image.Image.open` method sets the following The :py:meth:`~PIL.Image.Image.open` method sets the following
:py:attr:`~PIL.Image.Image.info` properties, when appropriate: :py:attr:`~PIL.Image.Image.info` properties, when appropriate:
@ -477,12 +490,12 @@ The :py:meth:`~PIL.Image.Image.open` method sets the following
For ``P`` images: Either the palette index for full transparent pixels, For ``P`` images: Either the palette index for full transparent pixels,
or a byte string with alpha values for each palette entry. or a byte string with alpha values for each palette entry.
For ``L`` and ``RGB`` images, the color that represents full transparent For ``1``, ``L``, ``I`` and ``RGB`` images, the color that represents
pixels in this image. full transparent pixels in this image.
This key is omitted if the image is not a transparent palette image. This key is omitted if the image is not a transparent palette image.
``Open`` also sets ``Image.text`` to a dictionary of the values of the ``open`` also sets ``Image.text`` to a dictionary of the values of the
``tEXt``, ``zTXt``, and ``iTXt`` chunks of the PNG image. Individual ``tEXt``, ``zTXt``, and ``iTXt`` chunks of the PNG image. Individual
compressed chunks are limited to a decompressed size of compressed chunks are limited to a decompressed size of
``PngImagePlugin.MAX_TEXT_CHUNK``, by default 1MB, to prevent ``PngImagePlugin.MAX_TEXT_CHUNK``, by default 1MB, to prevent
@ -498,8 +511,8 @@ The :py:meth:`~PIL.Image.Image.save` method supports the following options:
encoder settings. encoder settings.
**transparency** **transparency**
For ``P``, ``L``, and ``RGB`` images, this option controls what For ``P``, ``1``, ``L``, ``I``, and ``RGB`` images, this option controls
color image to mark as transparent. what color from the image to mark as transparent.
For ``P`` images, this can be a either the palette index, For ``P`` images, this can be a either the palette index,
or a byte string with alpha values for each palette entry. or a byte string with alpha values for each palette entry.
@ -519,6 +532,11 @@ The :py:meth:`~PIL.Image.Image.save` method supports the following options:
**icc_profile** **icc_profile**
The ICC Profile to include in the saved file. The ICC Profile to include in the saved file.
**exif**
The exif data to include in the saved file.
.. versionadded:: 6.0.0
**bits (experimental)** **bits (experimental)**
For ``P`` images, this option controls how many bits to store. If omitted, For ``P`` images, this option controls how many bits to store. If omitted,
the PNG writer uses 8 bits (256 colors). the PNG writer uses 8 bits (256 colors).
@ -535,8 +553,8 @@ The :py:meth:`~PIL.Image.Image.save` method supports the following options:
PPM PPM
^^^ ^^^
PIL reads and writes PBM, PGM and PPM files containing ``1``, ``L`` or ``RGB`` Pillow reads and writes PBM, PGM, PPM and PNM files containing ``1``, ``L`` or
data. ``RGB`` data.
SGI SGI
^^^ ^^^
@ -547,10 +565,10 @@ Pillow reads and writes uncompressed ``L``, ``RGB``, and ``RGBA`` files.
SPIDER SPIDER
^^^^^^ ^^^^^^
PIL reads and writes SPIDER image files of 32-bit floating point data Pillow reads and writes SPIDER image files of 32-bit floating point data
("F;32F"). ("F;32F").
PIL also reads SPIDER stack files containing sequences of SPIDER images. The Pillow also reads SPIDER stack files containing sequences of SPIDER images. The
:py:meth:`~file.seek` and :py:meth:`~file.tell` methods are supported, and :py:meth:`~file.seek` and :py:meth:`~file.tell` methods are supported, and
random access is allowed. random access is allowed.
@ -587,8 +605,8 @@ For more information about the SPIDER image processing package, see the
TGA TGA
^^^ ^^^
PIL reads and writes TGA images containing ``L``, ``LA``, ``P``, Pillow reads and writes TGA images containing ``L``, ``LA``, ``P``,
``RGB``, and ``RGBA`` data. PIL can read and write both uncompressed and ``RGB``, and ``RGBA`` data. Pillow can read and write both uncompressed and
run-length encoded TGAs. run-length encoded TGAs.
TIFF TIFF
@ -596,8 +614,8 @@ TIFF
Pillow reads and writes TIFF files. It can read both striped and tiled Pillow reads and writes TIFF files. It can read both striped and tiled
images, pixel and plane interleaved multi-band images. If you have images, pixel and plane interleaved multi-band images. If you have
libtiff and its headers installed, PIL can read and write many kinds libtiff and its headers installed, Pillow can read and write many kinds
of compressed TIFF files. If not, PIL will only read and write of compressed TIFF files. If not, Pillow will only read and write
uncompressed files. uncompressed files.
.. note:: .. note::
@ -734,8 +752,8 @@ using the general tags available through tiffinfo.
WebP WebP
^^^^ ^^^^
PIL reads and writes WebP files. The specifics of PIL's capabilities with this Pillow reads and writes WebP files. The specifics of Pillow's capabilities with
format are currently undocumented. this format are currently undocumented.
The :py:meth:`~PIL.Image.Image.save` method supports the following options: The :py:meth:`~PIL.Image.Image.save` method supports the following options:
@ -807,7 +825,7 @@ are available when the `save_all` argument is present and true.
XBM XBM
^^^ ^^^
PIL reads and writes X bitmap files (mode ``1``). Pillow reads and writes X bitmap files (mode ``1``).
Read-only formats Read-only formats
----------------- -----------------
@ -841,15 +859,15 @@ DDS
DDS is a popular container texture format used in video games and natively DDS is a popular container texture format used in video games and natively
supported by DirectX. supported by DirectX.
Currently, DXT1, DXT3, and DXT5 pixel formats are supported and only in ``RGBA`` Currently, uncompressed RGB data and DXT1, DXT3, and DXT5 pixel formats are
mode. supported, and only in ``RGBA`` mode.
.. versionadded:: 3.4.0 DXT3 .. versionadded:: 3.4.0 DXT3
FLI, FLC FLI, FLC
^^^^^^^^ ^^^^^^^^
PIL reads Autodesk FLI and FLC animations. Pillow reads Autodesk FLI and FLC animations.
The :py:meth:`~PIL.Image.Image.open` method sets the following The :py:meth:`~PIL.Image.Image.open` method sets the following
:py:attr:`~PIL.Image.Image.info` properties: :py:attr:`~PIL.Image.Image.info` properties:
@ -860,7 +878,7 @@ The :py:meth:`~PIL.Image.Image.open` method sets the following
FPX FPX
^^^ ^^^
PIL reads Kodak FlashPix files. In the current version, only the highest Pillow reads Kodak FlashPix files. In the current version, only the highest
resolution image is read from the file, and the viewing transform is not taken resolution image is read from the file, and the viewing transform is not taken
into account. into account.
@ -896,7 +914,7 @@ The :py:meth:`~PIL.Image.Image.open` method sets the following
GD GD
^^ ^^
PIL reads uncompressed GD2 files. Note that you must use Pillow reads uncompressed GD2 files. Note that you must use
:py:func:`PIL.GdImageFile.open` to read such a file. :py:func:`PIL.GdImageFile.open` to read such a file.
The :py:meth:`~PIL.Image.Image.open` method sets the following The :py:meth:`~PIL.Image.Image.open` method sets the following
@ -909,23 +927,23 @@ The :py:meth:`~PIL.Image.Image.open` method sets the following
IMT IMT
^^^ ^^^
PIL reads Image Tools images containing ``L`` data. Pillow reads Image Tools images containing ``L`` data.
IPTC/NAA IPTC/NAA
^^^^^^^^ ^^^^^^^^
PIL provides limited read support for IPTC/NAA newsphoto files. Pillow provides limited read support for IPTC/NAA newsphoto files.
MCIDAS MCIDAS
^^^^^^ ^^^^^^
PIL identifies and reads 8-bit McIdas area files. Pillow identifies and reads 8-bit McIdas area files.
MIC MIC
^^^ ^^^
PIL identifies and reads Microsoft Image Composer (MIC) files. When opened, the Pillow identifies and reads Microsoft Image Composer (MIC) files. When opened,
first sprite in the file is loaded. You can use :py:meth:`~file.seek` and the first sprite in the file is loaded. You can use :py:meth:`~file.seek` and
:py:meth:`~file.tell` to read other sprites from the file. :py:meth:`~file.tell` to read other sprites from the file.
Note that there may be an embedded gamma of 2.2 in MIC files. Note that there may be an embedded gamma of 2.2 in MIC files.
@ -941,22 +959,22 @@ zero-indexed and random access is supported.
PCD PCD
^^^ ^^^
PIL reads PhotoCD files containing ``RGB`` data. This only reads the 768x512 Pillow reads PhotoCD files containing ``RGB`` data. This only reads the 768x512
resolution image from the file. Higher resolutions are encoded in a proprietary resolution image from the file. Higher resolutions are encoded in a proprietary
encoding. encoding.
PIXAR PIXAR
^^^^^ ^^^^^
PIL provides limited support for PIXAR raster files. The library can identify Pillow provides limited support for PIXAR raster files. The library can
and read “dumped” RGB files. identify and read “dumped” RGB files.
The format code is ``PIXAR``. The format code is ``PIXAR``.
PSD PSD
^^^ ^^^
PIL identifies and reads PSD files written by Adobe Photoshop 2.5 and 3.0. Pillow identifies and reads PSD files written by Adobe Photoshop 2.5 and 3.0.
WAL WAL
@ -964,7 +982,7 @@ WAL
.. versionadded:: 1.1.4 .. versionadded:: 1.1.4
PIL reads Quake2 WAL texture files. Pillow reads Quake2 WAL texture files.
Note that this file format cannot be automatically identified, so you must use Note that this file format cannot be automatically identified, so you must use
the open function in the :py:mod:`~PIL.WalImageFile` module to read files in the open function in the :py:mod:`~PIL.WalImageFile` module to read files in
@ -976,7 +994,7 @@ the palette, use the putpalette method.
XPM XPM
^^^ ^^^
PIL reads X pixmap files (mode ``P``) with 256 colors or less. Pillow reads X pixmap files (mode ``P``) with 256 colors or less.
The :py:meth:`~PIL.Image.Image.open` method sets the following The :py:meth:`~PIL.Image.Image.open` method sets the following
:py:attr:`~PIL.Image.Image.info` properties: :py:attr:`~PIL.Image.Image.info` properties:
@ -991,14 +1009,14 @@ Write-only formats
PALM PALM
^^^^ ^^^^
PIL provides write-only support for PALM pixmap files. Pillow provides write-only support for PALM pixmap files.
The format code is ``Palm``, the extension is ``.palm``. The format code is ``Palm``, the extension is ``.palm``.
PDF PDF
^^^ ^^^
PIL can write PDF (Acrobat) images. Such images are written as binary PDF 1.4 Pillow can write PDF (Acrobat) images. Such images are written as binary PDF 1.4
files, using either JPEG or HEX encoding depending on the image mode (and files, using either JPEG or HEX encoding depending on the image mode (and
whether JPEG support is available or not). whether JPEG support is available or not).
@ -1077,7 +1095,7 @@ The :py:meth:`~PIL.Image.Image.save` method can take the following keyword argum
XV Thumbnails XV Thumbnails
^^^^^^^^^^^^^ ^^^^^^^^^^^^^
PIL can read XV thumbnail files. Pillow can read XV thumbnail files.
Identify-only formats Identify-only formats
--------------------- ---------------------
@ -1087,7 +1105,7 @@ BUFR
.. versionadded:: 1.1.3 .. versionadded:: 1.1.3
PIL provides a stub driver for BUFR files. Pillow provides a stub driver for BUFR files.
To add read or write support to your application, use To add read or write support to your application, use
:py:func:`PIL.BufrStubImagePlugin.register_handler`. :py:func:`PIL.BufrStubImagePlugin.register_handler`.
@ -1097,7 +1115,7 @@ FITS
.. versionadded:: 1.1.5 .. versionadded:: 1.1.5
PIL provides a stub driver for FITS files. Pillow provides a stub driver for FITS files.
To add read or write support to your application, use To add read or write support to your application, use
:py:func:`PIL.FitsStubImagePlugin.register_handler`. :py:func:`PIL.FitsStubImagePlugin.register_handler`.
@ -1107,11 +1125,11 @@ GRIB
.. versionadded:: 1.1.5 .. versionadded:: 1.1.5
PIL provides a stub driver for GRIB files. Pillow provides a stub driver for GRIB files.
The driver requires the file to start with a GRIB header. If you have files The driver requires the file to start with a GRIB header. If you have files
with embedded GRIB data, or files with multiple GRIB fields, your application with embedded GRIB data, or files with multiple GRIB fields, your application
has to seek to the header before passing the file handle to PIL. has to seek to the header before passing the file handle to Pillow.
To add read or write support to your application, use To add read or write support to your application, use
:py:func:`PIL.GribStubImagePlugin.register_handler`. :py:func:`PIL.GribStubImagePlugin.register_handler`.
@ -1121,7 +1139,7 @@ HDF5
.. versionadded:: 1.1.5 .. versionadded:: 1.1.5
PIL provides a stub driver for HDF5 files. Pillow provides a stub driver for HDF5 files.
To add read or write support to your application, use To add read or write support to your application, use
:py:func:`PIL.Hdf5StubImagePlugin.register_handler`. :py:func:`PIL.Hdf5StubImagePlugin.register_handler`.
@ -1129,12 +1147,12 @@ To add read or write support to your application, use
MPEG MPEG
^^^^ ^^^^
PIL identifies MPEG files. Pillow identifies MPEG files.
WMF WMF
^^^ ^^^
PIL can identify playable WMF files. Pillow can identify playable WMF files.
In PIL 1.1.4 and earlier, the WMF driver provides some limited rendering In PIL 1.1.4 and earlier, the WMF driver provides some limited rendering
support, but not enough to be useful for any real application. support, but not enough to be useful for any real application.

View File

@ -396,10 +396,6 @@ Reading sequences
As seen in this example, youll get an :py:exc:`EOFError` exception when the As seen in this example, youll get an :py:exc:`EOFError` exception when the
sequence ends. sequence ends.
Note that most drivers in the current version of the library only allow you to
seek to the next frame (as in the above example). To rewind the file, you may
have to reopen it.
The following class lets you use the for-statement to loop over the sequence: The following class lets you use the for-statement to loop over the sequence:
Using the ImageSequence Iterator class Using the ImageSequence Iterator class

View File

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

View File

@ -255,7 +255,7 @@ Methods
Draw a shape. Draw a shape.
.. py:method:: PIL.ImageDraw.ImageDraw.text(xy, text, fill=None, font=None, anchor=None, spacing=0, align="left", direction=None, features=None) .. py:method:: PIL.ImageDraw.ImageDraw.text(xy, text, fill=None, font=None, anchor=None, spacing=0, align="left", direction=None, features=None, language=None)
Draws the string at the given position. Draws the string at the given position.
@ -287,7 +287,17 @@ Methods
.. versionadded:: 4.2.0 .. versionadded:: 4.2.0
.. py:method:: PIL.ImageDraw.ImageDraw.multiline_text(xy, text, fill=None, font=None, anchor=None, spacing=0, align="left", direction=None, features=None) :param language: Language of the text. Different languages may use
different glyph shapes or ligatures. This parameter tells
the font which language the text is in, and to apply the
correct substitutions as appropriate, if available.
It should be a `BCP47 language code
<https://www.w3.org/International/articles/language-tags/>`
Requires libraqm.
.. versionadded:: 6.0.0
.. py:method:: PIL.ImageDraw.ImageDraw.multiline_text(xy, text, fill=None, font=None, anchor=None, spacing=0, align="left", direction=None, features=None, language=None)
Draws the string at the given position. Draws the string at the given position.
@ -316,7 +326,17 @@ Methods
.. versionadded:: 4.2.0 .. versionadded:: 4.2.0
.. py:method:: PIL.ImageDraw.ImageDraw.textsize(text, font=None, spacing=4, direction=None, features=None) :param language: Language of the text. Different languages may use
different glyph shapes or ligatures. This parameter tells
the font which language the text is in, and to apply the
correct substitutions as appropriate, if available.
It should be a `BCP47 language code
<https://www.w3.org/International/articles/language-tags/>`
Requires libraqm.
.. versionadded:: 6.0.0
.. py:method:: PIL.ImageDraw.ImageDraw.textsize(text, font=None, spacing=4, direction=None, features=None, language=None)
Return the size of the given string, in pixels. Return the size of the given string, in pixels.
@ -330,7 +350,6 @@ Methods
Requires libraqm. Requires libraqm.
.. versionadded:: 4.2.0 .. versionadded:: 4.2.0
:param features: A list of OpenType font features to be used during text :param features: A list of OpenType font features to be used during text
layout. This is usually used to turn on optional layout. This is usually used to turn on optional
font features that are not enabled by default, font features that are not enabled by default,
@ -343,8 +362,17 @@ Methods
Requires libraqm. Requires libraqm.
.. versionadded:: 4.2.0 .. versionadded:: 4.2.0
:param language: Language of the text. Different languages may use
different glyph shapes or ligatures. This parameter tells
the font which language the text is in, and to apply the
correct substitutions as appropriate, if available.
It should be a `BCP47 language code
<https://www.w3.org/International/articles/language-tags/>`
Requires libraqm.
.. py:method:: PIL.ImageDraw.ImageDraw.multiline_textsize(text, font=None, spacing=4, direction=None, features=None) .. versionadded:: 6.0.0
.. py:method:: PIL.ImageDraw.ImageDraw.multiline_textsize(text, font=None, spacing=4, direction=None, features=None, language=None)
Return the size of the given string, in pixels. Return the size of the given string, in pixels.
@ -370,6 +398,16 @@ Methods
.. versionadded:: 4.2.0 .. versionadded:: 4.2.0
:param language: Language of the text. Different languages may use
different glyph shapes or ligatures. This parameter tells
the font which language the text is in, and to apply the
correct substitutions as appropriate, if available.
It should be a `BCP47 language code
<https://www.w3.org/International/articles/language-tags/>`
Requires libraqm.
.. versionadded:: 6.0.0
.. py:method:: PIL.ImageDraw.getdraw(im=None, hints=None) .. py:method:: PIL.ImageDraw.getdraw(im=None, hints=None)
.. warning:: This method is experimental. .. warning:: This method is experimental.

View File

@ -47,11 +47,45 @@ Functions
Methods Methods
------- -------
.. py:method:: PIL.ImageFont.ImageFont.getsize(text) .. py:method:: PIL.ImageFont.ImageFont.getsize(text, direction=None, features=[], language=None)
Returns width and height (in pixels) of given text if rendered in font with
provided direction, features, and language.
:param text: Text to measure.
:param direction: Direction of the text. It can be 'rtl' (right to
left), 'ltr' (left to right) or 'ttb' (top to bottom).
Requires libraqm.
.. versionadded:: 4.2.0
:param features: A list of OpenType font features to be used during text
layout. This is usually used to turn on optional
font features that are not enabled by default,
for example 'dlig' or 'ss01', but can be also
used to turn off default font features for
example '-liga' to disable ligatures or '-kern'
to disable kerning. To get all supported
features, see
https://docs.microsoft.com/en-us/typography/opentype/spec/featurelist
Requires libraqm.
.. versionadded:: 4.2.0
:param language: Language of the text. Different languages may use
different glyph shapes or ligatures. This parameter tells
the font which language the text is in, and to apply the
correct substitutions as appropriate, if available.
It should be a `BCP47 language code
<https://www.w3.org/International/articles/language-tags/>`
Requires libraqm.
.. versionadded:: 6.0.0
:return: (width, height) :return: (width, height)
.. py:method:: PIL.ImageFont.ImageFont.getmask(text, mode='', direction=None, features=[]) .. py:method:: PIL.ImageFont.ImageFont.getmask(text, mode='', direction=None, features=[], language=None)
Create a bitmap for the text. Create a bitmap for the text.
@ -85,5 +119,15 @@ Methods
.. versionadded:: 4.2.0 .. versionadded:: 4.2.0
:param language: Language of the text. Different languages may use
different glyph shapes or ligatures. This parameter tells
the font which language the text is in, and to apply the
correct substitutions as appropriate, if available.
It should be a `BCP47 language code
<https://www.w3.org/International/articles/language-tags/>`
Requires libraqm.
.. versionadded:: 6.0.0
:return: An internal PIL storage memory instance as defined by the :return: An internal PIL storage memory instance as defined by the
:py:mod:`PIL.Image.core` interface module. :py:mod:`PIL.Image.core` interface module.

View File

@ -3,10 +3,10 @@
File Handling in Pillow File Handling in Pillow
======================= =======================
When opening a file as an image, Pillow requires a filename, When opening a file as an image, Pillow requires a filename, ``pathlib.Path``
pathlib.Path object, or a file-like object. Pillow uses the filename object, or a file-like object. Pillow uses the filename or ``Path`` to open a
or Path to open a file, so for the rest of this article, they will all file, so for the rest of this article, they will all be treated as a file-like
be treated as a file-like object. object.
The first four of these items are equivalent, the last is dangerous The first four of these items are equivalent, the last is dangerous
and may fail:: and may fail::
@ -48,24 +48,23 @@ Issues
Image Lifecycle Image Lifecycle
--------------- ---------------
* ``Image.open()`` Path-like objects are opened as a file. Metadata is read * ``Image.open()`` Filenames and ``Path`` objects are opened as a file.
from the open file. The file is left open for further usage. Metadata is read from the open file. The file is left open for further usage.
* ``Image.Image.load()`` When the pixel data from the image is * ``Image.Image.load()`` When the pixel data from the image is
required, ``load()`` is called. The current frame is read into required, ``load()`` is called. The current frame is read into
memory. The image can now be used independently of the underlying memory. The image can now be used independently of the underlying
image file. image file.
If a filename or a path-like object was passed to ``Image.open()``, then If a filename or a ``Path`` object was passed to ``Image.open()``, then the
the file object was opened by Pillow and is considered to be used exclusively file object was opened by Pillow and is considered to be used exclusively by
by Pillow. So if the image is a single-frame image, the file will Pillow. So if the image is a single-frame image, the file will be closed in
be closed in this method after the frame is read. If the image is a this method after the frame is read. If the image is a multi-frame image,
multi-frame image, (e.g. multipage TIFF and animated GIF) the image file is (e.g. multipage TIFF and animated GIF) the image file is left open so that
left open so that ``Image.Image.seek()`` can load the appropriate frame. ``Image.Image.seek()`` can load the appropriate frame.
* ``Image.Image.close()`` Closes the file pointer and destroys the * ``Image.Image.close()`` Closes the file and destroys the core image object.
core image object. This is used in the Pillow context manager This is used in the Pillow context manager support. e.g.::
support. e.g.::
with Image.open('test.jpg') as img: with Image.open('test.jpg') as img:
... # image operations here. ... # image operations here.
@ -84,17 +83,9 @@ data until the caller has explicitly closed the image.
Complications Complications
------------- -------------
* TiffImagePlugin has some code to pass the underlying file descriptor * ``TiffImagePlugin`` has some code to pass the underlying file descriptor into
into libtiff (if working on an actual file). Since libtiff closes libtiff (if working on an actual file). Since libtiff closes the file
the file descriptor internally, it is duplicated prior to passing it descriptor internally, it is duplicated prior to passing it into libtiff.
into libtiff.
* ``decoder.handles_eof`` This slightly misnamed flag indicates that
the decoder wants to be called with a 0 length buffer when reads are
done. Despite the comments in ``ImageFile.load()``, the only decoder
that actually uses this flag is the Jpeg2K decoder. The use of this
flag in Jpeg2K predated the change to the decoder that added the
pulls_fd flag, and is therefore not used.
* I don't think that there's any way to make this safe without * I don't think that there's any way to make this safe without
changing the lazy loading:: changing the lazy loading::

View File

@ -26,12 +26,26 @@ Several undocumented functions in ``ImageOps`` were deprecated in Pillow 4.3.0 (
and have now been removed: ``gaussian_blur``, ``gblur``, ``unsharp_mask``, ``usm`` and and have now been removed: ``gaussian_blur``, ``gblur``, ``unsharp_mask``, ``usm`` and
``box_blur``. Use the equivalent operations in ``ImageFilter`` instead. ``box_blur``. Use the equivalent operations in ``ImageFilter`` instead.
Removed deprecated VERSION
^^^^^^^^^^^^^^^^^^^^^^^^^^
``VERSION`` (the old PIL version, always 1.1.7) has been removed. Use ``__version__``
instead.
API Changes API Changes
=========== ===========
Deprecations Deprecations
^^^^^^^^^^^^ ^^^^^^^^^^^^
Python 2.7
~~~~~~~~~~
Python 2.7 reaches end-of-life on 2020-01-01.
Pillow 7.0.0 will be released on 2020-01-01 and will drop support for Python 2.7, making
Pillow 6.x the last series to support Python 2.
PyQt4 and PySide PyQt4 and PySide
~~~~~~~~~~~~~~~~ ~~~~~~~~~~~~~~~~
@ -103,18 +117,76 @@ Deprecated Use instead
``product_model`` Unicode ``model`` ``product_model`` Unicode ``model``
======================== =============================== ======================== ===============================
MIME type improvements
^^^^^^^^^^^^^^^^^^^^^^
Previously, all JPEG2000 images had the MIME type "image/jpx". This has now been
corrected. After the file format drivers have been loaded, ``Image.MIME["JPEG2000"]``
will return "image/jp2". ``ImageFile.get_format_mimetype`` will return "image/jpx" if
a JPX profile is present, or "image/jp2" otherwise.
Previously, all SGI images had the MIME type "image/rgb". This has now been
corrected. After the file format drivers have been loaded, ``Image.MIME["SGI"]``
will return "image/sgi". ``ImageFile.get_format_mimetype`` will return "image/rgb" if
RGB image data is present, or "image/sgi" otherwise.
MIME types have been added to the PPM format. After the file format drivers have been
loaded, ``Image.MIME["PPM"]`` will now return the generic "image/x-portable-anymap".
``ImageFile.get_format_mimetype`` will return a MIME type specific to the color type.
The TGA, PCX and ICO formats also now have MIME types: "image/x-tga", "image/x-pcx" and
"image/x-icon" respectively.
API Additions API Additions
============= =============
TODO DIB file format
^^^^ ^^^^^^^^^^^^^^^
TODO Pillow now supports reading and writing the Device Independent Bitmap file format.
Image.quantize
^^^^^^^^^^^^^^
The ``dither`` option is now a customisable parameter (was previously hardcoded to ``1``).
This parameter takes the same values used in ``Image.convert``.
New language parameter
^^^^^^^^^^^^^^^^^^^^^^
These text-rendering functions now accept a ``language`` parameter to request
language-specific glyphs and ligatures from the font:
* ``ImageDraw.ImageDraw.multiline_text()``
* ``ImageDraw.ImageDraw.multiline_textsize()``
* ``ImageDraw.ImageDraw.text()``
* ``ImageDraw.ImageDraw.textsize()``
* ``ImageFont.ImageFont.getmask()``
* ``ImageFont.ImageFont.getsize_multiline()``
* ``ImageFont.ImageFont.getsize()``
PNG EXIF data
^^^^^^^^^^^^^
EXIF data can now be read from and saved to PNG images. However, unlike other image
formats, EXIF data is not guaranteed to have been read until
:py:meth:`~PIL.Image.Image.load` has been called.
Other Changes Other Changes
============= =============
TODO Reading new DDS image format
^^^^ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^
TODO Pillow can now read uncompressed RGB data from DDS images.
Reading TIFF with old-style JPEG compression
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
Added support reading TIFF files with old-style JPEG compression through LibTIFF. All
YCbCr TIFF images are now always read as RGB.
TIFF compression codecs
^^^^^^^^^^^^^^^^^^^^^^^
Support has been added for the LZMA, Zstd and WebP TIFF compression codecs.

View File

@ -52,6 +52,10 @@ def _accept(prefix):
return prefix[:2] == b"BM" return prefix[:2] == b"BM"
def _dib_accept(prefix):
return i32(prefix[:4]) in [12, 40, 64, 108, 124]
# ============================================================================= # =============================================================================
# Image plugin for the Windows BMP format. # Image plugin for the Windows BMP format.
# ============================================================================= # =============================================================================
@ -101,53 +105,52 @@ class BmpImageFile(ImageFile.ImageFile):
# --------------------------------------------- Windows Bitmap v2 to v5 # --------------------------------------------- Windows Bitmap v2 to v5
# v3, OS/2 v2, v4, v5 # v3, OS/2 v2, v4, v5
elif file_info['header_size'] in (40, 64, 108, 124): elif file_info['header_size'] in (40, 64, 108, 124):
if file_info['header_size'] >= 40: # v3 and OS/2 file_info['y_flip'] = i8(header_data[7]) == 0xff
file_info['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])
file_info['height'] = (i32(header_data[4:8]) if not file_info['y_flip']
if not file_info['y_flip'] else 2**32 - i32(header_data[4:8]))
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]) # byte size of pixel data
# byte size of pixel data file_info['data_size'] = i32(header_data[16:20])
file_info['data_size'] = i32(header_data[16:20]) file_info['pixels_per_meter'] = (i32(header_data[20:24]),
file_info['pixels_per_meter'] = (i32(header_data[20:24]), i32(header_data[24:28]))
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( map(lambda x: int(math.ceil(x / 39.3701)),
map(lambda x: int(math.ceil(x / 39.3701)), file_info['pixels_per_meter']))
file_info['pixels_per_meter'])) if file_info['compression'] == self.BITFIELDS:
if file_info['compression'] == self.BITFIELDS: if len(header_data) >= 52:
if len(header_data) >= 52: for idx, mask in enumerate(['r_mask',
for idx, mask in enumerate(['r_mask', 'g_mask',
'g_mask', 'b_mask',
'b_mask', 'a_mask']):
'a_mask']): file_info[mask] = i32(
file_info[mask] = i32( header_data[36 + idx * 4:40 + idx * 4]
header_data[36 + idx * 4:40 + idx * 4] )
) else:
else: # 40 byte headers only have the three components in the
# 40 byte headers only have the three components in the # bitfields masks, ref:
# bitfields masks, ref: # https://msdn.microsoft.com/en-us/library/windows/desktop/dd183376(v=vs.85).aspx
# https://msdn.microsoft.com/en-us/library/windows/desktop/dd183376(v=vs.85).aspx # See also
# See also # https://github.com/python-pillow/Pillow/issues/1293
# https://github.com/python-pillow/Pillow/issues/1293 # There is a 4th component in the RGBQuad, in the alpha
# There is a 4th component in the RGBQuad, in the alpha # location, but it is listed as a reserved component,
# location, but it is listed as a reserved component, # and it is not generally an alpha channel
# 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['rgb_mask'] = (file_info['r_mask'], file_info['g_mask'],
file_info['g_mask'], file_info['b_mask'])
file_info['b_mask']) file_info['rgba_mask'] = (file_info['r_mask'],
file_info['rgba_mask'] = (file_info['r_mask'], file_info['g_mask'],
file_info['g_mask'], file_info['b_mask'],
file_info['b_mask'], file_info['a_mask'])
file_info['a_mask'])
else: else:
raise IOError("Unsupported BMP header type (%d)" % raise IOError("Unsupported BMP header type (%d)" %
file_info['header_size']) file_info['header_size'])
@ -176,6 +179,7 @@ class BmpImageFile(ImageFile.ImageFile):
SUPPORTED = { SUPPORTED = {
32: [(0xff0000, 0xff00, 0xff, 0x0), 32: [(0xff0000, 0xff00, 0xff, 0x0),
(0xff0000, 0xff00, 0xff, 0xff000000), (0xff0000, 0xff00, 0xff, 0xff000000),
(0xff, 0xff00, 0xff0000, 0xff000000),
(0x0, 0x0, 0x0, 0x0), (0x0, 0x0, 0x0, 0x0),
(0xff000000, 0xff0000, 0xff00, 0x0)], (0xff000000, 0xff0000, 0xff00, 0x0)],
24: [(0xff0000, 0xff00, 0xff)], 24: [(0xff0000, 0xff00, 0xff)],
@ -184,6 +188,7 @@ class BmpImageFile(ImageFile.ImageFile):
MASK_MODES = { MASK_MODES = {
(32, (0xff0000, 0xff00, 0xff, 0x0)): "BGRX", (32, (0xff0000, 0xff00, 0xff, 0x0)): "BGRX",
(32, (0xff000000, 0xff0000, 0xff00, 0x0)): "XBGR", (32, (0xff000000, 0xff0000, 0xff00, 0x0)): "XBGR",
(32, (0xff, 0xff00, 0xff0000, 0xff000000)): "RGBA",
(32, (0xff0000, 0xff00, 0xff, 0xff000000)): "BGRA", (32, (0xff0000, 0xff00, 0xff, 0xff000000)): "BGRA",
(32, (0x0, 0x0, 0x0, 0x0)): "BGRA", (32, (0x0, 0x0, 0x0, 0x0)): "BGRA",
(24, (0xff0000, 0xff00, 0xff)): "BGR", (24, (0xff0000, 0xff00, 0xff)): "BGR",
@ -196,7 +201,7 @@ class BmpImageFile(ImageFile.ImageFile):
raw_mode = MASK_MODES[ raw_mode = MASK_MODES[
(file_info["bits"], file_info["rgba_mask"]) (file_info["bits"], file_info["rgba_mask"])
] ]
self.mode = "RGBA" if raw_mode in ("BGRA",) else self.mode self.mode = "RGBA" if "A" in raw_mode else self.mode
elif (file_info['bits'] in (24, 16) and elif (file_info['bits'] in (24, 16) and
file_info['rgb_mask'] in SUPPORTED[file_info['bits']]): file_info['rgb_mask'] in SUPPORTED[file_info['bits']]):
raw_mode = MASK_MODES[ raw_mode = MASK_MODES[
@ -291,7 +296,11 @@ SAVE = {
} }
def _save(im, fp, filename): def _dib_save(im, fp, filename):
_save(im, fp, filename, False)
def _save(im, fp, filename, bitmap_header=True):
try: try:
rawmode, bits, colors = SAVE[im.mode] rawmode, bits, colors = SAVE[im.mode]
except KeyError: except KeyError:
@ -306,14 +315,15 @@ def _save(im, fp, filename):
stride = ((im.size[0]*bits+7)//8+3) & (~3) stride = ((im.size[0]*bits+7)//8+3) & (~3)
header = 40 # or 64 for OS/2 version 2 header = 40 # or 64 for OS/2 version 2
offset = 14 + header + colors * 4
image = stride * im.size[1] image = stride * im.size[1]
# bitmap header # bitmap header
fp.write(b"BM" + # file type (magic) if bitmap_header:
o32(offset+image) + # file size offset = 14 + header + colors * 4
o32(0) + # reserved fp.write(b"BM" + # file type (magic)
o32(offset)) # image data offset o32(offset+image) + # file size
o32(0) + # reserved
o32(offset)) # image data offset
# bitmap info header # bitmap info header
fp.write(o32(header) + # info header size fp.write(o32(header) + # info header size
@ -352,3 +362,10 @@ Image.register_save(BmpImageFile.format, _save)
Image.register_extension(BmpImageFile.format, ".bmp") Image.register_extension(BmpImageFile.format, ".bmp")
Image.register_mime(BmpImageFile.format, "image/bmp") Image.register_mime(BmpImageFile.format, "image/bmp")
Image.register_open(DibImageFile.format, DibImageFile, _dib_accept)
Image.register_save(DibImageFile.format, _dib_save)
Image.register_extension(DibImageFile.format, ".dib")
Image.register_mime(DibImageFile.format, "image/bmp")

View File

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

View File

@ -123,43 +123,52 @@ class DdsImageFile(ImageFile.ImageFile):
# pixel format # pixel format
pfsize, pfflags = struct.unpack("<2I", header.read(8)) pfsize, pfflags = struct.unpack("<2I", header.read(8))
fourcc = header.read(4) fourcc = header.read(4)
bitcount, rmask, gmask, bmask, amask = struct.unpack("<5I", bitcount, = struct.unpack("<I", header.read(4))
header.read(20)) masks = struct.unpack("<4I", header.read(16))
if pfflags & 0x40:
# DDPF_RGB - Texture contains uncompressed RGB data
masks = {mask: ["R", "G", "B", "A"][i] for i, mask in enumerate(masks)}
rawmode = ""
if bitcount == 32:
rawmode += masks[0xff000000]
rawmode += masks[0xff0000] + masks[0xff00] + masks[0xff]
data_start = header_size + 4 self.tile = [("raw", (0, 0) + self.size, 0, (rawmode, 0, 1))]
n = 0
if fourcc == b"DXT1":
self.pixel_format = "DXT1"
n = 1
elif fourcc == b"DXT3":
self.pixel_format = "DXT3"
n = 2
elif fourcc == b"DXT5":
self.pixel_format = "DXT5"
n = 3
elif fourcc == b"DX10":
data_start += 20
# ignoring flags which pertain to volume textures and cubemaps
dxt10 = BytesIO(self.fp.read(20))
dxgi_format, dimension = struct.unpack("<II", dxt10.read(8))
if dxgi_format in (DXGI_FORMAT_BC7_TYPELESS,
DXGI_FORMAT_BC7_UNORM):
self.pixel_format = "BC7"
n = 7
elif dxgi_format == DXGI_FORMAT_BC7_UNORM_SRGB:
self.pixel_format = "BC7"
self.im_info["gamma"] = 1/2.2
n = 7
else:
raise NotImplementedError("Unimplemented DXGI format %d" %
(dxgi_format))
else: else:
raise NotImplementedError("Unimplemented pixel format %r" % data_start = header_size + 4
(fourcc)) n = 0
if fourcc == b"DXT1":
self.pixel_format = "DXT1"
n = 1
elif fourcc == b"DXT3":
self.pixel_format = "DXT3"
n = 2
elif fourcc == b"DXT5":
self.pixel_format = "DXT5"
n = 3
elif fourcc == b"DX10":
data_start += 20
# ignoring flags which pertain to volume textures and cubemaps
dxt10 = BytesIO(self.fp.read(20))
dxgi_format, dimension = struct.unpack("<II", dxt10.read(8))
if dxgi_format in (DXGI_FORMAT_BC7_TYPELESS,
DXGI_FORMAT_BC7_UNORM):
self.pixel_format = "BC7"
n = 7
elif dxgi_format == DXGI_FORMAT_BC7_UNORM_SRGB:
self.pixel_format = "BC7"
self.im_info["gamma"] = 1/2.2
n = 7
else:
raise NotImplementedError("Unimplemented DXGI format %d" %
(dxgi_format))
else:
raise NotImplementedError("Unimplemented pixel format %r" %
(fourcc))
self.tile = [ self.tile = [
("bcn", (0, 0) + self.size, data_start, (n)) ("bcn", (0, 0) + self.size, data_start, (n))
] ]
def load_seek(self, pos): def load_seek(self, pos):
pass pass

View File

@ -104,7 +104,7 @@ def Ghostscript(tile, size, fp, scale=1):
# Copy whole file to read in Ghostscript # Copy whole file to read in Ghostscript
with open(infile_temp, 'wb') as f: with open(infile_temp, 'wb') as f:
# fetch length of fp # fetch length of fp
fp.seek(0, 2) fp.seek(0, io.SEEK_END)
fsize = fp.tell() fsize = fp.tell()
# ensure start position # ensure start position
# go back # go back
@ -141,13 +141,11 @@ def Ghostscript(tile, size, fp, scale=1):
# push data through Ghostscript # push data through Ghostscript
try: try:
with open(os.devnull, 'w+b') as devnull: startupinfo = None
startupinfo = None if sys.platform.startswith('win'):
if sys.platform.startswith('win'): startupinfo = subprocess.STARTUPINFO()
startupinfo = subprocess.STARTUPINFO() startupinfo.dwFlags |= subprocess.STARTF_USESHOWWINDOW
startupinfo.dwFlags |= subprocess.STARTF_USESHOWWINDOW subprocess.check_call(command, startupinfo=startupinfo)
subprocess.check_call(command, stdout=devnull,
startupinfo=startupinfo)
im = Image.open(outfile) im = Image.open(outfile)
im.load() im.load()
finally: finally:
@ -169,7 +167,7 @@ class PSFile(object):
self.fp = fp self.fp = fp
self.char = None self.char = None
def seek(self, offset, whence=0): def seek(self, offset, whence=io.SEEK_SET):
self.char = None self.char = None
self.fp.seek(offset, whence) self.fp.seek(offset, whence)
@ -312,7 +310,7 @@ class EpsImageFile(ImageFile.ImageFile):
if s[:4] == b"%!PS": if s[:4] == b"%!PS":
# for HEAD without binary preview # for HEAD without binary preview
fp.seek(0, 2) fp.seek(0, io.SEEK_END)
length = fp.tell() length = fp.tell()
offset = 0 offset = 0
elif i32(s[0:4]) == 0xC6D3D0C5: elif i32(s[0:4]) == 0xC6D3D0C5:

View File

@ -122,6 +122,8 @@ class GifImageFile(ImageFile.ImageFile):
if not self._seek_check(frame): if not self._seek_check(frame):
return return
if frame < self.__frame: if frame < self.__frame:
if frame != 0:
self.im = None
self._seek(0) self._seek(0)
last_frame = self.__frame last_frame = self.__frame
@ -604,16 +606,16 @@ def _save_netpbm(im, fp, filename):
import os import os
from subprocess import Popen, check_call, PIPE, CalledProcessError from subprocess import Popen, check_call, PIPE, CalledProcessError
file = im._dump() tempfile = im._dump()
with open(filename, 'wb') as f: with open(filename, 'wb') as f:
if im.mode != "RGB": if im.mode != "RGB":
with open(os.devnull, 'wb') as devnull: with open(os.devnull, 'wb') as devnull:
check_call(["ppmtogif", file], stdout=f, stderr=devnull) check_call(["ppmtogif", tempfile], stdout=f, stderr=devnull)
else: else:
# Pipe ppmquant output into ppmtogif # Pipe ppmquant output into ppmtogif
# "ppmquant 256 %s | ppmtogif > %s" % (file, filename) # "ppmquant 256 %s | ppmtogif > %s" % (tempfile, filename)
quant_cmd = ["ppmquant", "256", file] quant_cmd = ["ppmquant", "256", tempfile]
togif_cmd = ["ppmtogif"] togif_cmd = ["ppmtogif"]
with open(os.devnull, 'wb') as devnull: with open(os.devnull, 'wb') as devnull:
quant_proc = Popen(quant_cmd, stdout=PIPE, stderr=devnull) quant_proc = Popen(quant_cmd, stdout=PIPE, stderr=devnull)
@ -632,7 +634,7 @@ def _save_netpbm(im, fp, filename):
raise CalledProcessError(retcode, togif_cmd) raise CalledProcessError(retcode, togif_cmd)
try: try:
os.unlink(file) os.unlink(tempfile)
except OSError: except OSError:
pass pass

View File

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

View File

@ -295,3 +295,5 @@ class IcoImageFile(ImageFile.ImageFile):
Image.register_open(IcoImageFile.format, IcoImageFile, _accept) Image.register_open(IcoImageFile.format, IcoImageFile, _accept)
Image.register_save(IcoImageFile.format, _save) Image.register_save(IcoImageFile.format, _save)
Image.register_extension(IcoImageFile.format, ".ico") Image.register_extension(IcoImageFile.format, ".ico")
Image.register_mime(IcoImageFile.format, "image/x-icon")

View File

@ -24,10 +24,10 @@
# See the README file for information on usage and redistribution. # See the README file for information on usage and redistribution.
# #
# VERSION is deprecated and will be removed in Pillow 6.0.0. # VERSION was removed in Pillow 6.0.0.
# PILLOW_VERSION is deprecated and will be removed after that. # PILLOW_VERSION is deprecated and will be removed in Pillow 7.0.0.
# Use __version__ instead. # Use __version__ instead.
from . import VERSION, PILLOW_VERSION, __version__, _plugins from . import PILLOW_VERSION, __version__, _plugins
from ._util import py3 from ._util import py3
import logging import logging
@ -60,8 +60,7 @@ except ImportError:
from collections import Callable from collections import Callable
# Silence warnings # Silence warning
assert VERSION
assert PILLOW_VERSION assert PILLOW_VERSION
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
@ -579,7 +578,12 @@ class Image(object):
return self return self
def __exit__(self, *args): def __exit__(self, *args):
self.close() if hasattr(self, 'fp') and getattr(self, '_exclusive_fp', False):
if hasattr(self, "_close__fp"):
self._close__fp()
if self.fp:
self.fp.close()
self.fp = None
def close(self): def close(self):
""" """
@ -611,12 +615,7 @@ class Image(object):
if sys.version_info.major >= 3: if sys.version_info.major >= 3:
def __del__(self): def __del__(self):
if hasattr(self, "_close__fp"): self.__exit__()
self._close__fp()
if (hasattr(self, 'fp') and hasattr(self, '_exclusive_fp')
and self.fp and self._exclusive_fp):
self.fp.close()
self.fp = None
def _copy(self): def _copy(self):
self.load() self.load()
@ -680,8 +679,7 @@ class Image(object):
:returns: png version of the image as bytes :returns: png version of the image as bytes
""" """
from io import BytesIO b = io.BytesIO()
b = BytesIO()
self.save(b, 'PNG') self.save(b, 'PNG')
return b.getvalue() return b.getvalue()
@ -952,7 +950,7 @@ class Image(object):
delete_trns = False delete_trns = False
# transparency handling # transparency handling
if has_transparency: if has_transparency:
if self.mode in ('L', 'RGB') and mode == 'RGBA': if self.mode in ('1', 'L', 'I', 'RGB') and mode == 'RGBA':
# Use transparent conversion to promote from transparent # Use transparent conversion to promote from transparent
# color to an alpha channel. # color to an alpha channel.
new_im = self._new(self.im.convert_transparent( new_im = self._new(self.im.convert_transparent(
@ -1051,7 +1049,7 @@ class Image(object):
new_im.info['transparency'] = trns new_im.info['transparency'] = trns
return new_im return new_im
def quantize(self, colors=256, method=None, kmeans=0, palette=None): def quantize(self, colors=256, method=None, kmeans=0, palette=None, dither=1):
""" """
Convert the image to 'P' mode with the specified number Convert the image to 'P' mode with the specified number
of colors. of colors.
@ -1064,6 +1062,10 @@ class Image(object):
:param kmeans: Integer :param kmeans: Integer
:param palette: Quantize to the palette of given :param palette: Quantize to the palette of given
:py:class:`PIL.Image.Image`. :py:class:`PIL.Image.Image`.
:param dither: Dithering method, used when converting from
mode "RGB" to "P" or from "RGB" or "L" to "1".
Available methods are NONE or FLOYDSTEINBERG (default).
Default: 1 (legacy setting)
:returns: A new image :returns: A new image
""" """
@ -1091,7 +1093,7 @@ class Image(object):
raise ValueError( raise ValueError(
"only RGB or L mode images can be quantized to a palette" "only RGB or L mode images can be quantized to a palette"
) )
im = self.im.convert("P", 1, palette.im) im = self.im.convert("P", dither, palette.im)
return self._new(im) return self._new(im)
return self._new(self.im.quantize(colors, method, kmeans)) return self._new(self.im.quantize(colors, method, kmeans))
@ -2003,9 +2005,6 @@ class Image(object):
**EOFError** exception. When a sequence file is opened, the **EOFError** exception. When a sequence file is opened, the
library automatically seeks to frame 0. library automatically seeks to frame 0.
Note that in the current version of the library, most sequence
formats only allows you to seek to the next frame.
See :py:meth:`~PIL.Image.Image.tell`. See :py:meth:`~PIL.Image.Image.tell`.
:param frame: Frame number, starting at 0. :param frame: Frame number, starting at 0.
@ -2023,10 +2022,10 @@ class Image(object):
debugging purposes. debugging purposes.
On Unix platforms, this method saves the image to a temporary On Unix platforms, this method saves the image to a temporary
PPM file, and calls either the **xv** utility or the **display** PPM file, and calls the **display**, **eog** or **xv**
utility, depending on which one can be found. utility, depending on which one can be found.
On macOS, this method saves the image to a temporary BMP file, and On macOS, this method saves the image to a temporary PNG file, and
opens it with the native Preview application. opens it with the native Preview application.
On Windows, it saves the image to a temporary BMP file, and uses On Windows, it saves the image to a temporary BMP file, and uses
@ -2128,11 +2127,12 @@ class Image(object):
self.draft(None, size) self.draft(None, size)
im = self.resize(size, resample) if self.size != size:
im = self.resize(size, resample)
self.im = im.im self.im = im.im
self.mode = im.mode self._size = size
self._size = size self.mode = self.im.mode
self.readonly = 0 self.readonly = 0
self.pyaccess = None self.pyaccess = None

View File

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

View File

@ -282,13 +282,17 @@ class ImageDraw(object):
self.draw.draw_bitmap(xy, mask, ink) self.draw.draw_bitmap(xy, mask, ink)
def multiline_text(self, xy, text, fill=None, font=None, anchor=None, def multiline_text(self, xy, text, fill=None, font=None, anchor=None,
spacing=4, align="left", direction=None, features=None): spacing=4, align="left", direction=None, features=None,
language=None):
widths = [] widths = []
max_width = 0 max_width = 0
lines = self._multiline_split(text) lines = self._multiline_split(text)
line_spacing = self.textsize('A', font=font)[1] + spacing line_spacing = self.textsize('A', font=font)[1] + spacing
for line in lines: for line in lines:
line_width, line_height = self.textsize(line, font) line_width, line_height = self.textsize(line, font,
direction=direction,
features=features,
language=language)
widths.append(line_width) widths.append(line_width)
max_width = max(max_width, line_width) max_width = max(max_width, line_width)
left, top = xy left, top = xy
@ -302,29 +306,30 @@ class ImageDraw(object):
else: else:
raise ValueError('align must be "left", "center" or "right"') raise ValueError('align must be "left", "center" or "right"')
self.text((left, top), line, fill, font, anchor, self.text((left, top), line, fill, font, anchor,
direction=direction, features=features) direction=direction, features=features, language=language)
top += line_spacing top += line_spacing
left = xy[0] left = xy[0]
def textsize(self, text, font=None, spacing=4, direction=None, def textsize(self, text, font=None, spacing=4, direction=None,
features=None): features=None, language=None):
"""Get the size of a given string, in pixels.""" """Get the size of a given string, in pixels."""
if self._multiline_check(text): if self._multiline_check(text):
return self.multiline_textsize(text, font, spacing, return self.multiline_textsize(text, font, spacing,
direction, features) direction, features, language)
if font is None: if font is None:
font = self.getfont() font = self.getfont()
return font.getsize(text, direction, features) return font.getsize(text, direction, features, language)
def multiline_textsize(self, text, font=None, spacing=4, direction=None, def multiline_textsize(self, text, font=None, spacing=4, direction=None,
features=None): features=None, language=None):
max_width = 0 max_width = 0
lines = self._multiline_split(text) lines = self._multiline_split(text)
line_spacing = self.textsize('A', font=font)[1] + spacing line_spacing = self.textsize('A', font=font)[1] + spacing
for line in lines: for line in lines:
line_width, line_height = self.textsize(line, font, spacing, line_width, line_height = self.textsize(line, font, spacing,
direction, features) direction, features,
language)
max_width = max(max_width, line_width) max_width = max(max_width, line_width)
return max_width, len(lines)*line_spacing - spacing return max_width, len(lines)*line_spacing - spacing

View File

@ -120,9 +120,10 @@ class ImageFile(Image.Image):
pass pass
def get_format_mimetype(self): def get_format_mimetype(self):
if self.format is None: if self.custom_mimetype:
return return self.custom_mimetype
return self.custom_mimetype or Image.MIME.get(self.format.upper()) if self.format is not None:
return Image.MIME.get(self.format.upper())
def verify(self): def verify(self):
"""Check file integrity""" """Check file integrity"""
@ -491,7 +492,7 @@ def _save(im, fp, tile, bufsize=0):
for e, b, o, a in tile: for e, b, o, a in tile:
e = Image._getencoder(im.mode, e, a, im.encoderconfig) e = Image._getencoder(im.mode, e, a, im.encoderconfig)
if o > 0: if o > 0:
fp.seek(o, 0) fp.seek(o)
e.setimage(im.im, b) e.setimage(im.im, b)
if e.pushes_fd: if e.pushes_fd:
e.setfd(fp) e.setfd(fp)
@ -510,7 +511,7 @@ def _save(im, fp, tile, bufsize=0):
for e, b, o, a in tile: for e, b, o, a in tile:
e = Image._getencoder(im.mode, e, a, im.encoderconfig) e = Image._getencoder(im.mode, e, a, im.encoderconfig)
if o > 0: if o > 0:
fp.seek(o, 0) fp.seek(o)
e.setimage(im.im, b) e.setimage(im.im, b)
if e.pushes_fd: if e.pushes_fd:
e.setfd(fp) e.setfd(fp)
@ -595,8 +596,6 @@ 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. :param buffer: A bytes object with the data to be decoded.
If `handles_eof` is set, then `buffer` will be empty and `self.fd`
will be set.
:returns: A tuple of (bytes consumed, errcode). :returns: A tuple of (bytes consumed, errcode).
If finished with decoding return <0 for the bytes consumed. If finished with decoding return <0 for the bytes consumed.
Err codes are from `ERRORS` Err codes are from `ERRORS`

View File

@ -158,17 +158,17 @@ class FreeTypeFont(object):
def getmetrics(self): def getmetrics(self):
return self.font.ascent, self.font.descent return self.font.ascent, self.font.descent
def getsize(self, text, direction=None, features=None): def getsize(self, text, direction=None, features=None, language=None):
size, offset = self.font.getsize(text, direction, features) size, offset = self.font.getsize(text, direction, features, language)
return (size[0] + offset[0], size[1] + offset[1]) return (size[0] + offset[0], size[1] + offset[1])
def getsize_multiline(self, text, direction=None, def getsize_multiline(self, text, direction=None, spacing=4,
spacing=4, features=None): features=None, language=None):
max_width = 0 max_width = 0
lines = self._multiline_split(text) lines = self._multiline_split(text)
line_spacing = self.getsize('A')[1] + spacing line_spacing = self.getsize('A')[1] + spacing
for line in lines: for line in lines:
line_width, line_height = self.getsize(line, direction, features) line_width, line_height = self.getsize(line, direction, features, language)
max_width = max(max_width, line_width) max_width = max(max_width, line_width)
return max_width, len(lines)*line_spacing - spacing return max_width, len(lines)*line_spacing - spacing
@ -176,15 +176,15 @@ class FreeTypeFont(object):
def getoffset(self, text): def getoffset(self, text):
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, language=None):
return self.getmask2(text, mode, direction=direction, return self.getmask2(text, mode, direction=direction, features=features,
features=features)[0] language=language)[0]
def getmask2(self, text, mode="", fill=Image.core.fill, direction=None, def getmask2(self, text, mode="", fill=Image.core.fill, direction=None,
features=None, *args, **kwargs): features=None, language=None, *args, **kwargs):
size, offset = self.font.getsize(text, direction, features) size, offset = self.font.getsize(text, direction, features, language)
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, language)
return im, offset return im, offset
def font_variant(self, font=None, size=None, index=None, encoding=None, def font_variant(self, font=None, size=None, index=None, encoding=None,

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -54,19 +54,24 @@ _MAGIC = b"\211PNG\r\n\032\n"
_MODES = { _MODES = {
# supported bits/color combinations, and corresponding modes/rawmodes # supported bits/color combinations, and corresponding modes/rawmodes
# Greyscale
(1, 0): ("1", "1"), (1, 0): ("1", "1"),
(2, 0): ("L", "L;2"), (2, 0): ("L", "L;2"),
(4, 0): ("L", "L;4"), (4, 0): ("L", "L;4"),
(8, 0): ("L", "L"), (8, 0): ("L", "L"),
(16, 0): ("I", "I;16B"), (16, 0): ("I", "I;16B"),
# Truecolour
(8, 2): ("RGB", "RGB"), (8, 2): ("RGB", "RGB"),
(16, 2): ("RGB", "RGB;16B"), (16, 2): ("RGB", "RGB;16B"),
# Indexed-colour
(1, 3): ("P", "P;1"), (1, 3): ("P", "P;1"),
(2, 3): ("P", "P;2"), (2, 3): ("P", "P;2"),
(4, 3): ("P", "P;4"), (4, 3): ("P", "P;4"),
(8, 3): ("P", "P"), (8, 3): ("P", "P"),
# Greyscale with alpha
(8, 4): ("LA", "LA"), (8, 4): ("LA", "LA"),
(16, 4): ("RGBA", "LA;16B"), # LA;16B->LA not yet available (16, 4): ("RGBA", "LA;16B"), # LA;16B->LA not yet available
# Truecolour with alpha
(8, 6): ("RGBA", "RGBA"), (8, 6): ("RGBA", "RGBA"),
(16, 6): ("RGBA", "RGBA;16B"), (16, 6): ("RGBA", "RGBA;16B"),
} }
@ -386,7 +391,7 @@ class PngStream(ChunkStream):
# otherwise, we have a byte string with one alpha value # otherwise, we have a byte string with one alpha value
# for each palette entry # for each palette entry
self.im_info["transparency"] = s self.im_info["transparency"] = s
elif self.im_mode == "L": elif self.im_mode in ("1", "L", "I"):
self.im_info["transparency"] = i16(s) self.im_info["transparency"] = i16(s)
elif self.im_mode == "RGB": elif self.im_mode == "RGB":
self.im_info["transparency"] = i16(s), i16(s[2:]), i16(s[4:]) self.im_info["transparency"] = i16(s), i16(s[2:]), i16(s[4:])
@ -529,6 +534,11 @@ class PngStream(ChunkStream):
return s return s
def chunk_eXIf(self, pos, length):
s = ImageFile._safe_read(self.fp, length)
self.im_info["exif"] = b"Exif\x00\x00"+s
return s
# APNG chunks # APNG chunks
def chunk_acTL(self, pos, length): def chunk_acTL(self, pos, length):
s = ImageFile._safe_read(self.fp, length) s = ImageFile._safe_read(self.fp, length)
@ -683,6 +693,12 @@ class PngImageFile(ImageFile.ImageFile):
self.png.close() self.png.close()
self.png = None self.png = None
def _getexif(self):
if "exif" not in self.info:
self.load()
from .JpegImagePlugin import _getexif
return _getexif(self)
# -------------------------------------------------------------------- # --------------------------------------------------------------------
# PNG writer # PNG writer
@ -696,6 +712,7 @@ _OUTMODES = {
"L": ("L", b'\x08\x00'), "L": ("L", b'\x08\x00'),
"LA": ("LA", b'\x08\x04'), "LA": ("LA", b'\x08\x04'),
"I": ("I;16B", b'\x10\x00'), "I": ("I;16B", b'\x10\x00'),
"I;16": ("I;16B", b'\x10\x00'),
"P;1": ("P;1", b'\x01\x03'), "P;1": ("P;1", b'\x01\x03'),
"P;2": ("P;2", b'\x02\x03'), "P;2": ("P;2", b'\x02\x03'),
"P;4": ("P;4", b'\x04\x03'), "P;4": ("P;4", b'\x04\x03'),
@ -829,7 +846,7 @@ def _save(im, fp, filename, chunk=putchunk):
transparency = max(0, min(255, transparency)) transparency = max(0, min(255, transparency))
alpha = b'\xFF' * transparency + b'\0' alpha = b'\xFF' * transparency + b'\0'
chunk(fp, b"tRNS", alpha[:alpha_bytes]) chunk(fp, b"tRNS", alpha[:alpha_bytes])
elif im.mode == "L": elif im.mode in ("1", "L", "I"):
transparency = max(0, min(65535, transparency)) transparency = max(0, min(65535, transparency))
chunk(fp, b"tRNS", o16(transparency)) chunk(fp, b"tRNS", o16(transparency))
elif im.mode == "RGB": elif im.mode == "RGB":
@ -861,6 +878,12 @@ def _save(im, fp, filename, chunk=putchunk):
chunks.remove(cid) chunks.remove(cid)
chunk(fp, cid, data) chunk(fp, cid, data)
exif = im.encoderinfo.get("exif", im.info.get("exif"))
if exif:
if exif.startswith(b"Exif\x00\x00"):
exif = exif[6:]
chunk(fp, b"eXIf", exif)
ImageFile._save(im, _idat(fp, chunk), ImageFile._save(im, _idat(fp, chunk),
[("zip", (0, 0)+im.size, 0, rawmode)]) [("zip", (0, 0)+im.size, 0, rawmode)])

View File

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

View File

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

View File

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

View File

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

View File

@ -226,4 +226,6 @@ def _save(im, fp, filename):
Image.register_open(TgaImageFile.format, TgaImageFile) Image.register_open(TgaImageFile.format, TgaImageFile)
Image.register_save(TgaImageFile.format, _save) Image.register_save(TgaImageFile.format, _save)
Image.register_extension(TgaImageFile.format, ".tga") Image.register_extensions(TgaImageFile.format, [".tga", ".icb", ".vda", ".vst"])
Image.register_mime(TgaImageFile.format, "image/x-tga")

View File

@ -263,10 +263,10 @@ OPEN_INFO = {
(II, 5, (1,), 1, (8, 8, 8, 8, 8, 8), (0, 0)): ("CMYK", "CMYKXX"), (II, 5, (1,), 1, (8, 8, 8, 8, 8, 8), (0, 0)): ("CMYK", "CMYKXX"),
(MM, 5, (1,), 1, (8, 8, 8, 8, 8, 8), (0, 0)): ("CMYK", "CMYKXX"), (MM, 5, (1,), 1, (8, 8, 8, 8, 8, 8), (0, 0)): ("CMYK", "CMYKXX"),
# JPEG compressed images handled by LibTiff and auto-converted to RGB # JPEG compressed images handled by LibTiff and auto-converted to RGBX
# Minimal Baseline TIFF requires YCbCr images to have 3 SamplesPerPixel # Minimal Baseline TIFF requires YCbCr images to have 3 SamplesPerPixel
(II, 6, (1,), 1, (8, 8, 8), ()): ("RGB", "RGB"), (II, 6, (1,), 1, (8, 8, 8), ()): ("RGB", "RGBX"),
(MM, 6, (1,), 1, (8, 8, 8), ()): ("RGB", "RGB"), (MM, 6, (1,), 1, (8, 8, 8), ()): ("RGB", "RGBX"),
(II, 8, (1,), 1, (8, 8, 8), ()): ("LAB", "LAB"), (II, 8, (1,), 1, (8, 8, 8), ()): ("LAB", "LAB"),
(MM, 8, (1,), 1, (8, 8, 8), ()): ("LAB", "LAB"), (MM, 8, (1,), 1, (8, 8, 8), ()): ("LAB", "LAB"),
@ -819,7 +819,7 @@ class ImageFileDirectory_v2(MutableMapping):
print("- value:", values) print("- value:", values)
# count is sum of lengths for string and arbitrary data # count is sum of lengths for string and arbitrary data
if typ in [TiffTags.ASCII, TiffTags.UNDEFINED]: if typ in [TiffTags.BYTE, TiffTags.ASCII, TiffTags.UNDEFINED]:
count = len(data) count = len(data)
else: else:
count = len(values) count = len(values)
@ -1191,6 +1191,10 @@ class TiffImageFile(ImageFile.ImageFile):
# the specification # the specification
photo = self.tag_v2.get(PHOTOMETRIC_INTERPRETATION, 0) photo = self.tag_v2.get(PHOTOMETRIC_INTERPRETATION, 0)
# old style jpeg compression images most certainly are YCbCr
if self._compression == "tiff_jpeg":
photo = 6
fillorder = self.tag_v2.get(FILLORDER, 1) fillorder = self.tag_v2.get(FILLORDER, 1)
if DEBUG: if DEBUG:
@ -1439,7 +1443,7 @@ def _save(im, fp, filename):
try: try:
ifd.tagtype[key] = info.tagtype[key] ifd.tagtype[key] = info.tagtype[key]
except Exception: except Exception:
pass # might not be an IFD, Might not have populated type pass # might not be an IFD. Might not have populated type
# additions written by Greg Couch, gregc@cgl.ucsf.edu # additions written by Greg Couch, gregc@cgl.ucsf.edu
# inspired by image-sig posting from Kevin Cazabon, kcazabon@home.com # inspired by image-sig posting from Kevin Cazabon, kcazabon@home.com
@ -1683,7 +1687,7 @@ class AppendingTiffWriter:
def tell(self): def tell(self):
return self.f.tell() - self.offsetOfNewPage return self.f.tell() - self.offsetOfNewPage
def seek(self, offset, whence): def seek(self, offset, whence=io.SEEK_SET):
if whence == os.SEEK_SET: if whence == os.SEEK_SET:
offset += self.offsetOfNewPage offset += self.offsetOfNewPage

View File

@ -186,7 +186,7 @@ def _save_all(im, fp, filename):
# will preserve non-alpha modes # will preserve non-alpha modes
total = 0 total = 0
for ims in [im]+append_images: for ims in [im]+append_images:
total += 1 if not hasattr(ims, "n_frames") else ims.n_frames total += getattr(ims, "n_frames", 1)
if total == 1: if total == 1:
_save(im, fp, filename) _save(im, fp, filename)
return return
@ -254,10 +254,7 @@ def _save_all(im, fp, filename):
try: try:
for ims in [im]+append_images: for ims in [im]+append_images:
# Get # of frames in this image # Get # of frames in this image
if not hasattr(ims, "n_frames"): nfr = getattr(ims, "n_frames", 1)
nfr = 1
else:
nfr = ims.n_frames
for idx in range(nfr): for idx in range(nfr):
ims.seek(idx) ims.seek(idx)

View File

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

View File

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

View File

@ -25,6 +25,7 @@ kevin@cazabon.com\n\
http://www.cazabon.com\n\ http://www.cazabon.com\n\
" "
#define PY_SSIZE_T_CLEAN
#include "Python.h" // Include before wchar.h so _GNU_SOURCE is set #include "Python.h" // Include before wchar.h so _GNU_SOURCE is set
#include "wchar.h" #include "wchar.h"
#include "datetime.h" #include "datetime.h"
@ -120,7 +121,7 @@ cms_profile_fromstring(PyObject* self, PyObject* args)
cmsHPROFILE hProfile; cmsHPROFILE hProfile;
char* pProfile; char* pProfile;
int nProfile; Py_ssize_t nProfile;
#if PY_VERSION_HEX >= 0x03000000 #if PY_VERSION_HEX >= 0x03000000
if (!PyArg_ParseTuple(args, "y#:profile_frombytes", &pProfile, &nProfile)) if (!PyArg_ParseTuple(args, "y#:profile_frombytes", &pProfile, &nProfile))
return NULL; return NULL;

View File

@ -18,6 +18,7 @@
* Copyright (c) 1998-2007 by Secret Labs AB * Copyright (c) 1998-2007 by Secret Labs AB
*/ */
#define PY_SSIZE_T_CLEAN
#include "Python.h" #include "Python.h"
#include "Imaging.h" #include "Imaging.h"
@ -87,6 +88,10 @@ typedef bool (*t_raqm_set_text_utf8) (raqm_t *rq,
size_t len); size_t len);
typedef bool (*t_raqm_set_par_direction) (raqm_t *rq, typedef bool (*t_raqm_set_par_direction) (raqm_t *rq,
raqm_direction_t dir); raqm_direction_t dir);
typedef bool (*t_raqm_set_language) (raqm_t *rq,
const char *lang,
size_t start,
size_t len);
typedef bool (*t_raqm_add_font_feature) (raqm_t *rq, typedef bool (*t_raqm_add_font_feature) (raqm_t *rq,
const char *feature, const char *feature,
int len); int len);
@ -106,6 +111,7 @@ typedef struct {
t_raqm_set_text set_text; t_raqm_set_text set_text;
t_raqm_set_text_utf8 set_text_utf8; t_raqm_set_text_utf8 set_text_utf8;
t_raqm_set_par_direction set_par_direction; t_raqm_set_par_direction set_par_direction;
t_raqm_set_language set_language;
t_raqm_add_font_feature add_font_feature; t_raqm_add_font_feature add_font_feature;
t_raqm_set_freetype_face set_freetype_face; t_raqm_set_freetype_face set_freetype_face;
t_raqm_layout layout; t_raqm_layout layout;
@ -160,6 +166,7 @@ setraqm(void)
p_raqm.set_text = (t_raqm_set_text)dlsym(p_raqm.raqm, "raqm_set_text"); p_raqm.set_text = (t_raqm_set_text)dlsym(p_raqm.raqm, "raqm_set_text");
p_raqm.set_text_utf8 = (t_raqm_set_text_utf8)dlsym(p_raqm.raqm, "raqm_set_text_utf8"); p_raqm.set_text_utf8 = (t_raqm_set_text_utf8)dlsym(p_raqm.raqm, "raqm_set_text_utf8");
p_raqm.set_par_direction = (t_raqm_set_par_direction)dlsym(p_raqm.raqm, "raqm_set_par_direction"); p_raqm.set_par_direction = (t_raqm_set_par_direction)dlsym(p_raqm.raqm, "raqm_set_par_direction");
p_raqm.set_language = (t_raqm_set_language)dlsym(p_raqm.raqm, "raqm_set_language");
p_raqm.add_font_feature = (t_raqm_add_font_feature)dlsym(p_raqm.raqm, "raqm_add_font_feature"); p_raqm.add_font_feature = (t_raqm_add_font_feature)dlsym(p_raqm.raqm, "raqm_add_font_feature");
p_raqm.set_freetype_face = (t_raqm_set_freetype_face)dlsym(p_raqm.raqm, "raqm_set_freetype_face"); p_raqm.set_freetype_face = (t_raqm_set_freetype_face)dlsym(p_raqm.raqm, "raqm_set_freetype_face");
p_raqm.layout = (t_raqm_layout)dlsym(p_raqm.raqm, "raqm_layout"); p_raqm.layout = (t_raqm_layout)dlsym(p_raqm.raqm, "raqm_layout");
@ -176,6 +183,7 @@ setraqm(void)
p_raqm.set_text && p_raqm.set_text &&
p_raqm.set_text_utf8 && p_raqm.set_text_utf8 &&
p_raqm.set_par_direction && p_raqm.set_par_direction &&
p_raqm.set_language &&
p_raqm.add_font_feature && p_raqm.add_font_feature &&
p_raqm.set_freetype_face && p_raqm.set_freetype_face &&
p_raqm.layout && p_raqm.layout &&
@ -190,6 +198,7 @@ setraqm(void)
p_raqm.set_text = (t_raqm_set_text)GetProcAddress(p_raqm.raqm, "raqm_set_text"); p_raqm.set_text = (t_raqm_set_text)GetProcAddress(p_raqm.raqm, "raqm_set_text");
p_raqm.set_text_utf8 = (t_raqm_set_text_utf8)GetProcAddress(p_raqm.raqm, "raqm_set_text_utf8"); p_raqm.set_text_utf8 = (t_raqm_set_text_utf8)GetProcAddress(p_raqm.raqm, "raqm_set_text_utf8");
p_raqm.set_par_direction = (t_raqm_set_par_direction)GetProcAddress(p_raqm.raqm, "raqm_set_par_direction"); p_raqm.set_par_direction = (t_raqm_set_par_direction)GetProcAddress(p_raqm.raqm, "raqm_set_par_direction");
p_raqm.set_language = (t_raqm_set_language)GetProcAddress(p_raqm.raqm, "raqm_set_language");
p_raqm.add_font_feature = (t_raqm_add_font_feature)GetProcAddress(p_raqm.raqm, "raqm_add_font_feature"); p_raqm.add_font_feature = (t_raqm_add_font_feature)GetProcAddress(p_raqm.raqm, "raqm_add_font_feature");
p_raqm.set_freetype_face = (t_raqm_set_freetype_face)GetProcAddress(p_raqm.raqm, "raqm_set_freetype_face"); p_raqm.set_freetype_face = (t_raqm_set_freetype_face)GetProcAddress(p_raqm.raqm, "raqm_set_freetype_face");
p_raqm.layout = (t_raqm_layout)GetProcAddress(p_raqm.raqm, "raqm_layout"); p_raqm.layout = (t_raqm_layout)GetProcAddress(p_raqm.raqm, "raqm_layout");
@ -205,6 +214,7 @@ setraqm(void)
p_raqm.set_text && p_raqm.set_text &&
p_raqm.set_text_utf8 && p_raqm.set_text_utf8 &&
p_raqm.set_par_direction && p_raqm.set_par_direction &&
p_raqm.set_language &&
p_raqm.add_font_feature && p_raqm.add_font_feature &&
p_raqm.set_freetype_face && p_raqm.set_freetype_face &&
p_raqm.layout && p_raqm.layout &&
@ -228,12 +238,12 @@ getfont(PyObject* self_, PyObject* args, PyObject* kw)
int error = 0; int error = 0;
char* filename = NULL; char* filename = NULL;
int size; Py_ssize_t size;
int index = 0; Py_ssize_t index = 0;
int layout_engine = 0; Py_ssize_t layout_engine = 0;
unsigned char* encoding; unsigned char* encoding;
unsigned char* font_bytes; unsigned char* font_bytes;
int font_bytes_size = 0; Py_ssize_t font_bytes_size = 0;
static char* kwlist[] = { static char* kwlist[] = {
"filename", "size", "index", "encoding", "font_bytes", "filename", "size", "index", "encoding", "font_bytes",
"layout_engine", NULL "layout_engine", NULL
@ -332,8 +342,8 @@ font_getchar(PyObject* string, int index, FT_ULong* char_out)
} }
static size_t static size_t
text_layout_raqm(PyObject* string, FontObject* self, const char* dir, text_layout_raqm(PyObject* string, FontObject* self, const char* dir, PyObject *features,
PyObject *features ,GlyphInfo **glyph_info, int mask) const char* lang, GlyphInfo **glyph_info, int mask)
{ {
int i = 0; int i = 0;
raqm_t *rq; raqm_t *rq;
@ -341,6 +351,7 @@ text_layout_raqm(PyObject* string, FontObject* self, const char* dir,
raqm_glyph_t *glyphs = NULL; raqm_glyph_t *glyphs = NULL;
raqm_glyph_t_01 *glyphs_01 = NULL; raqm_glyph_t_01 *glyphs_01 = NULL;
raqm_direction_t direction; raqm_direction_t direction;
size_t start = 0;
rq = (*p_raqm.create)(); rq = (*p_raqm.create)();
if (rq == NULL) { if (rq == NULL) {
@ -360,6 +371,13 @@ text_layout_raqm(PyObject* string, FontObject* self, const char* dir,
PyErr_SetString(PyExc_ValueError, "raqm_set_text() failed"); PyErr_SetString(PyExc_ValueError, "raqm_set_text() failed");
goto failed; goto failed;
} }
if (lang) {
if (!(*p_raqm.set_language)(rq, lang, start, size)) {
PyErr_SetString(PyExc_ValueError, "raqm_set_language() failed");
goto failed;
}
}
} }
#if PY_VERSION_HEX < 0x03000000 #if PY_VERSION_HEX < 0x03000000
else if (PyString_Check(string)) { else if (PyString_Check(string)) {
@ -372,6 +390,12 @@ text_layout_raqm(PyObject* string, FontObject* self, const char* dir,
PyErr_SetString(PyExc_ValueError, "raqm_set_text_utf8() failed"); PyErr_SetString(PyExc_ValueError, "raqm_set_text_utf8() failed");
goto failed; goto failed;
} }
if (lang) {
if (!(*p_raqm.set_language)(rq, lang, start, size)) {
PyErr_SetString(PyExc_ValueError, "raqm_set_language() failed");
goto failed;
}
}
} }
#endif #endif
else { else {
@ -498,8 +522,8 @@ failed:
} }
static size_t static size_t
text_layout_fallback(PyObject* string, FontObject* self, const char* dir, text_layout_fallback(PyObject* string, FontObject* self, const char* dir, PyObject *features,
PyObject *features ,GlyphInfo **glyph_info, int mask) const char* lang, GlyphInfo **glyph_info, int mask)
{ {
int error, load_flags; int error, load_flags;
FT_ULong ch; FT_ULong ch;
@ -509,8 +533,8 @@ text_layout_fallback(PyObject* string, FontObject* self, const char* dir,
FT_UInt last_index = 0; FT_UInt last_index = 0;
int i; int i;
if (features != Py_None || dir != NULL) { if (features != Py_None || dir != NULL || lang != NULL) {
PyErr_SetString(PyExc_KeyError, "setting text direction or font features is not supported without libraqm"); PyErr_SetString(PyExc_KeyError, "setting text direction, language or font features is not supported without libraqm");
} }
#if PY_VERSION_HEX >= 0x03000000 #if PY_VERSION_HEX >= 0x03000000
if (!PyUnicode_Check(string)) { if (!PyUnicode_Check(string)) {
@ -564,15 +588,15 @@ text_layout_fallback(PyObject* string, FontObject* self, const char* dir,
} }
static size_t static size_t
text_layout(PyObject* string, FontObject* self, const char* dir, text_layout(PyObject* string, FontObject* self, const char* dir, PyObject *features,
PyObject *features, GlyphInfo **glyph_info, int mask) const char* lang, GlyphInfo **glyph_info, int mask)
{ {
size_t count; size_t count;
if (p_raqm.raqm && self->layout_engine == LAYOUT_RAQM) { if (p_raqm.raqm && self->layout_engine == LAYOUT_RAQM) {
count = text_layout_raqm(string, self, dir, features, glyph_info, mask); count = text_layout_raqm(string, self, dir, features, lang, glyph_info, mask);
} else { } else {
count = text_layout_fallback(string, self, dir, features, glyph_info, mask); count = text_layout_fallback(string, self, dir, features, lang, glyph_info, mask);
} }
return count; return count;
} }
@ -584,6 +608,7 @@ font_getsize(FontObject* self, PyObject* args)
FT_Face face; FT_Face face;
int xoffset, yoffset; int xoffset, yoffset;
const char *dir = NULL; const char *dir = NULL;
const char *lang = NULL;
size_t count; size_t count;
GlyphInfo *glyph_info = NULL; GlyphInfo *glyph_info = NULL;
PyObject *features = Py_None; PyObject *features = Py_None;
@ -591,14 +616,14 @@ font_getsize(FontObject* self, PyObject* args)
/* calculate size and bearing for a given string */ /* calculate size and bearing for a given string */
PyObject* string; PyObject* string;
if (!PyArg_ParseTuple(args, "O|zO:getsize", &string, &dir, &features)) if (!PyArg_ParseTuple(args, "O|zOz:getsize", &string, &dir, &features, &lang))
return NULL; return NULL;
face = NULL; face = NULL;
xoffset = yoffset = 0; xoffset = yoffset = 0;
y_max = y_min = 0; y_max = y_min = 0;
count = text_layout(string, self, dir, features, &glyph_info, 0); count = text_layout(string, self, dir, features, lang, &glyph_info, 0);
if (PyErr_Occurred()) { if (PyErr_Occurred()) {
return NULL; return NULL;
} }
@ -691,16 +716,17 @@ font_render(FontObject* self, PyObject* args)
int temp; int temp;
int xx, x0, x1; int xx, x0, x1;
const char *dir = NULL; const char *dir = NULL;
const char *lang = NULL;
size_t count; size_t count;
GlyphInfo *glyph_info; GlyphInfo *glyph_info;
PyObject *features = NULL; PyObject *features = NULL;
if (!PyArg_ParseTuple(args, "On|izO:render", &string, &id, &mask, &dir, &features)) { if (!PyArg_ParseTuple(args, "On|izOz:render", &string, &id, &mask, &dir, &features, &lang)) {
return NULL; return NULL;
} }
glyph_info = NULL; glyph_info = NULL;
count = text_layout(string, self, dir, features, &glyph_info, mask); count = text_layout(string, self, dir, features, lang, &glyph_info, mask);
if (PyErr_Occurred()) { if (PyErr_Occurred()) {
return NULL; return NULL;
} }

View File

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

View File

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

View File

@ -592,6 +592,22 @@ i2f(UINT8* out_, const UINT8* in_, int xsize)
*out++ = (FLOAT32) *in++; *out++ = (FLOAT32) *in++;
} }
static void
i2rgb(UINT8* out, const UINT8* in_, int xsize)
{
int x;
INT32* in = (INT32*) in_;
for (x = 0; x < xsize; x++, in++, out+=4) {
if (*in <= 0)
out[0] = out[1] = out[2] = 0;
else if (*in >= 255)
out[0] = out[1] = out[2] = 255;
else
out[0] = out[1] = out[2] = (UINT8) *in;
out[3] = 255;
}
}
/* ------------- */ /* ------------- */
/* F conversions */ /* F conversions */
/* ------------- */ /* ------------- */
@ -807,11 +823,14 @@ static struct {
{ "La", "LA", la2lA }, { "La", "LA", la2lA },
{ "I", "L", i2l }, { "I", "L", i2l },
{ "I", "F", i2f }, { "I", "F", i2f },
{ "I", "RGB", i2rgb },
{ "I", "RGBA", i2rgb },
{ "I", "RGBX", i2rgb },
{ "F", "L", f2l }, { "F", "L", f2l },
{ "F", "I", f2i }, { "F", "I", f2i },
{ "RGB", "1", rgb2bit }, { "RGB", "1", rgb2bit },
{ "RGB", "L", rgb2l }, { "RGB", "L", rgb2l },
@ -1385,6 +1404,8 @@ ImagingConvertTransparent(Imaging imIn, const char *mode,
} }
if (!((strcmp(imIn->mode, "RGB") == 0 || if (!((strcmp(imIn->mode, "RGB") == 0 ||
strcmp(imIn->mode, "1") == 0 ||
strcmp(imIn->mode, "I") == 0 ||
strcmp(imIn->mode, "L") == 0) strcmp(imIn->mode, "L") == 0)
&& strcmp(mode, "RGBA") == 0)) && strcmp(mode, "RGBA") == 0))
#ifdef notdef #ifdef notdef
@ -1403,7 +1424,13 @@ ImagingConvertTransparent(Imaging imIn, const char *mode,
if (strcmp(imIn->mode, "RGB") == 0) { if (strcmp(imIn->mode, "RGB") == 0) {
convert = rgb2rgba; convert = rgb2rgba;
} else { } else {
convert = l2rgb; if (strcmp(imIn->mode, "1") == 0) {
convert = bit2rgb;
} else if (strcmp(imIn->mode, "I") == 0) {
convert = i2rgb;
} else {
convert = l2rgb;
}
g = b = r; g = b = r;
} }

View File

@ -39,7 +39,11 @@ ImagingFlipLeftRight(Imaging imOut, Imaging imIn)
ImagingSectionEnter(&cookie); ImagingSectionEnter(&cookie);
if (imIn->image8) { if (imIn->image8) {
FLIP_LEFT_RIGHT(UINT8, image8) if (strncmp(imIn->mode, "I;16", 4) == 0) {
FLIP_LEFT_RIGHT(UINT16, image8)
} else {
FLIP_LEFT_RIGHT(UINT8, image8)
}
} else { } else {
FLIP_LEFT_RIGHT(INT32, image32) FLIP_LEFT_RIGHT(INT32, image32)
} }
@ -104,7 +108,8 @@ ImagingRotate90(Imaging imOut, Imaging imIn)
INT* in = imIn->image[yyy]; \ INT* in = imIn->image[yyy]; \
xr = imIn->xsize - 1 - xx; \ xr = imIn->xsize - 1 - xx; \
for (xxx = xx; xxx < xxxsize; xxx++, xr--) { \ for (xxx = xx; xxx < xxxsize; xxx++, xr--) { \
imOut->image[xr][yyy] = in[xxx]; \ INT* out = imOut->image[xr]; \
out[yyy] = in[xxx]; \
} \ } \
} \ } \
} \ } \
@ -114,10 +119,15 @@ ImagingRotate90(Imaging imOut, Imaging imIn)
ImagingSectionEnter(&cookie); ImagingSectionEnter(&cookie);
if (imIn->image8) if (imIn->image8) {
ROTATE_90(UINT8, image8) if (strncmp(imIn->mode, "I;16", 4) == 0) {
else ROTATE_90(UINT16, image8);
ROTATE_90(INT32, image32) } else {
ROTATE_90(UINT8, image8);
}
} else {
ROTATE_90(INT32, image32);
}
ImagingSectionLeave(&cookie); ImagingSectionLeave(&cookie);
@ -153,7 +163,8 @@ ImagingTranspose(Imaging imOut, Imaging imIn)
for (yyy = yy; yyy < yyysize; yyy++) { \ for (yyy = yy; yyy < yyysize; yyy++) { \
INT* in = imIn->image[yyy]; \ INT* in = imIn->image[yyy]; \
for (xxx = xx; xxx < xxxsize; xxx++) { \ for (xxx = xx; xxx < xxxsize; xxx++) { \
imOut->image[xxx][yyy] = in[xxx]; \ INT* out = imOut->image[xxx]; \
out[yyy] = in[xxx]; \
} \ } \
} \ } \
} \ } \
@ -163,10 +174,15 @@ ImagingTranspose(Imaging imOut, Imaging imIn)
ImagingSectionEnter(&cookie); ImagingSectionEnter(&cookie);
if (imIn->image8) if (imIn->image8) {
TRANSPOSE(UINT8, image8) if (strncmp(imIn->mode, "I;16", 4) == 0) {
else TRANSPOSE(UINT16, image8);
TRANSPOSE(INT32, image32) } else {
TRANSPOSE(UINT8, image8);
}
} else {
TRANSPOSE(INT32, image32);
}
ImagingSectionLeave(&cookie); ImagingSectionLeave(&cookie);
@ -204,7 +220,8 @@ ImagingTransverse(Imaging imOut, Imaging imIn)
INT* in = imIn->image[yyy]; \ INT* in = imIn->image[yyy]; \
xr = imIn->xsize - 1 - xx; \ xr = imIn->xsize - 1 - xx; \
for (xxx = xx; xxx < xxxsize; xxx++, xr--) { \ for (xxx = xx; xxx < xxxsize; xxx++, xr--) { \
imOut->image[xr][yr] = in[xxx]; \ INT* out = imOut->image[xr]; \
out[yr] = in[xxx]; \
} \ } \
} \ } \
} \ } \
@ -214,10 +231,15 @@ ImagingTransverse(Imaging imOut, Imaging imIn)
ImagingSectionEnter(&cookie); ImagingSectionEnter(&cookie);
if (imIn->image8) if (imIn->image8) {
TRANSVERSE(UINT8, image8) if (strncmp(imIn->mode, "I;16", 4) == 0) {
else TRANSVERSE(UINT16, image8);
TRANSVERSE(INT32, image32) } else {
TRANSVERSE(UINT8, image8);
}
} else {
TRANSVERSE(INT32, image32);
}
ImagingSectionLeave(&cookie); ImagingSectionLeave(&cookie);
@ -253,7 +275,11 @@ ImagingRotate180(Imaging imOut, Imaging imIn)
yr = imIn->ysize-1; yr = imIn->ysize-1;
if (imIn->image8) { if (imIn->image8) {
ROTATE_180(UINT8, image8) if (strncmp(imIn->mode, "I;16", 4) == 0) {
ROTATE_180(UINT16, image8)
} else {
ROTATE_180(UINT8, image8)
}
} else { } else {
ROTATE_180(INT32, image32) ROTATE_180(INT32, image32)
} }
@ -293,7 +319,8 @@ ImagingRotate270(Imaging imOut, Imaging imIn)
for (yyy = yy; yyy < yyysize; yyy++, yr--) { \ for (yyy = yy; yyy < yyysize; yyy++, yr--) { \
INT* in = imIn->image[yyy]; \ INT* in = imIn->image[yyy]; \
for (xxx = xx; xxx < xxxsize; xxx++) { \ for (xxx = xx; xxx < xxxsize; xxx++) { \
imOut->image[xxx][yr] = in[xxx]; \ INT* out = imOut->image[xxx]; \
out[yr] = in[xxx]; \
} \ } \
} \ } \
} \ } \
@ -303,10 +330,15 @@ ImagingRotate270(Imaging imOut, Imaging imIn)
ImagingSectionEnter(&cookie); ImagingSectionEnter(&cookie);
if (imIn->image8) if (imIn->image8) {
ROTATE_270(UINT8, image8) if (strncmp(imIn->mode, "I;16", 4) == 0) {
else ROTATE_270(UINT16, image8);
ROTATE_270(INT32, image32) } else {
ROTATE_270(UINT8, image8);
}
} else {
ROTATE_270(INT32, image32);
}
ImagingSectionLeave(&cookie); ImagingSectionLeave(&cookie);

View File

@ -639,6 +639,7 @@ static struct {
/* storage modes */ /* storage modes */
{"I;16", "I;16", 16, copy2}, {"I;16", "I;16", 16, copy2},
{"I;16", "I;16B", 16, packI16N_I16B},
{"I;16B", "I;16B", 16, copy2}, {"I;16B", "I;16B", 16, copy2},
{"I;16L", "I;16L", 16, copy2}, {"I;16L", "I;16L", 16, copy2},
{"I;16", "I;16N", 16, packI16N_I16}, // LibTiff native->image endian. {"I;16", "I;16N", 16, packI16N_I16}, // LibTiff native->image endian.

View File

@ -35,8 +35,6 @@ struct _HashTable {
uint32_t count; uint32_t count;
HashFunc hashFunc; HashFunc hashFunc;
HashCmpFunc cmpFunc; HashCmpFunc cmpFunc;
KeyDestroyFunc keyDestroyFunc;
ValDestroyFunc valDestroyFunc;
void *userData; void *userData;
}; };
@ -51,8 +49,6 @@ HashTable *hashtable_new(HashFunc hf,HashCmpFunc cf) {
if (!h) { return NULL; } if (!h) { return NULL; }
h->hashFunc=hf; h->hashFunc=hf;
h->cmpFunc=cf; h->cmpFunc=cf;
h->keyDestroyFunc=NULL;
h->valDestroyFunc=NULL;
h->length=MIN_LENGTH; h->length=MIN_LENGTH;
h->count=0; h->count=0;
h->userData=NULL; h->userData=NULL;
@ -62,15 +58,6 @@ HashTable *hashtable_new(HashFunc hf,HashCmpFunc cf) {
return h; return h;
} }
static void _hashtable_destroy(const HashTable *h,const HashKey_t key,const HashVal_t val,void *u) {
if (h->keyDestroyFunc) {
h->keyDestroyFunc(h,key);
}
if (h->valDestroyFunc) {
h->valDestroyFunc(h,val);
}
}
static uint32_t _findPrime(uint32_t start,int dir) { static uint32_t _findPrime(uint32_t start,int dir) {
static int unit[]={0,1,0,1,0,0,0,1,0,1,0,1,0,1,0,0}; static int unit[]={0,1,0,1,0,0,0,1,0,1,0,1,0,1,0,0};
uint32_t t; uint32_t t;
@ -144,12 +131,6 @@ static int _hashtable_insert_node(HashTable *h,HashNode *node,int resize,int upd
free(node); free(node);
return 1; return 1;
} else { } else {
if (h->valDestroyFunc) {
h->valDestroyFunc(h,nv->value);
}
if (h->keyDestroyFunc) {
h->keyDestroyFunc(h,nv->key);
}
nv->key=node->key; nv->key=node->key;
nv->value=node->value; nv->value=node->value;
free(node); free(node);
@ -180,7 +161,6 @@ static int _hashtable_insert(HashTable *h,HashKey_t key,HashVal_t val,int resize
nv=*n; nv=*n;
i=h->cmpFunc(h,nv->key,key); i=h->cmpFunc(h,nv->key,key);
if (!i) { if (!i) {
if (h->valDestroyFunc) { h->valDestroyFunc(h,nv->value); }
nv->value=val; nv->value=val;
return 1; return 1;
} else if (i>0) { } else if (i>0) {
@ -202,34 +182,6 @@ static int _hashtable_insert(HashTable *h,HashKey_t key,HashVal_t val,int resize
} }
} }
static int _hashtable_lookup_or_insert(HashTable *h,HashKey_t key,HashVal_t *retVal,HashVal_t newVal,int resize) {
HashNode **n,*nv;
HashNode *t;
int i;
uint32_t hash=h->hashFunc(h,key)%h->length;
for (n=&(h->table[hash]);*n;n=&((*n)->next)) {
nv=*n;
i=h->cmpFunc(h,nv->key,key);
if (!i) {
*retVal=nv->value;
return 1;
} else if (i>0) {
break;
}
}
t=malloc(sizeof(HashNode));
if (!t) return 0;
t->next=*n;
*n=t;
t->key=key;
t->value=newVal;
*retVal=newVal;
h->count++;
if (resize) _hashtable_resize(h);
return 1;
}
int hashtable_insert_or_update_computed(HashTable *h, int hashtable_insert_or_update_computed(HashTable *h,
HashKey_t key, HashKey_t key,
ComputeFunc newFunc, ComputeFunc newFunc,
@ -243,14 +195,8 @@ int hashtable_insert_or_update_computed(HashTable *h,
nv=*n; nv=*n;
i=h->cmpFunc(h,nv->key,key); i=h->cmpFunc(h,nv->key,key);
if (!i) { if (!i) {
HashVal_t old=nv->value;
if (existsFunc) { if (existsFunc) {
existsFunc(h,nv->key,&(nv->value)); existsFunc(h,nv->key,&(nv->value));
if (nv->value!=old) {
if (h->valDestroyFunc) {
h->valDestroyFunc(h,old);
}
}
} else { } else {
return 0; return 0;
} }
@ -275,10 +221,6 @@ int hashtable_insert_or_update_computed(HashTable *h,
return 1; return 1;
} }
int hashtable_update(HashTable *h,HashKey_t key,HashVal_t val) {
return _hashtable_insert(h,key,val,1,0);
}
int hashtable_insert(HashTable *h,HashKey_t key,HashVal_t val) { int hashtable_insert(HashTable *h,HashKey_t key,HashVal_t val) {
return _hashtable_insert(h,key,val,1,0); return _hashtable_insert(h,key,val,1,0);
} }
@ -314,9 +256,6 @@ void hashtable_free(HashTable *h) {
uint32_t i; uint32_t i;
if (h->table) { if (h->table) {
if (h->keyDestroyFunc || h->keyDestroyFunc) {
hashtable_foreach(h,_hashtable_destroy,NULL);
}
for (i=0;i<h->length;i++) { for (i=0;i<h->length;i++) {
for (n=h->table[i];n;n=nn) { for (n=h->table[i];n;n=nn) {
nn=n->next; nn=n->next;
@ -328,84 +267,10 @@ void hashtable_free(HashTable *h) {
free(h); free(h);
} }
ValDestroyFunc hashtable_set_value_destroy_func(HashTable *h,ValDestroyFunc d) {
ValDestroyFunc r=h->valDestroyFunc;
h->valDestroyFunc=d;
return r;
}
KeyDestroyFunc hashtable_set_key_destroy_func(HashTable *h,KeyDestroyFunc d) {
KeyDestroyFunc r=h->keyDestroyFunc;
h->keyDestroyFunc=d;
return r;
}
static int _hashtable_remove(HashTable *h,
const HashKey_t key,
HashKey_t *keyRet,
HashVal_t *valRet,
int resize) {
uint32_t hash=h->hashFunc(h,key)%h->length;
HashNode *n,*p;
int i;
for (p=NULL,n=h->table[hash];n;p=n,n=n->next) {
i=h->cmpFunc(h,n->key,key);
if (!i) {
if (p) p=n->next; else h->table[hash]=n->next;
*keyRet=n->key;
*valRet=n->value;
free(n);
h->count++;
return 1;
} else if (i>0) {
break;
}
}
return 0;
}
static int _hashtable_delete(HashTable *h,const HashKey_t key,int resize) {
uint32_t hash=h->hashFunc(h,key)%h->length;
HashNode *n,*p;
int i;
for (p=NULL,n=h->table[hash];n;p=n,n=n->next) {
i=h->cmpFunc(h,n->key,key);
if (!i) {
if (p) p=n->next; else h->table[hash]=n->next;
if (h->valDestroyFunc) { h->valDestroyFunc(h,n->value); }
if (h->keyDestroyFunc) { h->keyDestroyFunc(h,n->key); }
free(n);
h->count++;
return 1;
} else if (i>0) {
break;
}
}
return 0;
}
int hashtable_remove(HashTable *h,const HashKey_t key,HashKey_t *keyRet,HashVal_t *valRet) {
return _hashtable_remove(h,key,keyRet,valRet,1);
}
int hashtable_delete(HashTable *h,const HashKey_t key) {
return _hashtable_delete(h,key,1);
}
void hashtable_rehash_compute(HashTable *h,CollisionFunc cf) { void hashtable_rehash_compute(HashTable *h,CollisionFunc cf) {
_hashtable_rehash(h,cf,h->length); _hashtable_rehash(h,cf,h->length);
} }
void hashtable_rehash(HashTable *h) {
_hashtable_rehash(h,NULL,h->length);
}
int hashtable_lookup_or_insert(HashTable *h,HashKey_t key,HashVal_t *valp,HashVal_t val) {
return _hashtable_lookup_or_insert(h,key,valp,val,1);
}
int hashtable_lookup(const HashTable *h,const HashKey_t key,HashVal_t *valp) { int hashtable_lookup(const HashTable *h,const HashKey_t key,HashVal_t *valp) {
uint32_t hash=h->hashFunc(h,key)%h->length; uint32_t hash=h->hashFunc(h,key)%h->length;
HashNode *n; HashNode *n;

View File

@ -22,8 +22,6 @@ typedef uint32_t (*HashFunc)(const HashTable *,const HashKey_t);
typedef int (*HashCmpFunc)(const HashTable *,const HashKey_t,const HashKey_t); typedef int (*HashCmpFunc)(const HashTable *,const HashKey_t,const HashKey_t);
typedef void (*IteratorFunc)(const HashTable *,const HashKey_t,const HashVal_t,void *); typedef void (*IteratorFunc)(const HashTable *,const HashKey_t,const HashVal_t,void *);
typedef void (*IteratorUpdateFunc)(const HashTable *,const HashKey_t,HashVal_t *,void *); typedef void (*IteratorUpdateFunc)(const HashTable *,const HashKey_t,HashVal_t *,void *);
typedef void (*KeyDestroyFunc)(const HashTable *,HashKey_t);
typedef void (*ValDestroyFunc)(const HashTable *,HashVal_t);
typedef void (*ComputeFunc)(const HashTable *,const HashKey_t,HashVal_t *); typedef void (*ComputeFunc)(const HashTable *,const HashKey_t,HashVal_t *);
typedef void (*CollisionFunc)(const HashTable *,HashKey_t *,HashVal_t *,HashKey_t,HashVal_t); typedef void (*CollisionFunc)(const HashTable *,HashKey_t *,HashVal_t *,HashKey_t,HashVal_t);
@ -32,18 +30,11 @@ void hashtable_free(HashTable *h);
void hashtable_foreach(HashTable *h,IteratorFunc i,void *u); void hashtable_foreach(HashTable *h,IteratorFunc i,void *u);
void hashtable_foreach_update(HashTable *h,IteratorUpdateFunc i,void *u); void hashtable_foreach_update(HashTable *h,IteratorUpdateFunc i,void *u);
int hashtable_insert(HashTable *h,HashKey_t key,HashVal_t val); int hashtable_insert(HashTable *h,HashKey_t key,HashVal_t val);
int hashtable_update(HashTable *h,HashKey_t key,HashVal_t val);
int hashtable_lookup(const HashTable *h,const HashKey_t key,HashVal_t *valp); int hashtable_lookup(const HashTable *h,const HashKey_t key,HashVal_t *valp);
int hashtable_lookup_or_insert(HashTable *h,HashKey_t key,HashVal_t *valp,HashVal_t val);
int hashtable_insert_or_update_computed(HashTable *h,HashKey_t key,ComputeFunc newFunc,ComputeFunc existsFunc); int hashtable_insert_or_update_computed(HashTable *h,HashKey_t key,ComputeFunc newFunc,ComputeFunc existsFunc);
int hashtable_delete(HashTable *h,const HashKey_t key);
int hashtable_remove(HashTable *h,const HashKey_t key,HashKey_t *keyRet,HashVal_t *valRet);
void *hashtable_set_user_data(HashTable *h,void *data); void *hashtable_set_user_data(HashTable *h,void *data);
void *hashtable_get_user_data(const HashTable *h); void *hashtable_get_user_data(const HashTable *h);
KeyDestroyFunc hashtable_set_key_destroy_func(HashTable *,KeyDestroyFunc d);
ValDestroyFunc hashtable_set_value_destroy_func(HashTable *,ValDestroyFunc d);
uint32_t hashtable_get_count(const HashTable *h); uint32_t hashtable_get_count(const HashTable *h);
void hashtable_rehash(HashTable *h);
void hashtable_rehash_compute(HashTable *h,CollisionFunc cf); void hashtable_rehash_compute(HashTable *h,CollisionFunc cf);
#endif // __QUANTHASH_H__ #endif // __QUANTHASH_H__

View File

@ -124,6 +124,7 @@ toff_t _tiffSizeProc(thandle_t hdata) {
return (toff_t)state->size; return (toff_t)state->size;
} }
int _tiffMapProc(thandle_t hdata, tdata_t* pbase, toff_t* psize) { int _tiffMapProc(thandle_t hdata, tdata_t* pbase, toff_t* psize) {
TIFFSTATE *state = (TIFFSTATE *)hdata; TIFFSTATE *state = (TIFFSTATE *)hdata;
@ -168,13 +169,117 @@ int ImagingLibTiffInit(ImagingCodecState state, int fp, int offset) {
return 1; return 1;
} }
int ReadTile(TIFF* tiff, UINT32 col, UINT32 row, UINT32* buffer) {
uint16 photometric;
TIFFGetField(tiff, TIFFTAG_PHOTOMETRIC, &photometric);
// To avoid dealing with YCbCr subsampling, let libtiff handle it
if (photometric == PHOTOMETRIC_YCBCR) {
UINT32 tile_width, tile_height, swap_line_size, i_row;
UINT32* swap_line;
TIFFGetField(tiff, TIFFTAG_TILEWIDTH, &tile_width);
TIFFGetField(tiff, TIFFTAG_TILELENGTH, &tile_height);
swap_line_size = tile_width * sizeof(UINT32);
if (tile_width != swap_line_size / sizeof(UINT32)) {
return -1;
}
/* Read the tile into an RGBA array */
if (!TIFFReadRGBATile(tiff, col, row, buffer)) {
return -1;
}
swap_line = (UINT32*)malloc(swap_line_size);
/*
* For some reason the TIFFReadRGBATile() function chooses the
* lower left corner as the origin. Vertically mirror scanlines.
*/
for(i_row = 0; i_row < tile_height / 2; i_row++) {
UINT32 *top_line, *bottom_line;
top_line = buffer + tile_width * i_row;
bottom_line = buffer + tile_width * (tile_height - i_row - 1);
memcpy(swap_line, top_line, 4*tile_width);
memcpy(top_line, bottom_line, 4*tile_width);
memcpy(bottom_line, swap_line, 4*tile_width);
}
free(swap_line);
return 0;
}
if (TIFFReadTile(tiff, (tdata_t)buffer, col, row, 0, 0) == -1) {
TRACE(("Decode Error, Tile at %dx%d\n", col, row));
return -1;
}
TRACE(("Successfully read tile at %dx%d; \n\n", col, row));
return 0;
}
int ReadStrip(TIFF* tiff, UINT32 row, UINT32* buffer) {
uint16 photometric;
TIFFGetField(tiff, TIFFTAG_PHOTOMETRIC, &photometric);
// To avoid dealing with YCbCr subsampling, let libtiff handle it
if (photometric == PHOTOMETRIC_YCBCR) {
TIFFRGBAImage img;
char emsg[1024] = "";
UINT32 rows_per_strip, rows_to_read;
int ok;
TIFFGetFieldDefaulted(tiff, TIFFTAG_ROWSPERSTRIP, &rows_per_strip);
if ((row % rows_per_strip) != 0) {
TRACE(("Row passed to ReadStrip() must be first in a strip."));
return -1;
}
if (TIFFRGBAImageOK(tiff, emsg) && TIFFRGBAImageBegin(&img, tiff, 0, emsg)) {
TRACE(("Initialized RGBAImage\n"));
img.req_orientation = ORIENTATION_TOPLEFT;
img.row_offset = row;
img.col_offset = 0;
rows_to_read = min(rows_per_strip, img.height - row);
TRACE(("rows to read: %d\n", rows_to_read));
ok = TIFFRGBAImageGet(&img, buffer, img.width, rows_to_read);
TIFFRGBAImageEnd(&img);
} else {
ok = 0;
}
if (ok == 0) {
TRACE(("Decode Error, row %d; msg: %s\n", row, emsg));
return -1;
}
return 0;
}
if (TIFFReadEncodedStrip(tiff, TIFFComputeStrip(tiff, row, 0), (tdata_t)buffer, -1) == -1) {
TRACE(("Decode Error, strip %d\n", TIFFComputeStrip(tiff, row, 0)));
return -1;
}
return 0;
}
int ImagingLibTiffDecode(Imaging im, ImagingCodecState state, UINT8* buffer, int bytes) { int ImagingLibTiffDecode(Imaging im, ImagingCodecState state, UINT8* buffer, int bytes) {
TIFFSTATE *clientstate = (TIFFSTATE *)state->context; TIFFSTATE *clientstate = (TIFFSTATE *)state->context;
char *filename = "tempfile.tif"; char *filename = "tempfile.tif";
char *mode = "r"; char *mode = "r";
TIFF *tiff; TIFF *tiff;
uint16 photometric = 0, compression;
/* buffer is the encoded file, bytes is the length of the encoded file */ /* buffer is the encoded file, bytes is the length of the encoded file */
/* it all ends up in state->buffer, which is a uint8* from Imaging.h */ /* it all ends up in state->buffer, which is a uint8* from Imaging.h */
@ -235,19 +340,17 @@ int ImagingLibTiffDecode(Imaging im, ImagingCodecState state, UINT8* buffer, int
} }
} }
TIFFGetFieldDefaulted(tiff, TIFFTAG_COMPRESSION, &compression);
TIFFGetField(tiff, TIFFTAG_PHOTOMETRIC, &photometric);
if (compression == COMPRESSION_JPEG && photometric == PHOTOMETRIC_YCBCR) {
/* Set pseudo-tag to force automatic YCbCr->RGB conversion */
TIFFSetField(tiff, TIFFTAG_JPEGCOLORMODE, JPEGCOLORMODE_RGB);
}
if (TIFFIsTiled(tiff)) { if (TIFFIsTiled(tiff)) {
uint32 x, y, tile_y; UINT32 x, y, tile_y, row_byte_size;
uint32 tileWidth, tileLength; UINT32 tile_width, tile_length, current_tile_width;
UINT8 *new_data; UINT8 *new_data;
state->bytes = TIFFTileSize(tiff); TIFFGetField(tiff, TIFFTAG_TILEWIDTH, &tile_width);
TIFFGetField(tiff, TIFFTAG_TILELENGTH, &tile_length);
// We could use TIFFTileSize, but for YCbCr data it returns subsampled data size
row_byte_size = (tile_width * state->bits + 7) / 8;
state->bytes = row_byte_size * tile_length;
/* overflow check for malloc */ /* overflow check for malloc */
if (state->bytes > INT_MAX - 1) { if (state->bytes > INT_MAX - 1) {
@ -268,12 +371,9 @@ int ImagingLibTiffDecode(Imaging im, ImagingCodecState state, UINT8* buffer, int
TRACE(("TIFFTileSize: %d\n", state->bytes)); TRACE(("TIFFTileSize: %d\n", state->bytes));
TIFFGetField(tiff, TIFFTAG_TILEWIDTH, &tileWidth); for (y = state->yoff; y < state->ysize; y += tile_length) {
TIFFGetField(tiff, TIFFTAG_TILELENGTH, &tileLength); for (x = state->xoff; x < state->xsize; x += tile_width) {
if (ReadTile(tiff, x, y, (UINT32*) state->buffer) == -1) {
for (y = state->yoff; y < state->ysize; y += tileLength) {
for (x = state->xoff; x < state->xsize; x += tileWidth) {
if (TIFFReadTile(tiff, (tdata_t)state->buffer, x, y, 0, 0) == -1) {
TRACE(("Decode Error, Tile at %dx%d\n", x, y)); TRACE(("Decode Error, Tile at %dx%d\n", x, y));
state->errcode = IMAGING_CODEC_BROKEN; state->errcode = IMAGING_CODEC_BROKEN;
TIFFClose(tiff); TIFFClose(tiff);
@ -282,53 +382,68 @@ int ImagingLibTiffDecode(Imaging im, ImagingCodecState state, UINT8* buffer, int
TRACE(("Read tile at %dx%d; \n\n", x, y)); TRACE(("Read tile at %dx%d; \n\n", x, y));
current_tile_width = min(tile_width, state->xsize - x);
// iterate over each line in the tile and stuff data into image // iterate over each line in the tile and stuff data into image
for (tile_y = 0; tile_y < min(tileLength, state->ysize - y); tile_y++) { for (tile_y = 0; tile_y < min(tile_length, state->ysize - y); tile_y++) {
TRACE(("Writing tile data at %dx%d using tile_width: %d; \n", tile_y + y, x, current_tile_width));
TRACE(("Writing tile data at %dx%d using tilwWidth: %d; \n", tile_y + y, x, min(tileWidth, state->xsize - x))); // UINT8 * bbb = state->buffer + tile_y * row_byte_size;
// UINT8 * bbb = state->buffer + tile_y * (state->bytes / tileLength);
// TRACE(("chars: %x%x%x%x\n", ((UINT8 *)bbb)[0], ((UINT8 *)bbb)[1], ((UINT8 *)bbb)[2], ((UINT8 *)bbb)[3])); // TRACE(("chars: %x%x%x%x\n", ((UINT8 *)bbb)[0], ((UINT8 *)bbb)[1], ((UINT8 *)bbb)[2], ((UINT8 *)bbb)[3]));
state->shuffle((UINT8*) im->image[tile_y + y] + x * im->pixelsize, state->shuffle((UINT8*) im->image[tile_y + y] + x * im->pixelsize,
state->buffer + tile_y * (state->bytes / tileLength), state->buffer + tile_y * row_byte_size,
min(tileWidth, state->xsize - x) current_tile_width
); );
} }
} }
} }
} else { } else {
tsize_t size; UINT32 strip_row, row_byte_size;
UINT8 *new_data;
UINT32 rows_per_strip;
size = TIFFScanlineSize(tiff); TIFFGetField(tiff, TIFFTAG_ROWSPERSTRIP, &rows_per_strip);
TRACE(("ScanlineSize: %lu \n", size)); TRACE(("RowsPerStrip: %u \n", rows_per_strip));
if (size > state->bytes) {
TRACE(("Error, scanline size > buffer size\n")); // We could use TIFFStripSize, but for YCbCr data it returns subsampled data size
state->errcode = IMAGING_CODEC_BROKEN; row_byte_size = (state->xsize * state->bits + 7) / 8;
state->bytes = rows_per_strip * row_byte_size;
TRACE(("StripSize: %d \n", state->bytes));
/* realloc to fit whole strip */
new_data = realloc (state->buffer, state->bytes);
if (!new_data) {
state->errcode = IMAGING_CODEC_MEMORY;
TIFFClose(tiff); TIFFClose(tiff);
return -1; return -1;
} }
// Have to do this row by row and shove stuff into the buffer that way, state->buffer = new_data;
// with shuffle. (or, just alloc a buffer myself, then figure out how to get it
// back in. Can't use read encoded stripe.
// This thing pretty much requires that I have the whole image in one shot. for (; state->y < state->ysize; state->y += rows_per_strip) {
// Perhaps a stub version would work better??? if (ReadStrip(tiff, state->y, (UINT32 *)state->buffer) == -1) {
while(state->y < state->ysize){ TRACE(("Decode Error, strip %d\n", TIFFComputeStrip(tiff, state->y, 0)));
if (TIFFReadScanline(tiff, (tdata_t)state->buffer, (uint32)state->y, 0) == -1) {
TRACE(("Decode Error, row %d\n", state->y));
state->errcode = IMAGING_CODEC_BROKEN; state->errcode = IMAGING_CODEC_BROKEN;
TIFFClose(tiff); TIFFClose(tiff);
return -1; return -1;
} }
/* TRACE(("Decoded row %d \n", state->y)); */
state->shuffle((UINT8*) im->image[state->y + state->yoff] +
state->xoff * im->pixelsize,
state->buffer,
state->xsize);
state->y++; TRACE(("Decoded strip for row %d \n", state->y));
// iterate over each row in the strip and stuff data into image
for (strip_row = 0; strip_row < min(rows_per_strip, state->ysize - state->y); strip_row++) {
TRACE(("Writing data into line %d ; \n", state->y + strip_row));
// UINT8 * bbb = state->buffer + strip_row * (state->bytes / rows_per_strip);
// TRACE(("chars: %x %x %x %x\n", ((UINT8 *)bbb)[0], ((UINT8 *)bbb)[1], ((UINT8 *)bbb)[2], ((UINT8 *)bbb)[3]));
state->shuffle((UINT8*) im->image[state->y + state->yoff + strip_row] +
state->xoff * im->pixelsize,
state->buffer + strip_row * row_byte_size,
state->xsize);
}
} }
} }

View File

@ -1,3 +1,3 @@
curl -fsSL -o pypy2.zip https://bitbucket.org/pypy/pypy/downloads/pypy2.7-v7.0.0-win32.zip curl -fsSL -o pypy2.zip https://bitbucket.org/pypy/pypy/downloads/pypy2.7-v7.1.0-win32.zip
7z x pypy2.zip -oc:\ 7z x pypy2.zip -oc:\
c:\Python37\Scripts\virtualenv.exe -p c:\pypy2.7-v7.0.0-win32\pypy.exe c:\vp\pypy2 c:\Python37\Scripts\virtualenv.exe -p c:\pypy2.7-v7.1.0-win32\pypy.exe c:\vp\pypy2

View File

@ -33,9 +33,9 @@ libs = {
'dir': 'tiff-4.0.10', 'dir': 'tiff-4.0.10',
}, },
'freetype': { 'freetype': {
'url': 'https://download.savannah.gnu.org/releases/freetype/freetype-2.9.1.tar.gz', # noqa: E501 'url': 'https://download.savannah.gnu.org/releases/freetype/freetype-2.10.0.tar.gz', # noqa: E501
'filename': PILLOW_DEPENDS_DIR + 'freetype-2.9.1.tar.gz', 'filename': PILLOW_DEPENDS_DIR + 'freetype-2.10.0.tar.gz',
'dir': 'freetype-2.9.1', 'dir': 'freetype-2.10.0',
}, },
'lcms': { 'lcms': {
'url': SF_MIRROR+'/project/lcms/lcms/2.7/lcms2-2.7.zip', 'url': SF_MIRROR+'/project/lcms/lcms/2.7/lcms2-2.7.zip',

View File

@ -9,7 +9,11 @@ def fetch(url):
if not os.path.exists(name): if not os.path.exists(name):
print("Fetching", url) print("Fetching", url)
content = urllib.request.urlopen(url).read() try:
r = urllib.request.urlopen(url)
except urllib.error.URLError:
r = urllib.request.urlopen(url)
content = r.read()
with open(name, 'wb') as fd: with open(name, 'wb') as fd:
fd.write(content) fd.write(content)
return name return name