Merge pull request #1 from python-pillow/master

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

BIN
Tests/images/1_trns.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 612 B

Binary file not shown.

BIN
Tests/images/fujifilm.mpo Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.5 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 48 KiB

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

BIN
Tests/images/i_trns.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 117 KiB

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -113,12 +113,7 @@ class TestNumpy(PillowTestCase):
img = Image.fromarray(arr * 255).convert('1')
self.assertEqual(img.mode, '1')
arr_back = numpy.array(img)
# numpy 1.8 and earlier return this as a boolean. (trusty/precise)
if arr_back.dtype == numpy.bool:
arr_bool = numpy.array([[1, 0, 0, 1, 0], [0, 1, 0, 0, 0]], numpy.bool)
numpy.testing.assert_array_equal(arr_bool, arr_back)
else:
numpy.testing.assert_array_equal(arr, arr_back)
numpy.testing.assert_array_equal(arr, arr_back)
def test_save_tiff_uint16(self):
# Tests that we're getting the pixel value in the right byte order.

View File

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

View File

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

View File

@ -21,11 +21,6 @@ jobs:
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'

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -99,6 +99,24 @@ version.
Use ``PIL.__version__`` instead.
ImageCms.CmsProfile attributes
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Some attributes in ``ImageCms.CmsProfile`` have been deprecated since Pillow 3.2.0. From
6.0.0, they issue a ``DeprecationWarning``:
======================== ===============================
Deprecated Use instead
======================== ===============================
``color_space`` Padded ``xcolor_space``
``pcs`` Padded ``connection_space``
``product_copyright`` Unicode ``copyright``
``product_desc`` Unicode ``profile_description``
``product_description`` Unicode ``profile_description``
``product_manufacturer`` Unicode ``manufacturer``
``product_model`` Unicode ``model``
======================== ===============================
MIME type improvements
^^^^^^^^^^^^^^^^^^^^^^
@ -131,7 +149,7 @@ 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``.
This parameter takes the same values used in :py:meth:`~PIL.Image.Image.convert`.
New language parameter
^^^^^^^^^^^^^^^^^^^^^^
@ -147,12 +165,26 @@ language-specific glyphs and ligatures from the font:
* ``ImageFont.ImageFont.getsize_multiline()``
* ``ImageFont.ImageFont.getsize()``
Added EXIF class
^^^^^^^^^^^^^^^^
:py:meth:`~PIL.Image.Image.getexif` has been added, which returns an
:py:class:`~PIL.Image.Exif` instance. Values can be retrieved and set like a
dictionary. When saving JPEG, PNG or WEBP, the instance can be passed as an
``exif`` argument to include any changes in the output image.
Added ImageOps.exif_transpose
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
:py:meth:`~PIL.ImageOps.exif_transpose` returns a copy of an image, transposed
according to its EXIF Orientation tag.
PNG EXIF data
^^^^^^^^^^^^^
EXIF data can now be read from and saved to PNG images. However, unlike other image
formats, EXIF data is not guaranteed to have been read until
:py:meth:`~PIL.Image.Image.load` has been called.
formats, EXIF data is not guaranteed to be present in :py:attr:`~PIL.Image.Image.info`
until :py:meth:`~PIL.Image.Image.load` has been called.
Other Changes
=============
@ -172,3 +204,9 @@ TIFF compression codecs
^^^^^^^^^^^^^^^^^^^^^^^
Support has been added for the LZMA, Zstd and WebP TIFF compression codecs.
Improved support for transposing I;16 images
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
I;16, I;16L and I;16B are now supported image modes for all
:py:meth:`~PIL.Image.Image.transpose` operations.

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -25,6 +25,7 @@ kevin@cazabon.com\n\
http://www.cazabon.com\n\
"
#define PY_SSIZE_T_CLEAN
#include "Python.h" // Include before wchar.h so _GNU_SOURCE is set
#include "wchar.h"
#include "datetime.h"
@ -120,7 +121,7 @@ cms_profile_fromstring(PyObject* self, PyObject* args)
cmsHPROFILE hProfile;
char* pProfile;
int nProfile;
Py_ssize_t nProfile;
#if PY_VERSION_HEX >= 0x03000000
if (!PyArg_ParseTuple(args, "y#:profile_frombytes", &pProfile, &nProfile))
return NULL;
@ -735,12 +736,12 @@ _xyz3_py(cmsCIEXYZ* XYZ)
cmsXYZ2xyY(&xyY[2], &XYZ[2]);
return Py_BuildValue("(((d,d,d),(d,d,d),(d,d,d)),((d,d,d),(d,d,d),(d,d,d)))",
XYZ[0].X, XYZ[0].Y, XYZ[0].Z,
XYZ[1].X, XYZ[1].Y, XYZ[1].Z,
XYZ[2].X, XYZ[2].Y, XYZ[2].Z,
xyY[0].x, xyY[0].y, xyY[0].Y,
xyY[1].x, xyY[1].y, xyY[1].Y,
xyY[2].x, xyY[2].y, xyY[2].Y);
XYZ[0].X, XYZ[0].Y, XYZ[0].Z,
XYZ[1].X, XYZ[1].Y, XYZ[1].Z,
XYZ[2].X, XYZ[2].Y, XYZ[2].Z,
xyY[0].x, xyY[0].y, xyY[0].Y,
xyY[1].x, xyY[1].y, xyY[1].Y,
xyY[2].x, xyY[2].y, xyY[2].Y);
}
static PyObject*
@ -783,9 +784,9 @@ _profile_read_ciexyy_triple(CmsProfileObject* self, cmsTagSignature info)
/* Note: lcms does all the heavy lifting and error checking (nr of
channels == 3). */
return Py_BuildValue("((d,d,d),(d,d,d),(d,d,d)),",
triple->Red.x, triple->Red.y, triple->Red.Y,
triple->Green.x, triple->Green.y, triple->Green.Y,
triple->Blue.x, triple->Blue.y, triple->Blue.Y);
triple->Red.x, triple->Red.y, triple->Red.Y,
triple->Green.x, triple->Green.y, triple->Green.Y,
triple->Blue.x, triple->Blue.y, triple->Blue.Y);
}
static PyObject*
@ -817,12 +818,12 @@ _profile_read_named_color_list(CmsProfileObject* self, cmsTagSignature info)
for (i = 0; i < n; i++) {
PyObject* str;
cmsNamedColorInfo(ncl, i, name, NULL, NULL, NULL, NULL);
str = PyUnicode_FromString(name);
if (str == NULL) {
Py_DECREF(result);
Py_INCREF(Py_None);
return Py_None;
}
str = PyUnicode_FromString(name);
if (str == NULL) {
Py_DECREF(result);
Py_INCREF(Py_None);
return Py_None;
}
PyList_SET_ITEM(result, i, str);
}
@ -844,9 +845,9 @@ static cmsBool _calculate_rgb_primaries(CmsProfileObject* self, cmsCIEXYZTRIPLE*
// transform from our profile to XYZ using doubles for highest precision
hTransform = cmsCreateTransform(self->profile, TYPE_RGB_DBL,
hXYZ, TYPE_XYZ_DBL,
INTENT_RELATIVE_COLORIMETRIC,
cmsFLAGS_NOCACHE | cmsFLAGS_NOOPTIMIZE);
hXYZ, TYPE_XYZ_DBL,
INTENT_RELATIVE_COLORIMETRIC,
cmsFLAGS_NOCACHE | cmsFLAGS_NOOPTIMIZE);
cmsCloseProfile(hXYZ);
if (hTransform == NULL)
return 0;
@ -885,31 +886,31 @@ _is_intent_supported(CmsProfileObject* self, int clut)
n = cmsGetSupportedIntents(INTENTS,
intent_ids,
intent_descs);
intent_ids,
intent_descs);
for (i = 0; i < n; i++) {
int intent = (int) intent_ids[i];
PyObject* id;
PyObject* entry;
PyObject* entry;
/* Only valid for ICC Intents (otherwise we read invalid memory in lcms cmsio1.c). */
if (!(intent == INTENT_PERCEPTUAL || intent == INTENT_RELATIVE_COLORIMETRIC
|| intent == INTENT_SATURATION || intent == INTENT_ABSOLUTE_COLORIMETRIC))
continue;
/* Only valid for ICC Intents (otherwise we read invalid memory in lcms cmsio1.c). */
if (!(intent == INTENT_PERCEPTUAL || intent == INTENT_RELATIVE_COLORIMETRIC
|| intent == INTENT_SATURATION || intent == INTENT_ABSOLUTE_COLORIMETRIC))
continue;
id = PyInt_FromLong((long) intent);
entry = Py_BuildValue("(OOO)",
_check_intent(clut, self->profile, intent, LCMS_USED_AS_INPUT) ? Py_True : Py_False,
_check_intent(clut, self->profile, intent, LCMS_USED_AS_OUTPUT) ? Py_True : Py_False,
_check_intent(clut, self->profile, intent, LCMS_USED_AS_PROOF) ? Py_True : Py_False);
if (id == NULL || entry == NULL) {
Py_XDECREF(id);
Py_XDECREF(entry);
Py_XDECREF(result);
Py_INCREF(Py_None);
return Py_None;
}
PyDict_SetItem(result, id, entry);
id = PyInt_FromLong((long) intent);
entry = Py_BuildValue("(OOO)",
_check_intent(clut, self->profile, intent, LCMS_USED_AS_INPUT) ? Py_True : Py_False,
_check_intent(clut, self->profile, intent, LCMS_USED_AS_OUTPUT) ? Py_True : Py_False,
_check_intent(clut, self->profile, intent, LCMS_USED_AS_PROOF) ? Py_True : Py_False);
if (id == NULL || entry == NULL) {
Py_XDECREF(id);
Py_XDECREF(entry);
Py_XDECREF(result);
Py_INCREF(Py_None);
return Py_None;
}
PyDict_SetItem(result, id, entry);
}
return result;
}
@ -966,6 +967,8 @@ _profile_getattr(CmsProfileObject* self, cmsInfoType field)
static PyObject*
cms_profile_getattr_product_desc(CmsProfileObject* self, void* closure)
{
PyErr_WarnEx(PyExc_DeprecationWarning,
"product_desc is deprecated. Use Unicode profile_description instead.", 1);
// description was Description != 'Copyright' || or "%s - %s" (manufacturer, model) in 1.x
return _profile_getattr(self, cmsInfoDescription);
}
@ -975,24 +978,32 @@ cms_profile_getattr_product_desc(CmsProfileObject* self, void* closure)
static PyObject*
cms_profile_getattr_product_description(CmsProfileObject* self, void* closure)
{
PyErr_WarnEx(PyExc_DeprecationWarning,
"product_description is deprecated. Use Unicode profile_description instead.", 1);
return _profile_getattr(self, cmsInfoDescription);
}
static PyObject*
cms_profile_getattr_product_model(CmsProfileObject* self, void* closure)
{
PyErr_WarnEx(PyExc_DeprecationWarning,
"product_model is deprecated. Use Unicode model instead.", 1);
return _profile_getattr(self, cmsInfoModel);
}
static PyObject*
cms_profile_getattr_product_manufacturer(CmsProfileObject* self, void* closure)
{
PyErr_WarnEx(PyExc_DeprecationWarning,
"product_manufacturer is deprecated. Use Unicode manufacturer instead.", 1);
return _profile_getattr(self, cmsInfoManufacturer);
}
static PyObject*
cms_profile_getattr_product_copyright(CmsProfileObject* self, void* closure)
{
PyErr_WarnEx(PyExc_DeprecationWarning,
"product_copyright is deprecated. Use Unicode copyright instead.", 1);
return _profile_getattr(self, cmsInfoCopyright);
}
@ -1005,12 +1016,16 @@ cms_profile_getattr_rendering_intent(CmsProfileObject* self, void* closure)
static PyObject*
cms_profile_getattr_pcs(CmsProfileObject* self, void* closure)
{
PyErr_WarnEx(PyExc_DeprecationWarning,
"pcs is deprecated. Use padded connection_space instead.", 1);
return PyUnicode_DecodeFSDefault(findICmode(cmsGetPCS(self->profile)));
}
static PyObject*
cms_profile_getattr_color_space(CmsProfileObject* self, void* closure)
{
PyErr_WarnEx(PyExc_DeprecationWarning,
"color_space is deprecated. Use padded xcolor_space instead.", 1);
return PyUnicode_DecodeFSDefault(findICmode(cmsGetColorSpace(self->profile)));
}
@ -1070,7 +1085,7 @@ cms_profile_getattr_creation_date(CmsProfileObject* self, void* closure)
}
return PyDateTime_FromDateAndTime(1900 + ct.tm_year, ct.tm_mon, ct.tm_mday,
ct.tm_hour, ct.tm_min, ct.tm_sec, 0);
ct.tm_hour, ct.tm_min, ct.tm_sec, 0);
}
static PyObject*
@ -1295,7 +1310,7 @@ cms_profile_getattr_green_primary(CmsProfileObject* self, void* closure)
result = _calculate_rgb_primaries(self, &primaries);
if (! result) {
Py_INCREF(Py_None);
return Py_None;
return Py_None;
}
return _xyz_py(&primaries.Green);
@ -1311,7 +1326,7 @@ cms_profile_getattr_blue_primary(CmsProfileObject* self, void* closure)
result = _calculate_rgb_primaries(self, &primaries);
if (! result) {
Py_INCREF(Py_None);
return Py_None;
return Py_None;
}
return _xyz_py(&primaries.Blue);
@ -1394,11 +1409,11 @@ cms_profile_getattr_icc_measurement_condition (CmsProfileObject* self, void* clo
geo = "unknown";
return Py_BuildValue("{s:i,s:(ddd),s:s,s:d,s:s}",
"observer", mc->Observer,
"backing", mc->Backing.X, mc->Backing.Y, mc->Backing.Z,
"geo", geo,
"flare", mc->Flare,
"illuminant_type", _illu_map(mc->IlluminantType));
"observer", mc->Observer,
"backing", mc->Backing.X, mc->Backing.Y, mc->Backing.Z,
"geo", geo,
"flare", mc->Flare,
"illuminant_type", _illu_map(mc->IlluminantType));
}
static PyObject*
@ -1419,9 +1434,9 @@ cms_profile_getattr_icc_viewing_condition (CmsProfileObject* self, void* closure
}
return Py_BuildValue("{s:(ddd),s:(ddd),s:s}",
"illuminant", vc->IlluminantXYZ.X, vc->IlluminantXYZ.Y, vc->IlluminantXYZ.Z,
"surround", vc->SurroundXYZ.X, vc->SurroundXYZ.Y, vc->SurroundXYZ.Z,
"illuminant_type", _illu_map(vc->IlluminantType));
"illuminant", vc->IlluminantXYZ.X, vc->IlluminantXYZ.Y, vc->IlluminantXYZ.Z,
"surround", vc->SurroundXYZ.X, vc->SurroundXYZ.Y, vc->SurroundXYZ.Z,
"illuminant_type", _illu_map(vc->IlluminantType));
}

View File

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

View File

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

View File

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

View File

@ -517,6 +517,18 @@ l2cmyk(UINT8* out, const UINT8* in, int xsize)
}
}
static void
la2cmyk(UINT8* out, const UINT8* in, int xsize)
{
int x;
for (x = 0; x < xsize; x++, in += 4) {
*out++ = 0;
*out++ = 0;
*out++ = 0;
*out++ = ~(in[0]);
}
}
static void
rgb2cmyk(UINT8* out, const UINT8* in, int xsize)
{
@ -592,6 +604,22 @@ i2f(UINT8* out_, const UINT8* in_, int xsize)
*out++ = (FLOAT32) *in++;
}
static void
i2rgb(UINT8* out, const UINT8* in_, int xsize)
{
int x;
INT32* in = (INT32*) in_;
for (x = 0; x < xsize; x++, in++, out+=4) {
if (*in <= 0)
out[0] = out[1] = out[2] = 0;
else if (*in >= 255)
out[0] = out[1] = out[2] = 255;
else
out[0] = out[1] = out[2] = (UINT8) *in;
out[3] = 255;
}
}
/* ------------- */
/* F conversions */
/* ------------- */
@ -657,6 +685,18 @@ l2ycbcr(UINT8* out, const UINT8* in, int xsize)
}
}
static void
la2ycbcr(UINT8* out, const UINT8* in, int xsize)
{
int x;
for (x = 0; x < xsize; x++, in += 4) {
*out++ = in[0];
*out++ = 128;
*out++ = 128;
*out++ = 255;
}
}
static void
ycbcr2l(UINT8* out, const UINT8* in, int xsize)
{
@ -665,6 +705,16 @@ ycbcr2l(UINT8* out, const UINT8* in, int xsize)
*out++ = in[0];
}
static void
ycbcr2la(UINT8* out, const UINT8* in, int xsize)
{
int x;
for (x = 0; x < xsize; x++, in += 4, out += 4) {
out[0] = out[1] = out[2] = in[0];
out[3] = 255;
}
}
/* ------------------------- */
/* I;16 (16-bit) conversions */
/* ------------------------- */
@ -802,16 +852,21 @@ static struct {
{ "LA", "L", la2l },
{ "LA", "La", lA2la },
{ "LA", "RGB", la2rgb },
{ "LA", "RGBX", la2rgb },
{ "LA", "RGBA", la2rgb },
{ "LA", "RGBX", la2rgb },
{ "LA", "CMYK", la2cmyk },
{ "LA", "YCbCr", la2ycbcr },
{ "La", "LA", la2lA },
{ "I", "L", i2l },
{ "I", "F", i2f },
{ "I", "L", i2l },
{ "I", "F", i2f },
{ "I", "RGB", i2rgb },
{ "I", "RGBA", i2rgb },
{ "I", "RGBX", i2rgb },
{ "F", "L", f2l },
{ "F", "I", f2i },
{ "F", "L", f2l },
{ "F", "I", f2i },
{ "RGB", "1", rgb2bit },
{ "RGB", "L", rgb2l },
@ -842,8 +897,9 @@ static struct {
{ "RGBX", "1", rgb2bit },
{ "RGBX", "L", rgb2l },
{ "RGBA", "I", rgb2i },
{ "RGBA", "F", rgb2f },
{ "RGBX", "LA", rgb2la },
{ "RGBX", "I", rgb2i },
{ "RGBX", "F", rgb2f },
{ "RGBX", "RGB", rgba2rgb },
{ "RGBX", "CMYK", rgb2cmyk },
{ "RGBX", "YCbCr", ImagingConvertRGB2YCbCr },
@ -853,6 +909,7 @@ static struct {
{ "CMYK", "RGBX", cmyk2rgb },
{ "YCbCr", "L", ycbcr2l },
{ "YCbCr", "LA", ycbcr2la },
{ "YCbCr", "RGB", ImagingConvertYCbCr2RGB },
{ "HSV", "RGB", hsv2rgb },
@ -894,6 +951,15 @@ p2bit(UINT8* out, const UINT8* in, int xsize, const UINT8* palette)
*out++ = (L(&palette[in[x]*4]) >= 128000) ? 255 : 0;
}
static void
pa2bit(UINT8* out, const UINT8* in, int xsize, const UINT8* palette)
{
int x;
/* FIXME: precalculate greyscale palette? */
for (x = 0; x < xsize; x++, in += 4)
*out++ = (L(&palette[in[0]*4]) >= 128000) ? 255 : 0;
}
static void
p2l(UINT8* out, const UINT8* in, int xsize, const UINT8* palette)
{
@ -903,6 +969,28 @@ p2l(UINT8* out, const UINT8* in, int xsize, const UINT8* palette)
*out++ = L(&palette[in[x]*4]) / 1000;
}
static void
pa2l(UINT8* out, const UINT8* in, int xsize, const UINT8* palette)
{
int x;
/* FIXME: precalculate greyscale palette? */
for (x = 0; x < xsize; x++, in += 4)
*out++ = L(&palette[in[0]*4]) / 1000;
}
static void
p2pa(UINT8* out, const UINT8* in, int xsize, const UINT8* palette)
{
int x;
for (x = 0; x < xsize; x++, in++) {
const UINT8* rgba = &palette[in[0]];
*out++ = in[0];
*out++ = in[0];
*out++ = in[0];
*out++ = rgba[3];
}
}
static void
p2la(UINT8* out, const UINT8* in, int xsize, const UINT8* palette)
{
@ -920,9 +1008,9 @@ pa2la(UINT8* out, const UINT8* in, int xsize, const UINT8* palette)
{
int x;
/* FIXME: precalculate greyscale palette? */
for (x = 0; x < xsize; x++, in += 2) {
*out++ = L(&palette[in[0]*4]) / 1000;
*out++ = in[1];
for (x = 0; x < xsize; x++, in += 4, out += 4) {
out[0] = out[1] = out[2] = L(&palette[in[0]*4]) / 1000;
out[3] = in[3];
}
}
@ -935,6 +1023,15 @@ p2i(UINT8* out_, const UINT8* in, int xsize, const UINT8* palette)
*out++ = L(&palette[in[x]*4]) / 1000;
}
static void
pa2i(UINT8* out_, const UINT8* in, int xsize, const UINT8* palette)
{
int x;
INT32* out = (INT32*) out_;
for (x = 0; x < xsize; x++, in += 4)
*out++ = L(&palette[in[0]*4]) / 1000;
}
static void
p2f(UINT8* out_, const UINT8* in, int xsize, const UINT8* palette)
{
@ -944,6 +1041,15 @@ p2f(UINT8* out_, const UINT8* in, int xsize, const UINT8* palette)
*out++ = (float) L(&palette[in[x]*4]) / 1000.0F;
}
static void
pa2f(UINT8* out_, const UINT8* in, int xsize, const UINT8* palette)
{
int x;
FLOAT32* out = (FLOAT32*) out_;
for (x = 0; x < xsize; x++, in += 4)
*out++ = (float) L(&palette[in[0]*4]) / 1000.0F;
}
static void
p2rgb(UINT8* out, const UINT8* in, int xsize, const UINT8* palette)
{
@ -957,6 +1063,19 @@ p2rgb(UINT8* out, const UINT8* in, int xsize, const UINT8* palette)
}
}
static void
pa2rgb(UINT8* out, const UINT8* in, int xsize, const UINT8* palette)
{
int x;
for (x = 0; x < xsize; x++, in += 4) {
const UINT8* rgb = &palette[in[0] * 4];
*out++ = rgb[0];
*out++ = rgb[1];
*out++ = rgb[2];
*out++ = 255;
}
}
static void
p2rgba(UINT8* out, const UINT8* in, int xsize, const UINT8* palette)
{
@ -990,6 +1109,13 @@ p2cmyk(UINT8* out, const UINT8* in, int xsize, const UINT8* palette)
rgb2cmyk(out, out, xsize);
}
static void
pa2cmyk(UINT8* out, const UINT8* in, int xsize, const UINT8* palette)
{
pa2rgb(out, in, xsize, palette);
rgb2cmyk(out, out, xsize);
}
static void
p2ycbcr(UINT8* out, const UINT8* in, int xsize, const UINT8* palette)
{
@ -997,6 +1123,13 @@ p2ycbcr(UINT8* out, const UINT8* in, int xsize, const UINT8* palette)
ImagingConvertRGB2YCbCr(out, out, xsize);
}
static void
pa2ycbcr(UINT8* out, const UINT8* in, int xsize, const UINT8* palette)
{
pa2rgb(out, in, xsize, palette);
ImagingConvertRGB2YCbCr(out, out, xsize);
}
static Imaging
frompalette(Imaging imOut, Imaging imIn, const char *mode)
{
@ -1013,25 +1146,27 @@ frompalette(Imaging imOut, Imaging imIn, const char *mode)
alpha = !strcmp(imIn->mode, "PA");
if (strcmp(mode, "1") == 0)
convert = p2bit;
convert = alpha ? pa2bit : p2bit;
else if (strcmp(mode, "L") == 0)
convert = p2l;
convert = alpha ? pa2l : p2l;
else if (strcmp(mode, "LA") == 0)
convert = (alpha) ? pa2la : p2la;
convert = alpha ? pa2la : p2la;
else if (strcmp(mode, "PA") == 0)
convert = p2pa;
else if (strcmp(mode, "I") == 0)
convert = p2i;
convert = alpha ? pa2i : p2i;
else if (strcmp(mode, "F") == 0)
convert = p2f;
convert = alpha ? pa2f : p2f;
else if (strcmp(mode, "RGB") == 0)
convert = p2rgb;
convert = alpha ? pa2rgb : p2rgb;
else if (strcmp(mode, "RGBA") == 0)
convert = (alpha) ? pa2rgba : p2rgba;
convert = alpha ? pa2rgba : p2rgba;
else if (strcmp(mode, "RGBX") == 0)
convert = p2rgba;
convert = alpha ? pa2rgba : p2rgba;
else if (strcmp(mode, "CMYK") == 0)
convert = p2cmyk;
convert = alpha ? pa2cmyk : p2cmyk;
else if (strcmp(mode, "YCbCr") == 0)
convert = p2ycbcr;
convert = alpha ? pa2ycbcr : p2ycbcr;
else
return (Imaging) ImagingError_ValueError("conversion not supported");
@ -1052,9 +1187,10 @@ frompalette(Imaging imOut, Imaging imIn, const char *mode)
#pragma optimize("", off)
#endif
static Imaging
topalette(Imaging imOut, Imaging imIn, ImagingPalette inpalette, int dither)
topalette(Imaging imOut, Imaging imIn, const char *mode, ImagingPalette inpalette, int dither)
{
ImagingSectionCookie cookie;
int alpha;
int x, y;
ImagingPalette palette = inpalette;;
@ -1062,6 +1198,8 @@ topalette(Imaging imOut, Imaging imIn, ImagingPalette inpalette, int dither)
if (strcmp(imIn->mode, "L") != 0 && strncmp(imIn->mode, "RGB", 3) != 0)
return (Imaging) ImagingError_ValueError("conversion not supported");
alpha = !strcmp(mode, "PA");
if (palette == NULL) {
/* FIXME: make user configurable */
if (imIn->bands == 1)
@ -1073,7 +1211,7 @@ topalette(Imaging imOut, Imaging imIn, ImagingPalette inpalette, int dither)
if (!palette)
return (Imaging) ImagingError_ValueError("no palette");
imOut = ImagingNew2Dirty("P", imOut, imIn);
imOut = ImagingNew2Dirty(mode, imOut, imIn);
if (!imOut) {
if (palette != inpalette)
ImagingPaletteDelete(palette);
@ -1088,8 +1226,13 @@ topalette(Imaging imOut, Imaging imIn, ImagingPalette inpalette, int dither)
/* Greyscale palette: copy data as is */
ImagingSectionEnter(&cookie);
for (y = 0; y < imIn->ysize; y++)
memcpy(imOut->image[y], imIn->image[y], imIn->linesize);
for (y = 0; y < imIn->ysize; y++) {
if (alpha) {
l2la((UINT8*) imOut->image[y], (UINT8*) imIn->image[y], imIn->xsize);
} else {
memcpy(imOut->image[y], imIn->image[y], imIn->linesize);
}
}
ImagingSectionLeave(&cookie);
} else {
@ -1120,7 +1263,7 @@ topalette(Imaging imOut, Imaging imIn, ImagingPalette inpalette, int dither)
int g, g0, g1, g2;
int b, b0, b1, b2;
UINT8* in = (UINT8*) imIn->image[y];
UINT8* out = imOut->image8[y];
UINT8* out = alpha ? (UINT8*) imOut->image32[y] : imOut->image8[y];
int* e = errors;
r = r0 = r1 = 0;
@ -1139,7 +1282,12 @@ topalette(Imaging imOut, Imaging imIn, ImagingPalette inpalette, int dither)
cache = &ImagingPaletteCache(palette, r, g, b);
if (cache[0] == 0x100)
ImagingPaletteCacheUpdate(palette, r, g, b);
out[x] = (UINT8) cache[0];
if (alpha) {
out[x*4] = out[x*4+1] = out[x*4+2] = (UINT8) cache[0];
out[x*4+3] = 255;
} else {
out[x] = (UINT8) cache[0];
}
r -= (int) palette->palette[cache[0]*4];
g -= (int) palette->palette[cache[0]*4+1];
@ -1172,7 +1320,7 @@ topalette(Imaging imOut, Imaging imIn, ImagingPalette inpalette, int dither)
for (y = 0; y < imIn->ysize; y++) {
int r, g, b;
UINT8* in = (UINT8*) imIn->image[y];
UINT8* out = imOut->image8[y];
UINT8* out = alpha ? (UINT8*) imOut->image32[y] : imOut->image8[y];
for (x = 0; x < imIn->xsize; x++, in += 4) {
INT16* cache;
@ -1183,8 +1331,12 @@ topalette(Imaging imOut, Imaging imIn, ImagingPalette inpalette, int dither)
cache = &ImagingPaletteCache(palette, r, g, b);
if (cache[0] == 0x100)
ImagingPaletteCacheUpdate(palette, r, g, b);
out[x] = (UINT8) cache[0];
if (alpha) {
out[x*4] = out[x*4+1] = out[x*4+2] = (UINT8) cache[0];
out[x*4+3] = 255;
} else {
out[x] = (UINT8) cache[0];
}
}
}
ImagingSectionLeave(&cookie);
@ -1314,8 +1466,8 @@ convert(Imaging imOut, Imaging imIn, const char *mode,
if (strcmp(imIn->mode, "P") == 0 || strcmp(imIn->mode, "PA") == 0)
return frompalette(imOut, imIn, mode);
if (strcmp(mode, "P") == 0)
return topalette(imOut, imIn, palette, dither);
if (strcmp(mode, "P") == 0 || strcmp(mode, "PA") == 0)
return topalette(imOut, imIn, mode, palette, dither);
if (dither && strcmp(mode, "1") == 0)
return tobilevel(imOut, imIn, dither);
@ -1385,6 +1537,8 @@ ImagingConvertTransparent(Imaging imIn, const char *mode,
}
if (!((strcmp(imIn->mode, "RGB") == 0 ||
strcmp(imIn->mode, "1") == 0 ||
strcmp(imIn->mode, "I") == 0 ||
strcmp(imIn->mode, "L") == 0)
&& strcmp(mode, "RGBA") == 0))
#ifdef notdef
@ -1403,7 +1557,13 @@ ImagingConvertTransparent(Imaging imIn, const char *mode,
if (strcmp(imIn->mode, "RGB") == 0) {
convert = rgb2rgba;
} else {
convert = l2rgb;
if (strcmp(imIn->mode, "1") == 0) {
convert = bit2rgb;
} else if (strcmp(imIn->mode, "I") == 0) {
convert = i2rgb;
} else {
convert = l2rgb;
}
g = b = r;
}

View File

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

View File

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

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:\
c:\Python37\Scripts\virtualenv.exe -p c:\pypy2.7-v7.0.0-win32\pypy.exe c:\vp\pypy2
c:\Python37\Scripts\virtualenv.exe -p c:\pypy2.7-v7.1.0-win32\pypy.exe c:\vp\pypy2

View File

@ -109,6 +109,7 @@ set INCLUDE=%%INCLUDE%%;%%INCLIB%%\%(inc_dir)s;%%INCLIB%%\tcl%(tcl_ver)s\include
setlocal
set LIB=%%LIB%%;C:\Python%(py_ver)s\tcl%(vc_setup)s
call %(python_path)s\%(executable)s setup.py %(imaging_libs)s %%BLDOPT%%
call %(python_path)s\%(executable)s -c "from PIL import _webp;import os, shutil;shutil.copy('%%INCLIB%%\\freetype.dll', os.path.dirname(_webp.__file__));"
endlocal
endlocal

View File

@ -187,37 +187,30 @@ endlocal
return script % compiler
def msbuild_freetype(compiler):
if compiler['env_version'] == 'v7.1':
return msbuild_freetype_71(compiler)
return msbuild_freetype_70(compiler)
def msbuild_freetype_71(compiler):
return r"""
def msbuild_freetype(compiler, bit):
script = r"""
rem Build freetype
setlocal
rd /S /Q %%FREETYPE%%\objs
%%MSBUILD%% %%FREETYPE%%\builds\windows\vc%(vc_version)s\freetype.sln /t:Clean;Build /p:Configuration="Release" /p:Platform=%(platform)s /m
set DefaultPlatformToolset=v100
"""
properties = r"""/p:Configuration="Release" /p:Platform=%(platform)s"""
if bit == 64:
script += r'copy /Y /B ' +\
r'"C:\Program Files (x86)\Microsoft SDKs\Windows\v7.1A\Lib\x64\*.Lib" ' +\
r'%%FREETYPE%%\builds\windows\vc2010'
properties += r" /p:_IsNativeEnvironment=false"
script += r"""
%%MSBUILD%% %%FREETYPE%%\builds\windows\vc2010\freetype.sln /t:Clean;Build """+properties+r""" /m
xcopy /Y /E /Q %%FREETYPE%%\include %%INCLIB%%
copy /Y /B %%FREETYPE%%\objs\vc%(vc_version)s\%(platform)s\*.lib %%INCLIB%%\freetype.lib
"""
freetypeReleaseDir = r"%%FREETYPE%%\objs\%(platform)s\Release"
script += r"""
copy /Y /B """+freetypeReleaseDir+r"""\freetype.lib %%INCLIB%%\freetype.lib
copy /Y /B """+freetypeReleaseDir+r"""\freetype.dll %%INCLIB%%\..\freetype.dll
endlocal
""" % compiler # noqa: E501
def msbuild_freetype_70(compiler):
return r"""
rem Build freetype
setlocal
py -3 %%~dp0\fixproj.py %%FREETYPE%%\builds\windows\vc%(vc_version)s\freetype.sln %(platform)s
py -3 %%~dp0\fixproj.py %%FREETYPE%%\builds\windows\vc%(vc_version)s\freetype.vcproj %(platform)s
rd /S /Q %%FREETYPE%%\objs
%%MSBUILD%% %%FREETYPE%%\builds\windows\vc%(vc_version)s\freetype.sln /t:Clean;Build /p:Configuration="LIB Release";Platform=%(platform)s /m
xcopy /Y /E /Q %%FREETYPE%%\include %%INCLIB%%
xcopy /Y /E /Q %%FREETYPE%%\objs\win32\vc%(vc_version)s %%INCLIB%%
copy /Y /B %%FREETYPE%%\objs\win32\vc%(vc_version)s\*.lib %%INCLIB%%\freetype.lib
endlocal
""" % compiler # noqa: E501
"""
return script % compiler # noqa: E501
def build_lcms2(compiler):
@ -238,9 +231,9 @@ setlocal
rd /S /Q %%LCMS%%\Lib
rd /S /Q %%LCMS%%\Projects\VC%(vc_version)s\Release
%%MSBUILD%% %%LCMS%%\Projects\VC%(vc_version)s\lcms2.sln /t:Clean /p:Configuration="Release" /p:Platform=Win32 /m
%%MSBUILD%% %%LCMS%%\Projects\VC%(vc_version)s\lcms2.sln /t:lcms2_static /p:Configuration="Release" /p:Platform=Win32 /m
%%MSBUILD%% %%LCMS%%\Projects\VC%(vc_version)s\lcms2.sln /t:lcms2_static /p:Configuration="Release" /p:Platform=Win32 /p:PlatformToolset=v90 /m
xcopy /Y /E /Q %%LCMS%%\include %%INCLIB%%
copy /Y /B %%LCMS%%\Projects\VC%(vc_version)s\Release\*.lib %%INCLIB%%
copy /Y /B %%LCMS%%\Lib\MS\*.lib %%INCLIB%%
endlocal
""" % compiler # noqa: E501
@ -265,7 +258,7 @@ rem Build gs
setlocal
""" + vc_setup(compiler, bit) + r"""
set MSVC_VERSION=""" + {
"2008": "9",
"2010": "90",
"2015": "14"
}[compiler['vc_version']] + r"""
set RCOMP="C:\Program Files (x86)\Microsoft SDKs\Windows\v7.1A\Bin\RC.Exe"
@ -289,7 +282,7 @@ def add_compiler(compiler, bit):
# script.append(extract_openjpeg(compiler))
script.append(msbuild_freetype(compiler))
script.append(msbuild_freetype(compiler, bit))
script.append(build_lcms2(compiler))
# script.append(nmake_openjpeg(compiler))
script.append(build_ghostscript(compiler, bit))
@ -308,7 +301,7 @@ if 'PYTHON' in os.environ:
else:
# for compiler in all_compilers():
# add_compiler(compiler)
add_compiler(compilers[7.0][2008][32], 32)
add_compiler(compilers[7.0][2010][32], 32)
with open('build_deps.cmd', 'w') as f:
f.write("\n".join(script))

View File

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