mirror of
https://github.com/python-pillow/Pillow.git
synced 2024-09-21 11:28:58 +03:00
Merge branch 'master' into dpi_fix
This commit is contained in:
commit
ee5c134b33
|
@ -30,6 +30,10 @@ environment:
|
||||||
PIP_DIR: bin
|
PIP_DIR: bin
|
||||||
TEST_OPTIONS: --processes=0
|
TEST_OPTIONS: --processes=0
|
||||||
DEPLOY: NO
|
DEPLOY: NO
|
||||||
|
- PYTHON: C:/vp/pypy3
|
||||||
|
EXECUTABLE: bin/pypy.exe
|
||||||
|
PIP_DIR: bin
|
||||||
|
VENV: YES
|
||||||
|
|
||||||
|
|
||||||
install:
|
install:
|
||||||
|
@ -43,7 +47,12 @@ install:
|
||||||
- ps: |
|
- ps: |
|
||||||
if ($env:PYTHON -eq "c:/vp/pypy2")
|
if ($env:PYTHON -eq "c:/vp/pypy2")
|
||||||
{
|
{
|
||||||
c:\pillow\winbuild\appveyor_install_pypy.cmd
|
c:\pillow\winbuild\appveyor_install_pypy2.cmd
|
||||||
|
}
|
||||||
|
- ps: |
|
||||||
|
if ($env:PYTHON -eq "c:/vp/pypy3")
|
||||||
|
{
|
||||||
|
c:\pillow\winbuild\appveyor_install_pypy3.cmd
|
||||||
}
|
}
|
||||||
- ps: |
|
- ps: |
|
||||||
if ($env:PYTHON -eq "c:/msys64/mingw32")
|
if ($env:PYTHON -eq "c:/msys64/mingw32")
|
||||||
|
|
29
.github/workflows/lint.yml
vendored
Normal file
29
.github/workflows/lint.yml
vendored
Normal file
|
@ -0,0 +1,29 @@
|
||||||
|
name: Lint
|
||||||
|
|
||||||
|
on: [push, pull_request]
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
build:
|
||||||
|
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
strategy:
|
||||||
|
matrix:
|
||||||
|
python: [3.7]
|
||||||
|
|
||||||
|
name: Python ${{ matrix.python }}
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v1
|
||||||
|
|
||||||
|
- name: Set up Python ${{ matrix.python }}
|
||||||
|
uses: actions/setup-python@v1
|
||||||
|
with:
|
||||||
|
python-version: ${{ matrix.python }}
|
||||||
|
|
||||||
|
- name: Install dependencies
|
||||||
|
run: |
|
||||||
|
python -m pip install --upgrade pip
|
||||||
|
python -m pip install --upgrade tox
|
||||||
|
|
||||||
|
- name: Lint
|
||||||
|
run: tox -e lint
|
62
CHANGES.rst
62
CHANGES.rst
|
@ -7,12 +7,54 @@ Changelog (Pillow)
|
||||||
|
|
||||||
- This is the last Pillow release to support Python 2.7 #3642
|
- This is the last Pillow release to support Python 2.7 #3642
|
||||||
|
|
||||||
- Depends: Update libwebp to 1.0.3 #3983
|
- Changed WindowsViewer format to PNG #4080
|
||||||
|
[radarhere]
|
||||||
|
|
||||||
|
- Use TIFF orientation #4063
|
||||||
|
[radarhere]
|
||||||
|
|
||||||
|
- Raise the same error if a truncated image is loaded a second time #3965
|
||||||
|
[radarhere]
|
||||||
|
|
||||||
|
- Lazily use ImageFileDirectory_v1 values from Exif #4031
|
||||||
|
[radarhere]
|
||||||
|
|
||||||
|
- Improved HSV conversion #4004
|
||||||
|
[radarhere]
|
||||||
|
|
||||||
|
- Added text stroking #3978
|
||||||
|
[radarhere, hugovk]
|
||||||
|
|
||||||
|
- No more deprecated bdist_wininst .exe installers #4029
|
||||||
|
[hugovk]
|
||||||
|
|
||||||
|
- Do not allow floodfill to extend into negative coordinates #4017
|
||||||
|
[radarhere]
|
||||||
|
|
||||||
|
- Fixed arc drawing bug for a non-whole number of degrees #4014
|
||||||
|
[radarhere]
|
||||||
|
|
||||||
|
- Fix bug when merging identical images to GIF with a list of durations #4003
|
||||||
|
[djy0, radarhere]
|
||||||
|
|
||||||
|
- Fix bug in TIFF loading of BufferedReader #3998
|
||||||
|
[chadawagner]
|
||||||
|
|
||||||
|
- Added fallback for finding ld on MinGW Cygwin #4019
|
||||||
|
[radarhere]
|
||||||
|
|
||||||
|
- Remove indirect dependencies from requirements.txt #3976
|
||||||
|
[hugovk]
|
||||||
|
|
||||||
|
- Depends: Update libwebp to 1.0.3 #3983, libimagequant to 2.12.5 #3993, freetype to 2.10.1 #3991
|
||||||
[radarhere]
|
[radarhere]
|
||||||
|
|
||||||
- Change overflow check to use PY_SSIZE_T_MAX #3964
|
- Change overflow check to use PY_SSIZE_T_MAX #3964
|
||||||
[radarhere]
|
[radarhere]
|
||||||
|
|
||||||
|
- Report reason for pytest skips #3942
|
||||||
|
[hugovk]
|
||||||
|
|
||||||
6.1.0 (2019-07-01)
|
6.1.0 (2019-07-01)
|
||||||
------------------
|
------------------
|
||||||
|
|
||||||
|
@ -55,7 +97,7 @@ Changelog (Pillow)
|
||||||
- Updated TIFF tile descriptors to match current decoding functionality #3795
|
- Updated TIFF tile descriptors to match current decoding functionality #3795
|
||||||
[dmnisson]
|
[dmnisson]
|
||||||
|
|
||||||
- Added an `image.entropy()` method (second revision) #3608
|
- Added an ``image.entropy()`` method (second revision) #3608
|
||||||
[fish2000]
|
[fish2000]
|
||||||
|
|
||||||
- Pass the correct types to PyArg_ParseTuple #3880
|
- Pass the correct types to PyArg_ParseTuple #3880
|
||||||
|
@ -691,7 +733,7 @@ Changelog (Pillow)
|
||||||
- Enable background colour parameter on rotate #3057
|
- Enable background colour parameter on rotate #3057
|
||||||
[storesource]
|
[storesource]
|
||||||
|
|
||||||
- Remove unnecessary `#if 1` directive #3072
|
- Remove unnecessary ``#if 1`` directive #3072
|
||||||
[jdufresne]
|
[jdufresne]
|
||||||
|
|
||||||
- Remove unused Python class, Path #3070
|
- Remove unused Python class, Path #3070
|
||||||
|
@ -1228,7 +1270,7 @@ Changelog (Pillow)
|
||||||
- Add decompression bomb check to Image.crop #2410
|
- Add decompression bomb check to Image.crop #2410
|
||||||
[wiredfool]
|
[wiredfool]
|
||||||
|
|
||||||
- ImageFile: Ensure that the `err_code` variable is initialized in case of exception. #2363
|
- ImageFile: Ensure that the ``err_code`` variable is initialized in case of exception. #2363
|
||||||
[alexkiro]
|
[alexkiro]
|
||||||
|
|
||||||
- Tiff: Support append_images for saving multipage TIFFs #2406
|
- Tiff: Support append_images for saving multipage TIFFs #2406
|
||||||
|
@ -1465,7 +1507,7 @@ Changelog (Pillow)
|
||||||
- Removed PIL 1.0 era TK readme that concerns Windows 95/NT #2360
|
- Removed PIL 1.0 era TK readme that concerns Windows 95/NT #2360
|
||||||
[wiredfool]
|
[wiredfool]
|
||||||
|
|
||||||
- Prevent `nose -v` printing docstrings #2369
|
- Prevent ``nose -v`` printing docstrings #2369
|
||||||
[hugovk]
|
[hugovk]
|
||||||
|
|
||||||
- Replaced absolute PIL imports with relative imports #2349
|
- Replaced absolute PIL imports with relative imports #2349
|
||||||
|
@ -1910,7 +1952,7 @@ Changelog (Pillow)
|
||||||
- Changed depends/install_*.sh urls to point to github pillow-depends repo #1983
|
- Changed depends/install_*.sh urls to point to github pillow-depends repo #1983
|
||||||
[wiredfool]
|
[wiredfool]
|
||||||
|
|
||||||
- Allow ICC profile from `encoderinfo` while saving PNGs #1909
|
- Allow ICC profile from ``encoderinfo`` while saving PNGs #1909
|
||||||
[homm]
|
[homm]
|
||||||
|
|
||||||
- Fix integer overflow on ILP32 systems (32-bit Linux). #1975
|
- Fix integer overflow on ILP32 systems (32-bit Linux). #1975
|
||||||
|
@ -2353,7 +2395,7 @@ Changelog (Pillow)
|
||||||
- Added PDF multipage saving #1445
|
- Added PDF multipage saving #1445
|
||||||
[radarhere]
|
[radarhere]
|
||||||
|
|
||||||
- Removed deprecated code, Image.tostring, Image.fromstring, Image.offset, ImageDraw.setink, ImageDraw.setfill, ImageFileIO, ImageFont.FreeTypeFont and ImageFont.truetype `file` kwarg, ImagePalette private _make functions, ImageWin.fromstring and ImageWin.tostring #1343
|
- Removed deprecated code, Image.tostring, Image.fromstring, Image.offset, ImageDraw.setink, ImageDraw.setfill, ImageFileIO, ImageFont.FreeTypeFont and ImageFont.truetype ``file`` kwarg, ImagePalette private _make functions, ImageWin.fromstring and ImageWin.tostring #1343
|
||||||
[radarhere]
|
[radarhere]
|
||||||
|
|
||||||
- Load more broken images #1428
|
- Load more broken images #1428
|
||||||
|
@ -2845,7 +2887,7 @@ Changelog (Pillow)
|
||||||
- Doc cleanup
|
- Doc cleanup
|
||||||
[wiredfool]
|
[wiredfool]
|
||||||
|
|
||||||
- Fix `ImageStat` docs #796
|
- Fix ``ImageStat`` docs #796
|
||||||
[akx]
|
[akx]
|
||||||
|
|
||||||
- Added docs for ExifTags #794
|
- Added docs for ExifTags #794
|
||||||
|
@ -3282,7 +3324,7 @@ Changelog (Pillow)
|
||||||
- Add RGBA support to ImageColor #309
|
- Add RGBA support to ImageColor #309
|
||||||
[yoavweiss]
|
[yoavweiss]
|
||||||
|
|
||||||
- Test for `str`, not `"utf-8"` #306 (fixes #304)
|
- Test for ``str``, not ``"utf-8"`` #306 (fixes #304)
|
||||||
[mjpieters]
|
[mjpieters]
|
||||||
|
|
||||||
- Fix missing import os in _util.py #303
|
- Fix missing import os in _util.py #303
|
||||||
|
@ -3388,7 +3430,7 @@ Changelog (Pillow)
|
||||||
|
|
||||||
- Partial work to add a wrapper for WebPGetFeatures to correctly support #220 (fixes #204)
|
- Partial work to add a wrapper for WebPGetFeatures to correctly support #220 (fixes #204)
|
||||||
|
|
||||||
- Significant performance improvement of `alpha_composite` function #156
|
- Significant performance improvement of ``alpha_composite`` function #156
|
||||||
[homm]
|
[homm]
|
||||||
|
|
||||||
- Support explicitly disabling features via --disable-* options #240
|
- Support explicitly disabling features via --disable-* options #240
|
||||||
|
|
|
@ -67,7 +67,7 @@ To report a security vulnerability, please follow the procedure described in the
|
||||||
.. |zenodo| image:: https://zenodo.org/badge/17549/python-pillow/Pillow.svg
|
.. |zenodo| image:: https://zenodo.org/badge/17549/python-pillow/Pillow.svg
|
||||||
:target: https://zenodo.org/badge/latestdoi/17549/python-pillow/Pillow
|
:target: https://zenodo.org/badge/latestdoi/17549/python-pillow/Pillow
|
||||||
|
|
||||||
.. |tidelift| image:: https://tidelift.com/badges/github/python-pillow/Pillow?style=flat
|
.. |tidelift| image:: https://tidelift.com/badges/package/pypi/Pillow?style=flat
|
||||||
:target: https://tidelift.com/subscription/pkg/pypi-pillow?utm_source=pypi-pillow&utm_medium=referral&utm_campaign=readme
|
:target: https://tidelift.com/subscription/pkg/pypi-pillow?utm_source=pypi-pillow&utm_medium=referral&utm_campaign=readme
|
||||||
|
|
||||||
.. |version| image:: https://img.shields.io/pypi/v/pillow.svg
|
.. |version| image:: https://img.shields.io/pypi/v/pillow.svg
|
||||||
|
|
|
@ -4,7 +4,7 @@ Pillow Tests
|
||||||
Test scripts are named ``test_xxx.py`` and use the ``unittest`` module. A base class and helper functions can be found in ``helper.py``.
|
Test scripts are named ``test_xxx.py`` and use the ``unittest`` module. A base class and helper functions can be found in ``helper.py``.
|
||||||
|
|
||||||
Dependencies
|
Dependencies
|
||||||
-----------
|
------------
|
||||||
|
|
||||||
Install::
|
Install::
|
||||||
|
|
||||||
|
|
|
@ -355,6 +355,12 @@ def on_appveyor():
|
||||||
return "APPVEYOR" in os.environ
|
return "APPVEYOR" in os.environ
|
||||||
|
|
||||||
|
|
||||||
|
def on_ci():
|
||||||
|
# Travis and AppVeyor have "CI"
|
||||||
|
# Azure Pipelines has "TF_BUILD"
|
||||||
|
return "CI" in os.environ or "TF_BUILD" in os.environ
|
||||||
|
|
||||||
|
|
||||||
if sys.platform == "win32":
|
if sys.platform == "win32":
|
||||||
IMCONVERT = os.environ.get("MAGICK_HOME", "")
|
IMCONVERT = os.environ.get("MAGICK_HOME", "")
|
||||||
if IMCONVERT:
|
if IMCONVERT:
|
||||||
|
|
BIN
Tests/images/g4_orientation_1.tif
Executable file
BIN
Tests/images/g4_orientation_1.tif
Executable file
Binary file not shown.
BIN
Tests/images/g4_orientation_2.tif
Executable file
BIN
Tests/images/g4_orientation_2.tif
Executable file
Binary file not shown.
BIN
Tests/images/g4_orientation_3.tif
Executable file
BIN
Tests/images/g4_orientation_3.tif
Executable file
Binary file not shown.
BIN
Tests/images/g4_orientation_4.tif
Executable file
BIN
Tests/images/g4_orientation_4.tif
Executable file
Binary file not shown.
BIN
Tests/images/g4_orientation_5.tif
Executable file
BIN
Tests/images/g4_orientation_5.tif
Executable file
Binary file not shown.
BIN
Tests/images/g4_orientation_6.tif
Executable file
BIN
Tests/images/g4_orientation_6.tif
Executable file
Binary file not shown.
BIN
Tests/images/g4_orientation_7.tif
Executable file
BIN
Tests/images/g4_orientation_7.tif
Executable file
Binary file not shown.
BIN
Tests/images/g4_orientation_8.tif
Executable file
BIN
Tests/images/g4_orientation_8.tif
Executable file
Binary file not shown.
BIN
Tests/images/imagedraw_arc_width_non_whole_angle.png
Normal file
BIN
Tests/images/imagedraw_arc_width_non_whole_angle.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 439 B |
BIN
Tests/images/imagedraw_floodfill_not_negative.png
Normal file
BIN
Tests/images/imagedraw_floodfill_not_negative.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 214 B |
BIN
Tests/images/imagedraw_stroke_different.png
Normal file
BIN
Tests/images/imagedraw_stroke_different.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 2.2 KiB |
BIN
Tests/images/imagedraw_stroke_multiline.png
Normal file
BIN
Tests/images/imagedraw_stroke_multiline.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 4.0 KiB |
BIN
Tests/images/imagedraw_stroke_same.png
Normal file
BIN
Tests/images/imagedraw_stroke_same.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 1.3 KiB |
BIN
Tests/images/test_direction_ttb_stroke.png
Normal file
BIN
Tests/images/test_direction_ttb_stroke.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 3.7 KiB |
|
@ -495,6 +495,26 @@ class TestFileGif(PillowTestCase):
|
||||||
# Assert that the new duration is the total of the identical frames
|
# Assert that the new duration is the total of the identical frames
|
||||||
self.assertEqual(reread.info["duration"], 4500)
|
self.assertEqual(reread.info["duration"], 4500)
|
||||||
|
|
||||||
|
def test_identical_frames_to_single_frame(self):
|
||||||
|
for duration in ([1000, 1500, 2000, 4000], (1000, 1500, 2000, 4000), 8500):
|
||||||
|
out = self.tempfile("temp.gif")
|
||||||
|
im_list = [
|
||||||
|
Image.new("L", (100, 100), "#000"),
|
||||||
|
Image.new("L", (100, 100), "#000"),
|
||||||
|
Image.new("L", (100, 100), "#000"),
|
||||||
|
]
|
||||||
|
|
||||||
|
im_list[0].save(
|
||||||
|
out, save_all=True, append_images=im_list[1:], duration=duration
|
||||||
|
)
|
||||||
|
reread = Image.open(out)
|
||||||
|
|
||||||
|
# Assert that all frames were combined
|
||||||
|
self.assertEqual(reread.n_frames, 1)
|
||||||
|
|
||||||
|
# Assert that the new duration is the total of the identical frames
|
||||||
|
self.assertEqual(reread.info["duration"], 8500)
|
||||||
|
|
||||||
def test_number_of_loops(self):
|
def test_number_of_loops(self):
|
||||||
number_of_loops = 2
|
number_of_loops = 2
|
||||||
|
|
||||||
|
|
|
@ -369,6 +369,10 @@ class TestFileJpeg(PillowTestCase):
|
||||||
with self.assertRaises(IOError):
|
with self.assertRaises(IOError):
|
||||||
im.load()
|
im.load()
|
||||||
|
|
||||||
|
# Test that the error is raised if loaded a second time
|
||||||
|
with self.assertRaises(IOError):
|
||||||
|
im.load()
|
||||||
|
|
||||||
def _n_qtables_helper(self, n, test_file):
|
def _n_qtables_helper(self, n, test_file):
|
||||||
im = Image.open(test_file)
|
im = Image.open(test_file)
|
||||||
f = self.tempfile("temp.jpg")
|
f = self.tempfile("temp.jpg")
|
||||||
|
|
|
@ -91,6 +91,12 @@ class TestFileJpeg2k(PillowTestCase):
|
||||||
)
|
)
|
||||||
self.assert_image_equal(im, test_card)
|
self.assert_image_equal(im, test_card)
|
||||||
|
|
||||||
|
def test_tiled_offset_too_small(self):
|
||||||
|
with self.assertRaises(ValueError):
|
||||||
|
self.roundtrip(
|
||||||
|
test_card, tile_size=(128, 128), tile_offset=(0, 0), offset=(128, 32)
|
||||||
|
)
|
||||||
|
|
||||||
def test_irreversible_rt(self):
|
def test_irreversible_rt(self):
|
||||||
im = self.roundtrip(test_card, irreversible=True, quality_layers=[20])
|
im = self.roundtrip(test_card, irreversible=True, quality_layers=[20])
|
||||||
self.assert_image_similar(im, test_card, 2.0)
|
self.assert_image_similar(im, test_card, 2.0)
|
||||||
|
|
|
@ -81,6 +81,19 @@ class TestFileLibTiff(LibTiffTestCase):
|
||||||
self.assertEqual(im.size, (500, 500))
|
self.assertEqual(im.size, (500, 500))
|
||||||
self._assert_noerr(im)
|
self._assert_noerr(im)
|
||||||
|
|
||||||
|
def test_g4_non_disk_file_object(self):
|
||||||
|
"""Testing loading from non-disk non-BytesIO file object"""
|
||||||
|
test_file = "Tests/images/hopper_g4_500.tif"
|
||||||
|
s = io.BytesIO()
|
||||||
|
with open(test_file, "rb") as f:
|
||||||
|
s.write(f.read())
|
||||||
|
s.seek(0)
|
||||||
|
r = io.BufferedReader(s)
|
||||||
|
im = Image.open(r)
|
||||||
|
|
||||||
|
self.assertEqual(im.size, (500, 500))
|
||||||
|
self._assert_noerr(im)
|
||||||
|
|
||||||
def test_g4_eq_png(self):
|
def test_g4_eq_png(self):
|
||||||
""" Checking that we're actually getting the data that we expect"""
|
""" Checking that we're actually getting the data that we expect"""
|
||||||
png = Image.open("Tests/images/hopper_bw_500.png")
|
png = Image.open("Tests/images/hopper_bw_500.png")
|
||||||
|
@ -818,3 +831,12 @@ class TestFileLibTiff(LibTiffTestCase):
|
||||||
self.assert_image_equal_tofile(
|
self.assert_image_equal_tofile(
|
||||||
im, "Tests/images/old-style-jpeg-compression.png"
|
im, "Tests/images/old-style-jpeg-compression.png"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
def test_orientation(self):
|
||||||
|
base_im = Image.open("Tests/images/g4_orientation_1.tif")
|
||||||
|
|
||||||
|
for i in range(2, 9):
|
||||||
|
im = Image.open("Tests/images/g4_orientation_" + str(i) + ".tif")
|
||||||
|
im.load()
|
||||||
|
|
||||||
|
self.assert_image_similar(base_im, im, 0.7)
|
||||||
|
|
|
@ -222,7 +222,7 @@ class TestFileTiffMetadata(PillowTestCase):
|
||||||
self.assertEqual(0, reloaded.tag_v2[41988].numerator)
|
self.assertEqual(0, reloaded.tag_v2[41988].numerator)
|
||||||
self.assertEqual(0, reloaded.tag_v2[41988].denominator)
|
self.assertEqual(0, reloaded.tag_v2[41988].denominator)
|
||||||
|
|
||||||
def test_expty_values(self):
|
def test_empty_values(self):
|
||||||
data = io.BytesIO(
|
data = io.BytesIO(
|
||||||
b"II*\x00\x08\x00\x00\x00\x03\x00\x1a\x01\x05\x00\x00\x00\x00\x00"
|
b"II*\x00\x08\x00\x00\x00\x03\x00\x1a\x01\x05\x00\x00\x00\x00\x00"
|
||||||
b"\x00\x00\x00\x00\x1b\x01\x05\x00\x00\x00\x00\x00\x00\x00\x00\x00"
|
b"\x00\x00\x00\x00\x1b\x01\x05\x00\x00\x00\x00\x00\x00\x00\x00\x00"
|
||||||
|
@ -239,11 +239,13 @@ class TestFileTiffMetadata(PillowTestCase):
|
||||||
def test_PhotoshopInfo(self):
|
def test_PhotoshopInfo(self):
|
||||||
im = Image.open("Tests/images/issue_2278.tif")
|
im = Image.open("Tests/images/issue_2278.tif")
|
||||||
|
|
||||||
self.assertIsInstance(im.tag_v2[34377], bytes)
|
self.assertEqual(len(im.tag_v2[34377]), 1)
|
||||||
|
self.assertIsInstance(im.tag_v2[34377][0], bytes)
|
||||||
out = self.tempfile("temp.tiff")
|
out = self.tempfile("temp.tiff")
|
||||||
im.save(out)
|
im.save(out)
|
||||||
reloaded = Image.open(out)
|
reloaded = Image.open(out)
|
||||||
self.assertIsInstance(reloaded.tag_v2[34377], bytes)
|
self.assertEqual(len(reloaded.tag_v2[34377]), 1)
|
||||||
|
self.assertIsInstance(reloaded.tag_v2[34377][0], bytes)
|
||||||
|
|
||||||
def test_too_many_entries(self):
|
def test_too_many_entries(self):
|
||||||
ifd = TiffImagePlugin.ImageFileDirectory_v2()
|
ifd = TiffImagePlugin.ImageFileDirectory_v2()
|
||||||
|
|
|
@ -23,6 +23,7 @@ class TestImageConvert(PillowTestCase):
|
||||||
"RGBX",
|
"RGBX",
|
||||||
"CMYK",
|
"CMYK",
|
||||||
"YCbCr",
|
"YCbCr",
|
||||||
|
"HSV",
|
||||||
)
|
)
|
||||||
|
|
||||||
for mode in modes:
|
for mode in modes:
|
||||||
|
|
|
@ -1,8 +1,8 @@
|
||||||
import os.path
|
import os.path
|
||||||
|
|
||||||
from PIL import Image, ImageColor, ImageDraw
|
from PIL import Image, ImageColor, ImageDraw, ImageFont, features
|
||||||
|
|
||||||
from .helper import PillowTestCase, hopper
|
from .helper import PillowTestCase, hopper, unittest
|
||||||
|
|
||||||
BLACK = (0, 0, 0)
|
BLACK = (0, 0, 0)
|
||||||
WHITE = (255, 255, 255)
|
WHITE = (255, 255, 255)
|
||||||
|
@ -29,6 +29,8 @@ POINTS2 = [10, 10, 20, 40, 30, 30]
|
||||||
|
|
||||||
KITE_POINTS = [(10, 50), (70, 10), (90, 50), (70, 90), (10, 50)]
|
KITE_POINTS = [(10, 50), (70, 10), (90, 50), (70, 90), (10, 50)]
|
||||||
|
|
||||||
|
HAS_FREETYPE = features.check("freetype2")
|
||||||
|
|
||||||
|
|
||||||
class TestImageDraw(PillowTestCase):
|
class TestImageDraw(PillowTestCase):
|
||||||
def test_sanity(self):
|
def test_sanity(self):
|
||||||
|
@ -140,6 +142,18 @@ class TestImageDraw(PillowTestCase):
|
||||||
# Assert
|
# Assert
|
||||||
self.assert_image_similar(im, Image.open(expected), 1)
|
self.assert_image_similar(im, Image.open(expected), 1)
|
||||||
|
|
||||||
|
def test_arc_width_non_whole_angle(self):
|
||||||
|
# Arrange
|
||||||
|
im = Image.new("RGB", (W, H))
|
||||||
|
draw = ImageDraw.Draw(im)
|
||||||
|
expected = "Tests/images/imagedraw_arc_width_non_whole_angle.png"
|
||||||
|
|
||||||
|
# Act
|
||||||
|
draw.arc(BBOX1, 10, 259.5, width=5)
|
||||||
|
|
||||||
|
# Assert
|
||||||
|
self.assert_image_similar(im, Image.open(expected), 1)
|
||||||
|
|
||||||
def test_bitmap(self):
|
def test_bitmap(self):
|
||||||
# Arrange
|
# Arrange
|
||||||
small = Image.open("Tests/images/pil123rgba.png").resize((50, 50))
|
small = Image.open("Tests/images/pil123rgba.png").resize((50, 50))
|
||||||
|
@ -559,6 +573,24 @@ class TestImageDraw(PillowTestCase):
|
||||||
# Assert
|
# Assert
|
||||||
self.assert_image_equal(im, Image.open("Tests/images/imagedraw_floodfill2.png"))
|
self.assert_image_equal(im, Image.open("Tests/images/imagedraw_floodfill2.png"))
|
||||||
|
|
||||||
|
def test_floodfill_not_negative(self):
|
||||||
|
# floodfill() is experimental
|
||||||
|
# Test that floodfill does not extend into negative coordinates
|
||||||
|
|
||||||
|
# Arrange
|
||||||
|
im = Image.new("RGB", (W, H))
|
||||||
|
draw = ImageDraw.Draw(im)
|
||||||
|
draw.line((W / 2, 0, W / 2, H / 2), fill="green")
|
||||||
|
draw.line((0, H / 2, W / 2, H / 2), fill="green")
|
||||||
|
|
||||||
|
# Act
|
||||||
|
ImageDraw.floodfill(im, (int(W / 4), int(H / 4)), ImageColor.getrgb("red"))
|
||||||
|
|
||||||
|
# Assert
|
||||||
|
self.assert_image_equal(
|
||||||
|
im, Image.open("Tests/images/imagedraw_floodfill_not_negative.png")
|
||||||
|
)
|
||||||
|
|
||||||
def create_base_image_draw(
|
def create_base_image_draw(
|
||||||
self, size, mode=DEFAULT_MODE, background1=WHITE, background2=GRAY
|
self, size, mode=DEFAULT_MODE, background1=WHITE, background2=GRAY
|
||||||
):
|
):
|
||||||
|
@ -771,6 +803,54 @@ class TestImageDraw(PillowTestCase):
|
||||||
draw.textsize("\n")
|
draw.textsize("\n")
|
||||||
draw.textsize("test\n")
|
draw.textsize("test\n")
|
||||||
|
|
||||||
|
@unittest.skipUnless(HAS_FREETYPE, "ImageFont not available")
|
||||||
|
def test_textsize_stroke(self):
|
||||||
|
# Arrange
|
||||||
|
im = Image.new("RGB", (W, H))
|
||||||
|
draw = ImageDraw.Draw(im)
|
||||||
|
font = ImageFont.truetype("Tests/fonts/FreeMono.ttf", 20)
|
||||||
|
|
||||||
|
# Act / Assert
|
||||||
|
self.assertEqual(draw.textsize("A", font, stroke_width=2), (16, 20))
|
||||||
|
self.assertEqual(
|
||||||
|
draw.multiline_textsize("ABC\nAaaa", font, stroke_width=2), (52, 44)
|
||||||
|
)
|
||||||
|
|
||||||
|
@unittest.skipUnless(HAS_FREETYPE, "ImageFont not available")
|
||||||
|
def test_stroke(self):
|
||||||
|
for suffix, stroke_fill in {"same": None, "different": "#0f0"}.items():
|
||||||
|
# Arrange
|
||||||
|
im = Image.new("RGB", (120, 130))
|
||||||
|
draw = ImageDraw.Draw(im)
|
||||||
|
font = ImageFont.truetype("Tests/fonts/FreeMono.ttf", 120)
|
||||||
|
|
||||||
|
# Act
|
||||||
|
draw.text(
|
||||||
|
(10, 10), "A", "#f00", font, stroke_width=2, stroke_fill=stroke_fill
|
||||||
|
)
|
||||||
|
|
||||||
|
# Assert
|
||||||
|
self.assert_image_similar(
|
||||||
|
im, Image.open("Tests/images/imagedraw_stroke_" + suffix + ".png"), 3.1
|
||||||
|
)
|
||||||
|
|
||||||
|
@unittest.skipUnless(HAS_FREETYPE, "ImageFont not available")
|
||||||
|
def test_stroke_multiline(self):
|
||||||
|
# Arrange
|
||||||
|
im = Image.new("RGB", (100, 250))
|
||||||
|
draw = ImageDraw.Draw(im)
|
||||||
|
font = ImageFont.truetype("Tests/fonts/FreeMono.ttf", 120)
|
||||||
|
|
||||||
|
# Act
|
||||||
|
draw.multiline_text(
|
||||||
|
(10, 10), "A\nB", "#f00", font, stroke_width=2, stroke_fill="#0f0"
|
||||||
|
)
|
||||||
|
|
||||||
|
# Assert
|
||||||
|
self.assert_image_similar(
|
||||||
|
im, Image.open("Tests/images/imagedraw_stroke_multiline.png"), 3.3
|
||||||
|
)
|
||||||
|
|
||||||
def test_same_color_outline(self):
|
def test_same_color_outline(self):
|
||||||
# Prepare shape
|
# Prepare shape
|
||||||
x0, y0 = 5, 5
|
x0, y0 = 5, 5
|
||||||
|
|
|
@ -111,6 +111,10 @@ class TestImageFile(PillowTestCase):
|
||||||
with self.assertRaises(IOError):
|
with self.assertRaises(IOError):
|
||||||
im.load()
|
im.load()
|
||||||
|
|
||||||
|
# Test that the error is raised if loaded a second time
|
||||||
|
with self.assertRaises(IOError):
|
||||||
|
im.load()
|
||||||
|
|
||||||
def test_truncated_without_errors(self):
|
def test_truncated_without_errors(self):
|
||||||
if "zip_encoder" not in codecs:
|
if "zip_encoder" not in codecs:
|
||||||
self.skipTest("PNG (zlib) encoder not available")
|
self.skipTest("PNG (zlib) encoder not available")
|
||||||
|
@ -321,3 +325,13 @@ class TestPyDecoder(PillowTestCase):
|
||||||
self.assertEqual(
|
self.assertEqual(
|
||||||
exif.get_ifd(0xA005), {1: "R98", 2: b"0100", 4097: 2272, 4098: 1704}
|
exif.get_ifd(0xA005), {1: "R98", 2: b"0100", 4097: 2272, 4098: 1704}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
def test_exif_shared(self):
|
||||||
|
im = Image.open("Tests/images/exif.png")
|
||||||
|
exif = im.getexif()
|
||||||
|
self.assertIs(im.getexif(), exif)
|
||||||
|
|
||||||
|
def test_exif_str(self):
|
||||||
|
im = Image.open("Tests/images/exif.png")
|
||||||
|
exif = im.getexif()
|
||||||
|
self.assertEqual(str(exif), "{274: 1}")
|
||||||
|
|
|
@ -605,6 +605,21 @@ class TestImageFont(PillowTestCase):
|
||||||
self.assertEqual(t.getsize_multiline("ABC\nA"), (36, 36))
|
self.assertEqual(t.getsize_multiline("ABC\nA"), (36, 36))
|
||||||
self.assertEqual(t.getsize_multiline("ABC\nAaaa"), (48, 36))
|
self.assertEqual(t.getsize_multiline("ABC\nAaaa"), (48, 36))
|
||||||
|
|
||||||
|
def test_getsize_stroke(self):
|
||||||
|
# Arrange
|
||||||
|
t = self.get_font()
|
||||||
|
|
||||||
|
# Act / Assert
|
||||||
|
for stroke_width in [0, 2]:
|
||||||
|
self.assertEqual(
|
||||||
|
t.getsize("A", stroke_width=stroke_width),
|
||||||
|
(12 + stroke_width * 2, 16 + stroke_width * 2),
|
||||||
|
)
|
||||||
|
self.assertEqual(
|
||||||
|
t.getsize_multiline("ABC\nAaaa", stroke_width=stroke_width),
|
||||||
|
(48 + stroke_width * 2, 36 + stroke_width * 4),
|
||||||
|
)
|
||||||
|
|
||||||
def test_complex_font_settings(self):
|
def test_complex_font_settings(self):
|
||||||
# Arrange
|
# Arrange
|
||||||
t = self.get_font()
|
t = self.get_font()
|
||||||
|
|
|
@ -115,6 +115,30 @@ class TestImagecomplextext(PillowTestCase):
|
||||||
|
|
||||||
self.assert_image_similar(im, target_img, 1.15)
|
self.assert_image_similar(im, target_img, 1.15)
|
||||||
|
|
||||||
|
def test_text_direction_ttb_stroke(self):
|
||||||
|
ttf = ImageFont.truetype("Tests/fonts/NotoSansJP-Regular.otf", 50)
|
||||||
|
|
||||||
|
im = Image.new(mode="RGB", size=(100, 300))
|
||||||
|
draw = ImageDraw.Draw(im)
|
||||||
|
try:
|
||||||
|
draw.text(
|
||||||
|
(25, 25),
|
||||||
|
"あい",
|
||||||
|
font=ttf,
|
||||||
|
fill=500,
|
||||||
|
direction="ttb",
|
||||||
|
stroke_width=2,
|
||||||
|
stroke_fill="#0f0",
|
||||||
|
)
|
||||||
|
except ValueError as ex:
|
||||||
|
if str(ex) == "libraqm 0.7 or greater required for 'ttb' direction":
|
||||||
|
self.skipTest("libraqm 0.7 or greater not available")
|
||||||
|
|
||||||
|
target = "Tests/images/test_direction_ttb_stroke.png"
|
||||||
|
target_img = Image.open(target)
|
||||||
|
|
||||||
|
self.assert_image_similar(im, target_img, 12.4)
|
||||||
|
|
||||||
def test_ligature_features(self):
|
def test_ligature_features(self):
|
||||||
ttf = ImageFont.truetype(FONT_PATH, FONT_SIZE)
|
ttf = ImageFont.truetype(FONT_PATH, FONT_SIZE)
|
||||||
|
|
||||||
|
|
|
@ -8,7 +8,11 @@ try:
|
||||||
|
|
||||||
class TestImageGrab(PillowTestCase):
|
class TestImageGrab(PillowTestCase):
|
||||||
def test_grab(self):
|
def test_grab(self):
|
||||||
for im in [ImageGrab.grab(), ImageGrab.grab(include_layered_windows=True)]:
|
for im in [
|
||||||
|
ImageGrab.grab(),
|
||||||
|
ImageGrab.grab(include_layered_windows=True),
|
||||||
|
ImageGrab.grab(all_screens=True),
|
||||||
|
]:
|
||||||
self.assert_image(im, im.mode, im.size)
|
self.assert_image(im, im.mode, im.size)
|
||||||
|
|
||||||
def test_grabclipboard(self):
|
def test_grabclipboard(self):
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
from PIL import Image, ImageShow
|
from PIL import Image, ImageShow
|
||||||
|
|
||||||
from .helper import PillowTestCase, hopper
|
from .helper import PillowTestCase, hopper, on_ci, unittest
|
||||||
|
|
||||||
|
|
||||||
class TestImageShow(PillowTestCase):
|
class TestImageShow(PillowTestCase):
|
||||||
|
@ -15,7 +15,7 @@ class TestImageShow(PillowTestCase):
|
||||||
# Restore original state
|
# Restore original state
|
||||||
ImageShow._viewers.pop()
|
ImageShow._viewers.pop()
|
||||||
|
|
||||||
def test_show(self):
|
def test_viewer_show(self):
|
||||||
class TestViewer(ImageShow.Viewer):
|
class TestViewer(ImageShow.Viewer):
|
||||||
methodCalled = False
|
methodCalled = False
|
||||||
|
|
||||||
|
@ -34,6 +34,12 @@ class TestImageShow(PillowTestCase):
|
||||||
# Restore original state
|
# Restore original state
|
||||||
ImageShow._viewers.pop(0)
|
ImageShow._viewers.pop(0)
|
||||||
|
|
||||||
|
@unittest.skipUnless(on_ci(), "Only run on CIs")
|
||||||
|
def test_show(self):
|
||||||
|
for mode in ("1", "I;16", "LA", "RGB", "RGBA"):
|
||||||
|
im = hopper(mode)
|
||||||
|
self.assertTrue(ImageShow.show(im))
|
||||||
|
|
||||||
def test_viewer(self):
|
def test_viewer(self):
|
||||||
viewer = ImageShow.Viewer()
|
viewer = ImageShow.Viewer()
|
||||||
|
|
||||||
|
|
|
@ -31,4 +31,8 @@ class TestLocale(PillowTestCase):
|
||||||
locale.setlocale(locale.LC_ALL, "polish")
|
locale.setlocale(locale.LC_ALL, "polish")
|
||||||
except locale.Error:
|
except locale.Error:
|
||||||
unittest.skip("Polish locale not available")
|
unittest.skip("Polish locale not available")
|
||||||
|
|
||||||
|
try:
|
||||||
Image.open(path)
|
Image.open(path)
|
||||||
|
finally:
|
||||||
|
locale.setlocale(locale.LC_ALL, (None, None))
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
#!/bin/bash
|
#!/bin/bash
|
||||||
# install libimagequant
|
# install libimagequant
|
||||||
|
|
||||||
archive=libimagequant-2.12.3
|
archive=libimagequant-2.12.5
|
||||||
|
|
||||||
./download-and-extract.sh $archive https://raw.githubusercontent.com/python-pillow/pillow-depends/master/$archive.tar.gz
|
./download-and-extract.sh $archive https://raw.githubusercontent.com/python-pillow/pillow-depends/master/$archive.tar.gz
|
||||||
|
|
||||||
|
|
|
@ -44,11 +44,24 @@ supports the following standard modes:
|
||||||
* ``I`` (32-bit signed integer pixels)
|
* ``I`` (32-bit signed integer pixels)
|
||||||
* ``F`` (32-bit floating point pixels)
|
* ``F`` (32-bit floating point pixels)
|
||||||
|
|
||||||
PIL also provides limited support for a few special modes, including ``LA`` (L
|
Pillow also provides limited support for a few special modes, including:
|
||||||
with alpha), ``RGBX`` (true color with padding) and ``RGBa`` (true color with
|
|
||||||
premultiplied alpha). However, PIL doesn’t support user-defined modes; if you
|
* ``LA`` (L with alpha)
|
||||||
need to handle band combinations that are not listed above, use a sequence of
|
* ``PA`` (P with alpha)
|
||||||
Image objects.
|
* ``RGBX`` (true color with padding)
|
||||||
|
* ``RGBa`` (true color with premultiplied alpha)
|
||||||
|
* ``La`` (L with premultiplied alpha)
|
||||||
|
* ``I;16`` (16-bit unsigned integer pixels)
|
||||||
|
* ``I;16L`` (16-bit little endian unsigned integer pixels)
|
||||||
|
* ``I;16B`` (16-bit big endian unsigned integer pixels)
|
||||||
|
* ``I;16N`` (16-bit native endian unsigned integer pixels)
|
||||||
|
* ``BGR;15`` (15-bit reversed true colour)
|
||||||
|
* ``BGR;16`` (16-bit reversed true colour)
|
||||||
|
* ``BGR;24`` (24-bit reversed true colour)
|
||||||
|
* ``BGR;32`` (32-bit reversed true colour)
|
||||||
|
|
||||||
|
However, Pillow doesn’t support user-defined modes; if you need to handle band
|
||||||
|
combinations that are not listed above, use a sequence of Image objects.
|
||||||
|
|
||||||
You can read the mode of an image through the :py:attr:`~PIL.Image.Image.mode`
|
You can read the mode of an image through the :py:attr:`~PIL.Image.Image.mode`
|
||||||
attribute. This is a string containing one of the above values.
|
attribute. This is a string containing one of the above values.
|
||||||
|
|
|
@ -389,12 +389,12 @@ The :py:meth:`~PIL.Image.Image.save` method supports the following options:
|
||||||
image will be saved without tiling.
|
image will be saved without tiling.
|
||||||
|
|
||||||
**quality_mode**
|
**quality_mode**
|
||||||
Either `"rates"` or `"dB"` depending on the units you want to use to
|
Either ``"rates"`` or ``"dB"`` depending on the units you want to use to
|
||||||
specify image quality.
|
specify image quality.
|
||||||
|
|
||||||
**quality_layers**
|
**quality_layers**
|
||||||
A sequence of numbers, each of which represents either an approximate size
|
A sequence of numbers, each of which represents either an approximate size
|
||||||
reduction (if quality mode is `"rates"`) or a signal to noise ratio value
|
reduction (if quality mode is ``"rates"``) or a signal to noise ratio value
|
||||||
in decibels. If not specified, defaults to a single layer of full quality.
|
in decibels. If not specified, defaults to a single layer of full quality.
|
||||||
|
|
||||||
**num_resolutions**
|
**num_resolutions**
|
||||||
|
@ -811,10 +811,10 @@ Saving sequences
|
||||||
|
|
||||||
Support for animated WebP files will only be enabled if the system WebP
|
Support for animated WebP files will only be enabled if the system WebP
|
||||||
library is v0.5.0 or later. You can check webp animation support at
|
library is v0.5.0 or later. You can check webp animation support at
|
||||||
runtime by calling `features.check("webp_anim")`.
|
runtime by calling ``features.check("webp_anim")``.
|
||||||
|
|
||||||
When calling :py:meth:`~PIL.Image.Image.save`, the following options
|
When calling :py:meth:`~PIL.Image.Image.save`, the following options
|
||||||
are available when the `save_all` argument is present and true.
|
are available when the ``save_all`` argument is present and true.
|
||||||
|
|
||||||
**append_images**
|
**append_images**
|
||||||
A list of images to append as additional frames. Each of the
|
A list of images to append as additional frames. Each of the
|
||||||
|
|
|
@ -247,7 +247,7 @@ Transposing an image
|
||||||
out = im.transpose(Image.ROTATE_270)
|
out = im.transpose(Image.ROTATE_270)
|
||||||
|
|
||||||
``transpose(ROTATE)`` operations can also be performed identically with
|
``transpose(ROTATE)`` operations can also be performed identically with
|
||||||
:py:meth:`~PIL.Image.Image.rotate` operations, provided the `expand` flag is
|
:py:meth:`~PIL.Image.Image.rotate` operations, provided the ``expand`` flag is
|
||||||
true, to provide for the same changes to the image's size.
|
true, to provide for the same changes to the image's size.
|
||||||
|
|
||||||
A more general form of image transformations can be carried out via the
|
A more general form of image transformations can be carried out via the
|
||||||
|
|
|
@ -3,6 +3,8 @@ Pillow
|
||||||
|
|
||||||
Pillow is the friendly PIL fork by `Alex Clark and Contributors <https://github.com/python-pillow/Pillow/graphs/contributors>`_. PIL is the Python Imaging Library by Fredrik Lundh and Contributors.
|
Pillow is the friendly PIL fork by `Alex Clark and Contributors <https://github.com/python-pillow/Pillow/graphs/contributors>`_. PIL is the Python Imaging Library by Fredrik Lundh and Contributors.
|
||||||
|
|
||||||
|
Pillow for enterprise is available via the Tidelift Subscription. `Learn more <https://tidelift.com/subscription/pkg/pypi-pillow?utm_source=pillow&utm_medium=referral&utm_campaign=docs>`_.
|
||||||
|
|
||||||
.. image:: https://zenodo.org/badge/17549/python-pillow/Pillow.svg
|
.. image:: https://zenodo.org/badge/17549/python-pillow/Pillow.svg
|
||||||
:target: https://zenodo.org/badge/latestdoi/17549/python-pillow/Pillow
|
:target: https://zenodo.org/badge/latestdoi/17549/python-pillow/Pillow
|
||||||
|
|
||||||
|
|
|
@ -169,7 +169,7 @@ Many of Pillow's features require external libraries:
|
||||||
|
|
||||||
* **libimagequant** provides improved color quantization
|
* **libimagequant** provides improved color quantization
|
||||||
|
|
||||||
* Pillow has been tested with libimagequant **2.6-2.12.3**
|
* Pillow has been tested with libimagequant **2.6-2.12.5**
|
||||||
* Libimagequant is licensed GPLv3, which is more restrictive than
|
* Libimagequant is licensed GPLv3, which is more restrictive than
|
||||||
the Pillow license, therefore we will not be distributing binaries
|
the Pillow license, therefore we will not be distributing binaries
|
||||||
with libimagequant support enabled.
|
with libimagequant support enabled.
|
||||||
|
|
|
@ -52,7 +52,7 @@ Functions
|
||||||
.. warning::
|
.. warning::
|
||||||
To protect against potential DOS attacks caused by "`decompression bombs`_" (i.e. malicious files
|
To protect against potential DOS attacks caused by "`decompression bombs`_" (i.e. malicious files
|
||||||
which decompress into a huge amount of data and are designed to crash or cause disruption by using up
|
which decompress into a huge amount of data and are designed to crash or cause disruption by using up
|
||||||
a lot of memory), Pillow will issue a `DecompressionBombWarning` if the image is over a certain
|
a lot of memory), Pillow will issue a ``DecompressionBombWarning`` if the image is over a certain
|
||||||
limit. If desired, the warning can be turned into an error with
|
limit. If desired, the warning can be turned into an error with
|
||||||
``warnings.simplefilter('error', Image.DecompressionBombWarning)`` or suppressed entirely with
|
``warnings.simplefilter('error', Image.DecompressionBombWarning)`` or suppressed entirely with
|
||||||
``warnings.simplefilter('ignore', Image.DecompressionBombWarning)``. See also `the logging
|
``warnings.simplefilter('ignore', Image.DecompressionBombWarning)``. See also `the logging
|
||||||
|
@ -262,7 +262,7 @@ Instances of the :py:class:`Image` class have the following attributes:
|
||||||
.. py:attribute:: filename
|
.. py:attribute:: filename
|
||||||
|
|
||||||
The filename or path of the source file. Only images created with the
|
The filename or path of the source file. Only images created with the
|
||||||
factory function `open` have a filename attribute. If the input is a
|
factory function ``open`` have a filename attribute. If the input is a
|
||||||
file like object, the filename attribute is set to an empty string.
|
file like object, the filename attribute is set to an empty string.
|
||||||
|
|
||||||
:type: :py:class:`string`
|
:type: :py:class:`string`
|
||||||
|
|
|
@ -33,13 +33,13 @@ can be easily displayed in a chromaticity diagram, for example).
|
||||||
.. py:attribute:: version
|
.. py:attribute:: version
|
||||||
|
|
||||||
The version number of the ICC standard that this profile follows
|
The version number of the ICC standard that this profile follows
|
||||||
(e.g. `2.0`).
|
(e.g. ``2.0``).
|
||||||
|
|
||||||
:type: :py:class:`float`
|
:type: :py:class:`float`
|
||||||
|
|
||||||
.. py:attribute:: icc_version
|
.. py:attribute:: icc_version
|
||||||
|
|
||||||
Same as `version`, but in encoded format (see 7.2.4 of ICC.1:2010).
|
Same as ``version``, but in encoded format (see 7.2.4 of ICC.1:2010).
|
||||||
|
|
||||||
.. py:attribute:: device_class
|
.. py:attribute:: device_class
|
||||||
|
|
||||||
|
|
|
@ -255,7 +255,7 @@ Methods
|
||||||
|
|
||||||
Draw a shape.
|
Draw a shape.
|
||||||
|
|
||||||
.. py:method:: PIL.ImageDraw.ImageDraw.text(xy, text, fill=None, font=None, anchor=None, spacing=0, align="left", direction=None, features=None, language=None)
|
.. py:method:: PIL.ImageDraw.ImageDraw.text(xy, text, fill=None, font=None, anchor=None, spacing=0, align="left", direction=None, features=None, language=None, stroke_width=0, stroke_fill=None)
|
||||||
|
|
||||||
Draws the string at the given position.
|
Draws the string at the given position.
|
||||||
|
|
||||||
|
@ -297,6 +297,15 @@ Methods
|
||||||
|
|
||||||
.. versionadded:: 6.0.0
|
.. versionadded:: 6.0.0
|
||||||
|
|
||||||
|
:param stroke_width: The width of the text stroke.
|
||||||
|
|
||||||
|
.. versionadded:: 6.2.0
|
||||||
|
|
||||||
|
:param stroke_fill: Color to use for the text stroke. If not given, will default to
|
||||||
|
the ``fill`` parameter.
|
||||||
|
|
||||||
|
.. versionadded:: 6.2.0
|
||||||
|
|
||||||
.. py:method:: PIL.ImageDraw.ImageDraw.multiline_text(xy, text, fill=None, font=None, anchor=None, spacing=0, align="left", direction=None, features=None, language=None)
|
.. py:method:: PIL.ImageDraw.ImageDraw.multiline_text(xy, text, fill=None, font=None, anchor=None, spacing=0, align="left", direction=None, features=None, language=None)
|
||||||
|
|
||||||
Draws the string at the given position.
|
Draws the string at the given position.
|
||||||
|
@ -336,7 +345,7 @@ Methods
|
||||||
|
|
||||||
.. versionadded:: 6.0.0
|
.. versionadded:: 6.0.0
|
||||||
|
|
||||||
.. py:method:: PIL.ImageDraw.ImageDraw.textsize(text, font=None, spacing=4, direction=None, features=None, language=None)
|
.. py:method:: PIL.ImageDraw.ImageDraw.textsize(text, font=None, spacing=4, direction=None, features=None, language=None, stroke_width=0)
|
||||||
|
|
||||||
Return the size of the given string, in pixels.
|
Return the size of the given string, in pixels.
|
||||||
|
|
||||||
|
@ -372,7 +381,11 @@ Methods
|
||||||
|
|
||||||
.. versionadded:: 6.0.0
|
.. versionadded:: 6.0.0
|
||||||
|
|
||||||
.. py:method:: PIL.ImageDraw.ImageDraw.multiline_textsize(text, font=None, spacing=4, direction=None, features=None, language=None)
|
:param stroke_width: The width of the text stroke.
|
||||||
|
|
||||||
|
.. versionadded:: 6.2.0
|
||||||
|
|
||||||
|
.. py:method:: PIL.ImageDraw.ImageDraw.multiline_textsize(text, font=None, spacing=4, direction=None, features=None, language=None, stroke_width=0)
|
||||||
|
|
||||||
Return the size of the given string, in pixels.
|
Return the size of the given string, in pixels.
|
||||||
|
|
||||||
|
@ -408,6 +421,10 @@ Methods
|
||||||
|
|
||||||
.. versionadded:: 6.0.0
|
.. versionadded:: 6.0.0
|
||||||
|
|
||||||
|
:param stroke_width: The width of the text stroke.
|
||||||
|
|
||||||
|
.. versionadded:: 6.2.0
|
||||||
|
|
||||||
.. py:method:: PIL.ImageDraw.getdraw(im=None, hints=None)
|
.. py:method:: PIL.ImageDraw.getdraw(im=None, hints=None)
|
||||||
|
|
||||||
.. warning:: This method is experimental.
|
.. warning:: This method is experimental.
|
||||||
|
|
|
@ -11,7 +11,7 @@ or the clipboard to a PIL image memory.
|
||||||
|
|
||||||
.. versionadded:: 1.1.3
|
.. versionadded:: 1.1.3
|
||||||
|
|
||||||
.. py:function:: PIL.ImageGrab.grab(bbox=None, include_layered_windows=False)
|
.. py:function:: PIL.ImageGrab.grab(bbox=None, include_layered_windows=False, all_screens=False)
|
||||||
|
|
||||||
Take a snapshot of the screen. The pixels inside the bounding box are
|
Take a snapshot of the screen. The pixels inside the bounding box are
|
||||||
returned as an "RGB" image on Windows or "RGBA" on macOS.
|
returned as an "RGB" image on Windows or "RGBA" on macOS.
|
||||||
|
@ -20,7 +20,13 @@ or the clipboard to a PIL image memory.
|
||||||
.. versionadded:: 1.1.3 (Windows), 3.0.0 (macOS)
|
.. versionadded:: 1.1.3 (Windows), 3.0.0 (macOS)
|
||||||
|
|
||||||
:param bbox: What region to copy. Default is the entire screen.
|
:param bbox: What region to copy. Default is the entire screen.
|
||||||
|
Note that on Windows OS, the top-left point may be negative if ``all_screens=True`` is used.
|
||||||
:param include_layered_windows: Includes layered windows. Windows OS only.
|
:param include_layered_windows: Includes layered windows. Windows OS only.
|
||||||
|
|
||||||
|
.. versionadded:: 6.1.0
|
||||||
|
:param all_screens: Capture all monitors. Windows OS only.
|
||||||
|
|
||||||
|
.. versionadded:: 6.2.0
|
||||||
:return: An image
|
:return: An image
|
||||||
|
|
||||||
.. py:function:: PIL.ImageGrab.grabclipboard()
|
.. py:function:: PIL.ImageGrab.grabclipboard()
|
||||||
|
|
|
@ -53,7 +53,7 @@ vector data. Path objects can be passed to the methods on the
|
||||||
Converts the path to a Python list [(x, y), …].
|
Converts the path to a Python list [(x, y), …].
|
||||||
|
|
||||||
:param flat: By default, this function returns a list of 2-tuples
|
:param flat: By default, this function returns a list of 2-tuples
|
||||||
[(x, y), ...]. If this argument is `True`, it
|
[(x, y), ...]. If this argument is ``True``, it
|
||||||
returns a flat list [x, y, ...] instead.
|
returns a flat list [x, y, ...] instead.
|
||||||
:return: A list of coordinates. See **flat**.
|
:return: A list of coordinates. See **flat**.
|
||||||
|
|
||||||
|
|
|
@ -27,7 +27,7 @@ Image resizing filters
|
||||||
----------------------
|
----------------------
|
||||||
|
|
||||||
Image resizing methods :py:meth:`~PIL.Image.Image.resize` and
|
Image resizing methods :py:meth:`~PIL.Image.Image.resize` and
|
||||||
:py:meth:`~PIL.Image.Image.thumbnail` take a `resample` argument, which tells
|
:py:meth:`~PIL.Image.Image.thumbnail` take a ``resample`` argument, which tells
|
||||||
which filter should be used for resampling. Possible values are:
|
which filter should be used for resampling. Possible values are:
|
||||||
:py:attr:`PIL.Image.NEAREST`, :py:attr:`PIL.Image.BILINEAR`,
|
:py:attr:`PIL.Image.NEAREST`, :py:attr:`PIL.Image.BILINEAR`,
|
||||||
:py:attr:`PIL.Image.BICUBIC` and :py:attr:`PIL.Image.ANTIALIAS`.
|
:py:attr:`PIL.Image.BICUBIC` and :py:attr:`PIL.Image.ANTIALIAS`.
|
||||||
|
|
|
@ -4,18 +4,28 @@
|
||||||
Open HTTP response objects with Image.open
|
Open HTTP response objects with Image.open
|
||||||
------------------------------------------
|
------------------------------------------
|
||||||
|
|
||||||
HTTP response objects returned from `urllib2.urlopen(url)` or `requests.get(url, stream=True).raw` are 'file-like' but do not support `.seek()` operations. As a result PIL was unable to open them as images, requiring a wrap in `cStringIO` or `BytesIO`.
|
HTTP response objects returned from ``urllib2.urlopen(url)`` or
|
||||||
|
``requests.get(url, stream=True).raw`` are 'file-like' but do not support ``.seek()``
|
||||||
|
operations. As a result PIL was unable to open them as images, requiring a wrap in
|
||||||
|
``cStringIO`` or ``BytesIO``.
|
||||||
|
|
||||||
Now new functionality has been added to `Image.open()` by way of an `.seek(0)` check and catch on exception `AttributeError` or `io.UnsupportedOperation`. If this is caught we attempt to wrap the object using `io.BytesIO` (which will only work on buffer-file-like objects).
|
Now new functionality has been added to ``Image.open()`` by way of an ``.seek(0)`` check and
|
||||||
|
catch on exception ``AttributeError`` or ``io.UnsupportedOperation``. If this is caught we
|
||||||
|
attempt to wrap the object using ``io.BytesIO`` (which will only work on buffer-file-like
|
||||||
|
objects).
|
||||||
|
|
||||||
This allows opening of files using both `urllib2` and `requests`, e.g.::
|
This allows opening of files using both ``urllib2`` and ``requests``, e.g.::
|
||||||
|
|
||||||
Image.open(urllib2.urlopen(url))
|
Image.open(urllib2.urlopen(url))
|
||||||
Image.open(requests.get(url, stream=True).raw)
|
Image.open(requests.get(url, stream=True).raw)
|
||||||
|
|
||||||
If the response uses content-encoding (compression, either gzip or deflate) then this will fail as both the urllib2 and requests raw file object will produce compressed data in that case. Using Content-Encoding on images is rather non-sensical as most images are already compressed, but it can still happen.
|
If the response uses content-encoding (compression, either gzip or deflate) then this
|
||||||
|
will fail as both the urllib2 and requests raw file object will produce compressed data
|
||||||
|
in that case. Using Content-Encoding on images is rather non-sensical as most images are
|
||||||
|
already compressed, but it can still happen.
|
||||||
|
|
||||||
For requests the work-around is to set the decode_content attribute on the raw object to True::
|
For requests the work-around is to set the decode_content attribute on the raw object to
|
||||||
|
True::
|
||||||
|
|
||||||
response = requests.get(url, stream=True)
|
response = requests.get(url, stream=True)
|
||||||
response.raw.decode_content = True
|
response.raw.decode_content = True
|
||||||
|
|
|
@ -5,8 +5,8 @@
|
||||||
Saving Multipage Images
|
Saving Multipage Images
|
||||||
-----------------------
|
-----------------------
|
||||||
|
|
||||||
There is now support for saving multipage images in the `GIF` and
|
There is now support for saving multipage images in the ``GIF`` and
|
||||||
`PDF` formats. To enable this functionality, pass in `save_all=True`
|
``PDF`` formats. To enable this functionality, pass in ``save_all=True``
|
||||||
as a keyword argument to the save::
|
as a keyword argument to the save::
|
||||||
|
|
||||||
im.save('test.pdf', save_all=True)
|
im.save('test.pdf', save_all=True)
|
||||||
|
@ -37,7 +37,7 @@ have been removed in this release::
|
||||||
ImageDraw.setink()
|
ImageDraw.setink()
|
||||||
ImageDraw.setfill()
|
ImageDraw.setfill()
|
||||||
The ImageFileIO module
|
The ImageFileIO module
|
||||||
The ImageFont.FreeTypeFont and ImageFont.truetype `file` keyword arg
|
The ImageFont.FreeTypeFont and ImageFont.truetype ``file`` keyword arg
|
||||||
The ImagePalette private _make functions
|
The ImagePalette private _make functions
|
||||||
ImageWin.fromstring()
|
ImageWin.fromstring()
|
||||||
ImageWin.tostring()
|
ImageWin.tostring()
|
||||||
|
|
|
@ -5,8 +5,8 @@
|
||||||
ImageDraw arc, chord and pieslice can now use floats
|
ImageDraw arc, chord and pieslice can now use floats
|
||||||
----------------------------------------------------
|
----------------------------------------------------
|
||||||
|
|
||||||
There is no longer a need to ensure that the start and end arguments for `arc`,
|
There is no longer a need to ensure that the start and end arguments for ``arc``,
|
||||||
`chord` and `pieslice` are integers.
|
``chord`` and ``pieslice`` are integers.
|
||||||
|
|
||||||
Note that these numbers are not simply rounded internally, but are actually
|
Note that these numbers are not simply rounded internally, but are actually
|
||||||
utilised in the drawing process.
|
utilised in the drawing process.
|
||||||
|
|
90
docs/releasenotes/6.2.0.rst
Normal file
90
docs/releasenotes/6.2.0.rst
Normal file
|
@ -0,0 +1,90 @@
|
||||||
|
6.2.0
|
||||||
|
-----
|
||||||
|
|
||||||
|
API Additions
|
||||||
|
=============
|
||||||
|
|
||||||
|
Text stroking
|
||||||
|
^^^^^^^^^^^^^
|
||||||
|
|
||||||
|
``stroke_width`` and ``stroke_fill`` arguments have been added to text drawing
|
||||||
|
operations. They allow text to be outlined, setting the width of the stroke and
|
||||||
|
and the color respectively. If not provided, ``stroke_fill`` will default to
|
||||||
|
the ``fill`` parameter.
|
||||||
|
|
||||||
|
.. code-block:: python
|
||||||
|
|
||||||
|
from PIL import Image, ImageDraw, ImageFont
|
||||||
|
|
||||||
|
font = ImageFont.truetype("Tests/fonts/FreeMono.ttf", 40)
|
||||||
|
font.getsize_multiline("A", stroke_width=2)
|
||||||
|
font.getsize("ABC\nAaaa", stroke_width=2)
|
||||||
|
|
||||||
|
im = Image.new("RGB", (100, 100))
|
||||||
|
draw = ImageDraw.Draw(im)
|
||||||
|
draw.textsize("A", font, stroke_width=2)
|
||||||
|
draw.multiline_textsize("ABC\nAaaa", font, stroke_width=2)
|
||||||
|
draw.text((10, 10), "A", "#f00", font, stroke_width=2, stroke_fill="#0f0")
|
||||||
|
draw.multiline_text((10, 10), "A\nB", "#f00", font,
|
||||||
|
stroke_width=2, stroke_fill="#0f0")
|
||||||
|
|
||||||
|
For example,
|
||||||
|
|
||||||
|
.. code-block:: python
|
||||||
|
|
||||||
|
from PIL import Image, ImageDraw, ImageFont
|
||||||
|
|
||||||
|
im = Image.new("RGB", (120, 130))
|
||||||
|
draw = ImageDraw.Draw(im)
|
||||||
|
font = ImageFont.truetype("Tests/fonts/FreeMono.ttf", 120)
|
||||||
|
draw.text((10, 10), "A", "#f00", font, stroke_width=2, stroke_fill="#0f0")
|
||||||
|
|
||||||
|
|
||||||
|
creates the following image:
|
||||||
|
|
||||||
|
.. image:: ../../Tests/images/imagedraw_stroke_different.png
|
||||||
|
|
||||||
|
API Changes
|
||||||
|
===========
|
||||||
|
|
||||||
|
Image.getexif
|
||||||
|
^^^^^^^^^^^^^
|
||||||
|
|
||||||
|
To allow for lazy loading of Exif data, ``Image.getexif()`` now returns a
|
||||||
|
shared instance of ``Image.Exif``.
|
||||||
|
|
||||||
|
Deprecations
|
||||||
|
^^^^^^^^^^^^
|
||||||
|
|
||||||
|
Python 2.7
|
||||||
|
~~~~~~~~~~
|
||||||
|
|
||||||
|
Python 2.7 reaches end-of-life on 2020-01-01.
|
||||||
|
|
||||||
|
Pillow 7.0.0 will be released on 2020-01-01 and will drop support for Python
|
||||||
|
2.7, making Pillow 6.2.x the last release series to support Python 2.
|
||||||
|
|
||||||
|
Image.frombuffer
|
||||||
|
~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
|
There has been a longstanding warning that the defaults of ``Image.frombuffer``
|
||||||
|
may change in the future for the "raw" decoder. The change will now take place
|
||||||
|
in Pillow 7.0.
|
||||||
|
|
||||||
|
Other Changes
|
||||||
|
=============
|
||||||
|
|
||||||
|
Removed bdist_wininst .exe installers
|
||||||
|
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||||
|
|
||||||
|
.exe installers fell out of favour with PEP 527, and will be deprecated in
|
||||||
|
Python 3.8. Pillow will no longer be distributing them. Wheels should be used
|
||||||
|
instead.
|
||||||
|
|
||||||
|
Flags for libwebp in wheels
|
||||||
|
^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||||
|
|
||||||
|
When building libwebp for inclusion in wheels, Pillow now adds the -O3 and
|
||||||
|
-DNDEBUG CFLAGS. These flags would be used by default if building libwebp
|
||||||
|
without debugging, and using them fixes a significant decrease in speed when
|
||||||
|
a wheel-installed copy of Pillow performs libwebp operations.
|
|
@ -6,6 +6,7 @@ Release Notes
|
||||||
.. toctree::
|
.. toctree::
|
||||||
:maxdepth: 2
|
:maxdepth: 2
|
||||||
|
|
||||||
|
6.2.0
|
||||||
6.1.0
|
6.1.0
|
||||||
6.0.0
|
6.0.0
|
||||||
5.4.1
|
5.4.1
|
||||||
|
|
4
setup.py
4
setup.py
|
@ -432,7 +432,9 @@ class pil_build_ext(build_ext):
|
||||||
# pythonX.Y.dll.a is in the /usr/lib/pythonX.Y/config directory
|
# pythonX.Y.dll.a is in the /usr/lib/pythonX.Y/config directory
|
||||||
_add_directory(
|
_add_directory(
|
||||||
library_dirs,
|
library_dirs,
|
||||||
os.path.join("/usr/lib", "python%s" % sys.version[:3], "config"),
|
os.path.join(
|
||||||
|
"/usr/lib", "python{}.{}".format(*sys.version_info), "config"
|
||||||
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
elif sys.platform == "darwin":
|
elif sys.platform == "darwin":
|
||||||
|
|
|
@ -489,6 +489,11 @@ def _write_multiple_frames(im, fp, palette):
|
||||||
offset = frame_data["bbox"][:2]
|
offset = frame_data["bbox"][:2]
|
||||||
_write_frame_data(fp, im_frame, offset, frame_data["encoderinfo"])
|
_write_frame_data(fp, im_frame, offset, frame_data["encoderinfo"])
|
||||||
return True
|
return True
|
||||||
|
elif "duration" in im.encoderinfo and isinstance(
|
||||||
|
im.encoderinfo["duration"], (list, tuple)
|
||||||
|
):
|
||||||
|
# Since multiple frames will not be written, add together the frame durations
|
||||||
|
im.encoderinfo["duration"] = sum(im.encoderinfo["duration"])
|
||||||
|
|
||||||
|
|
||||||
def _save_all(im, fp, filename):
|
def _save_all(im, fp, filename):
|
||||||
|
|
|
@ -263,6 +263,9 @@ class IcoImageFile(ImageFile.ImageFile):
|
||||||
|
|
||||||
Handles classic, XP and Vista icon formats.
|
Handles classic, XP and Vista icon formats.
|
||||||
|
|
||||||
|
When saving, PNG compression is used. Support for this was only added in
|
||||||
|
Windows Vista.
|
||||||
|
|
||||||
This plugin is a refactored version of Win32IconImagePlugin by Bryan Davis
|
This plugin is a refactored version of Win32IconImagePlugin by Bryan Davis
|
||||||
<casadebender@gmail.com>.
|
<casadebender@gmail.com>.
|
||||||
https://code.google.com/archive/p/casadebender/wikis/Win32IconImagePlugin.wiki
|
https://code.google.com/archive/p/casadebender/wikis/Win32IconImagePlugin.wiki
|
||||||
|
|
108
src/PIL/Image.py
108
src/PIL/Image.py
|
@ -555,6 +555,7 @@ class Image(object):
|
||||||
self.category = NORMAL
|
self.category = NORMAL
|
||||||
self.readonly = 0
|
self.readonly = 0
|
||||||
self.pyaccess = None
|
self.pyaccess = None
|
||||||
|
self._exif = None
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def width(self):
|
def width(self):
|
||||||
|
@ -1324,10 +1325,10 @@ class Image(object):
|
||||||
return self.im.getextrema()
|
return self.im.getextrema()
|
||||||
|
|
||||||
def getexif(self):
|
def getexif(self):
|
||||||
exif = Exif()
|
if self._exif is None:
|
||||||
if "exif" in self.info:
|
self._exif = Exif()
|
||||||
exif.load(self.info["exif"])
|
self._exif.load(self.info.get("exif"))
|
||||||
return exif
|
return self._exif
|
||||||
|
|
||||||
def getim(self):
|
def getim(self):
|
||||||
"""
|
"""
|
||||||
|
@ -2073,10 +2074,10 @@ class Image(object):
|
||||||
|
|
||||||
if open_fp:
|
if open_fp:
|
||||||
if params.get("append", False):
|
if params.get("append", False):
|
||||||
fp = builtins.open(filename, "r+b")
|
|
||||||
else:
|
|
||||||
# Open also for reading ("+"), because TIFF save_all
|
# Open also for reading ("+"), because TIFF save_all
|
||||||
# writer needs to go back and edit the written data.
|
# writer needs to go back and edit the written data.
|
||||||
|
fp = builtins.open(filename, "r+b")
|
||||||
|
else:
|
||||||
fp = builtins.open(filename, "w+b")
|
fp = builtins.open(filename, "w+b")
|
||||||
|
|
||||||
try:
|
try:
|
||||||
|
@ -2109,15 +2110,15 @@ class Image(object):
|
||||||
Displays this image. This method is mainly intended for
|
Displays this image. This method is mainly intended for
|
||||||
debugging purposes.
|
debugging purposes.
|
||||||
|
|
||||||
On Unix platforms, this method saves the image to a temporary
|
The image is first saved to a temporary file. By default, it will be in
|
||||||
PPM file, and calls the **display**, **eog** or **xv**
|
PNG format.
|
||||||
utility, depending on which one can be found.
|
|
||||||
|
|
||||||
On macOS, this method saves the image to a temporary PNG file, and
|
On Unix, the image is then opened using the **display**, **eog** or
|
||||||
opens it with the native Preview application.
|
**xv** utility, depending on which one can be found.
|
||||||
|
|
||||||
On Windows, it saves the image to a temporary BMP file, and uses
|
On macOS, the image is opened with the native Preview application.
|
||||||
the standard BMP display utility to show it (usually Paint).
|
|
||||||
|
On Windows, the image is opened with the standard PNG display utility.
|
||||||
|
|
||||||
:param title: Optional title to use for the image window,
|
:param title: Optional title to use for the image window,
|
||||||
where possible.
|
where possible.
|
||||||
|
@ -2592,7 +2593,7 @@ def frombuffer(mode, size, data, decoder_name="raw", *args):
|
||||||
if decoder_name == "raw":
|
if decoder_name == "raw":
|
||||||
if args == ():
|
if args == ():
|
||||||
warnings.warn(
|
warnings.warn(
|
||||||
"the frombuffer defaults may change in a future release; "
|
"the frombuffer defaults will change in Pillow 7.0.0; "
|
||||||
"for portability, change the call to read:\n"
|
"for portability, change the call to read:\n"
|
||||||
" frombuffer(mode, size, data, 'raw', mode, 0, 1)",
|
" frombuffer(mode, size, data, 'raw', mode, 0, 1)",
|
||||||
RuntimeWarning,
|
RuntimeWarning,
|
||||||
|
@ -3137,11 +3138,10 @@ class Exif(MutableMapping):
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
self._data = {}
|
self._data = {}
|
||||||
self._ifds = {}
|
self._ifds = {}
|
||||||
|
self._info = None
|
||||||
|
self._loaded_exif = None
|
||||||
|
|
||||||
def _fixup_dict(self, src_dict):
|
def _fixup(self, value):
|
||||||
# Helper function for _getexif()
|
|
||||||
# returns a dict with any single item tuples/lists as individual values
|
|
||||||
def _fixup(value):
|
|
||||||
try:
|
try:
|
||||||
if len(value) == 1 and not isinstance(value, dict):
|
if len(value) == 1 and not isinstance(value, dict):
|
||||||
return value[0]
|
return value[0]
|
||||||
|
@ -3149,13 +3149,16 @@ class Exif(MutableMapping):
|
||||||
pass
|
pass
|
||||||
return value
|
return value
|
||||||
|
|
||||||
return {k: _fixup(v) for k, v in src_dict.items()}
|
def _fixup_dict(self, src_dict):
|
||||||
|
# Helper function for _getexif()
|
||||||
|
# returns a dict with any single item tuples/lists as individual values
|
||||||
|
return {k: self._fixup(v) for k, v in src_dict.items()}
|
||||||
|
|
||||||
def _get_ifd_dict(self, tag):
|
def _get_ifd_dict(self, tag):
|
||||||
try:
|
try:
|
||||||
# an offset pointer to the location of the nested embedded IFD.
|
# an offset pointer to the location of the nested embedded IFD.
|
||||||
# It should be a long, but may be corrupted.
|
# It should be a long, but may be corrupted.
|
||||||
self.fp.seek(self._data[tag])
|
self.fp.seek(self[tag])
|
||||||
except (KeyError, TypeError):
|
except (KeyError, TypeError):
|
||||||
pass
|
pass
|
||||||
else:
|
else:
|
||||||
|
@ -3172,16 +3175,24 @@ class Exif(MutableMapping):
|
||||||
|
|
||||||
# The EXIF record consists of a TIFF file embedded in a JPEG
|
# The EXIF record consists of a TIFF file embedded in a JPEG
|
||||||
# application marker (!).
|
# application marker (!).
|
||||||
|
if data == self._loaded_exif:
|
||||||
|
return
|
||||||
|
self._loaded_exif = data
|
||||||
|
self._data.clear()
|
||||||
|
self._ifds.clear()
|
||||||
|
self._info = None
|
||||||
|
if not data:
|
||||||
|
return
|
||||||
|
|
||||||
self.fp = io.BytesIO(data[6:])
|
self.fp = io.BytesIO(data[6:])
|
||||||
self.head = self.fp.read(8)
|
self.head = self.fp.read(8)
|
||||||
# process dictionary
|
# process dictionary
|
||||||
from . import TiffImagePlugin
|
from . import TiffImagePlugin
|
||||||
|
|
||||||
info = TiffImagePlugin.ImageFileDirectory_v1(self.head)
|
self._info = TiffImagePlugin.ImageFileDirectory_v1(self.head)
|
||||||
self.endian = info._endian
|
self.endian = self._info._endian
|
||||||
self.fp.seek(info.next)
|
self.fp.seek(self._info.next)
|
||||||
info.load(self.fp)
|
self._info.load(self.fp)
|
||||||
self._data = dict(self._fixup_dict(info))
|
|
||||||
|
|
||||||
# get EXIF extension
|
# get EXIF extension
|
||||||
ifd = self._get_ifd_dict(0x8769)
|
ifd = self._get_ifd_dict(0x8769)
|
||||||
|
@ -3189,12 +3200,6 @@ class Exif(MutableMapping):
|
||||||
self._data.update(ifd)
|
self._data.update(ifd)
|
||||||
self._ifds[0x8769] = 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):
|
def tobytes(self, offset=0):
|
||||||
from . import TiffImagePlugin
|
from . import TiffImagePlugin
|
||||||
|
|
||||||
|
@ -3203,19 +3208,20 @@ class Exif(MutableMapping):
|
||||||
else:
|
else:
|
||||||
head = b"MM\x00\x2A\x00\x00\x00\x08"
|
head = b"MM\x00\x2A\x00\x00\x00\x08"
|
||||||
ifd = TiffImagePlugin.ImageFileDirectory_v2(ifh=head)
|
ifd = TiffImagePlugin.ImageFileDirectory_v2(ifh=head)
|
||||||
for tag, value in self._data.items():
|
for tag, value in self.items():
|
||||||
ifd[tag] = value
|
ifd[tag] = value
|
||||||
return b"Exif\x00\x00" + head + ifd.tobytes(offset)
|
return b"Exif\x00\x00" + head + ifd.tobytes(offset)
|
||||||
|
|
||||||
def get_ifd(self, tag):
|
def get_ifd(self, tag):
|
||||||
if tag not in self._ifds and tag in self._data:
|
if tag not in self._ifds and tag in self:
|
||||||
if tag == 0xA005: # interop
|
if tag in [0x8825, 0xA005]:
|
||||||
|
# gpsinfo, interop
|
||||||
self._ifds[tag] = self._get_ifd_dict(tag)
|
self._ifds[tag] = self._get_ifd_dict(tag)
|
||||||
elif tag == 0x927C: # makernote
|
elif tag == 0x927C: # makernote
|
||||||
from .TiffImagePlugin import ImageFileDirectory_v2
|
from .TiffImagePlugin import ImageFileDirectory_v2
|
||||||
|
|
||||||
if self._data[0x927C][:8] == b"FUJIFILM":
|
if self[0x927C][:8] == b"FUJIFILM":
|
||||||
exif_data = self._data[0x927C]
|
exif_data = self[0x927C]
|
||||||
ifd_offset = i32le(exif_data[8:12])
|
ifd_offset = i32le(exif_data[8:12])
|
||||||
ifd_data = exif_data[ifd_offset:]
|
ifd_data = exif_data[ifd_offset:]
|
||||||
|
|
||||||
|
@ -3252,8 +3258,8 @@ class Exif(MutableMapping):
|
||||||
ImageFileDirectory_v2(), data, False
|
ImageFileDirectory_v2(), data, False
|
||||||
)
|
)
|
||||||
self._ifds[0x927C] = dict(self._fixup_dict(makernote))
|
self._ifds[0x927C] = dict(self._fixup_dict(makernote))
|
||||||
elif self._data.get(0x010F) == "Nintendo":
|
elif self.get(0x010F) == "Nintendo":
|
||||||
ifd_data = self._data[0x927C]
|
ifd_data = self[0x927C]
|
||||||
|
|
||||||
makernote = {}
|
makernote = {}
|
||||||
for i in range(0, struct.unpack(">H", ifd_data[:2])[0]):
|
for i in range(0, struct.unpack(">H", ifd_data[:2])[0]):
|
||||||
|
@ -3291,16 +3297,29 @@ class Exif(MutableMapping):
|
||||||
return self._ifds.get(tag, {})
|
return self._ifds.get(tag, {})
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
|
if self._info is not None:
|
||||||
|
# Load all keys into self._data
|
||||||
|
for tag in self._info.keys():
|
||||||
|
self[tag]
|
||||||
|
|
||||||
return str(self._data)
|
return str(self._data)
|
||||||
|
|
||||||
def __len__(self):
|
def __len__(self):
|
||||||
return len(self._data)
|
keys = set(self._data)
|
||||||
|
if self._info is not None:
|
||||||
|
keys.update(self._info)
|
||||||
|
return len(keys)
|
||||||
|
|
||||||
def __getitem__(self, tag):
|
def __getitem__(self, tag):
|
||||||
|
if self._info is not None and tag not in self._data and tag in self._info:
|
||||||
|
self._data[tag] = self._fixup(self._info[tag])
|
||||||
|
if tag == 0x8825:
|
||||||
|
self._data[tag] = self.get_ifd(tag)
|
||||||
|
del self._info[tag]
|
||||||
return self._data[tag]
|
return self._data[tag]
|
||||||
|
|
||||||
def __contains__(self, tag):
|
def __contains__(self, tag):
|
||||||
return tag in self._data
|
return tag in self._data or (self._info is not None and tag in self._info)
|
||||||
|
|
||||||
if not py3:
|
if not py3:
|
||||||
|
|
||||||
|
@ -3308,10 +3327,17 @@ class Exif(MutableMapping):
|
||||||
return tag in self
|
return tag in self
|
||||||
|
|
||||||
def __setitem__(self, tag, value):
|
def __setitem__(self, tag, value):
|
||||||
|
if self._info is not None and tag in self._info:
|
||||||
|
del self._info[tag]
|
||||||
self._data[tag] = value
|
self._data[tag] = value
|
||||||
|
|
||||||
def __delitem__(self, tag):
|
def __delitem__(self, tag):
|
||||||
|
if self._info is not None and tag in self._info:
|
||||||
|
del self._info[tag]
|
||||||
del self._data[tag]
|
del self._data[tag]
|
||||||
|
|
||||||
def __iter__(self):
|
def __iter__(self):
|
||||||
return iter(set(self._data))
|
keys = set(self._data)
|
||||||
|
if self._info is not None:
|
||||||
|
keys.update(self._info)
|
||||||
|
return iter(keys)
|
||||||
|
|
|
@ -261,24 +261,95 @@ class ImageDraw(object):
|
||||||
|
|
||||||
return text.split(split_character)
|
return text.split(split_character)
|
||||||
|
|
||||||
def text(self, xy, text, fill=None, font=None, anchor=None, *args, **kwargs):
|
def text(
|
||||||
|
self,
|
||||||
|
xy,
|
||||||
|
text,
|
||||||
|
fill=None,
|
||||||
|
font=None,
|
||||||
|
anchor=None,
|
||||||
|
spacing=4,
|
||||||
|
align="left",
|
||||||
|
direction=None,
|
||||||
|
features=None,
|
||||||
|
language=None,
|
||||||
|
stroke_width=0,
|
||||||
|
stroke_fill=None,
|
||||||
|
*args,
|
||||||
|
**kwargs
|
||||||
|
):
|
||||||
if self._multiline_check(text):
|
if self._multiline_check(text):
|
||||||
return self.multiline_text(xy, text, fill, font, anchor, *args, **kwargs)
|
return self.multiline_text(
|
||||||
ink, fill = self._getink(fill)
|
xy,
|
||||||
|
text,
|
||||||
|
fill,
|
||||||
|
font,
|
||||||
|
anchor,
|
||||||
|
spacing,
|
||||||
|
align,
|
||||||
|
direction,
|
||||||
|
features,
|
||||||
|
language,
|
||||||
|
stroke_width,
|
||||||
|
stroke_fill,
|
||||||
|
)
|
||||||
|
|
||||||
if font is None:
|
if font is None:
|
||||||
font = self.getfont()
|
font = self.getfont()
|
||||||
|
|
||||||
|
def getink(fill):
|
||||||
|
ink, fill = self._getink(fill)
|
||||||
if ink is None:
|
if ink is None:
|
||||||
ink = fill
|
return fill
|
||||||
if ink is not None:
|
return ink
|
||||||
|
|
||||||
|
def draw_text(ink, stroke_width=0, stroke_offset=None):
|
||||||
|
coord = xy
|
||||||
try:
|
try:
|
||||||
mask, offset = font.getmask2(text, self.fontmode, *args, **kwargs)
|
mask, offset = font.getmask2(
|
||||||
xy = xy[0] + offset[0], xy[1] + offset[1]
|
text,
|
||||||
|
self.fontmode,
|
||||||
|
direction=direction,
|
||||||
|
features=features,
|
||||||
|
language=language,
|
||||||
|
stroke_width=stroke_width,
|
||||||
|
*args,
|
||||||
|
**kwargs
|
||||||
|
)
|
||||||
|
coord = coord[0] + offset[0], coord[1] + offset[1]
|
||||||
except AttributeError:
|
except AttributeError:
|
||||||
try:
|
try:
|
||||||
mask = font.getmask(text, self.fontmode, *args, **kwargs)
|
mask = font.getmask(
|
||||||
|
text,
|
||||||
|
self.fontmode,
|
||||||
|
direction,
|
||||||
|
features,
|
||||||
|
language,
|
||||||
|
stroke_width,
|
||||||
|
*args,
|
||||||
|
**kwargs
|
||||||
|
)
|
||||||
except TypeError:
|
except TypeError:
|
||||||
mask = font.getmask(text)
|
mask = font.getmask(text)
|
||||||
self.draw.draw_bitmap(xy, mask, ink)
|
if stroke_offset:
|
||||||
|
coord = coord[0] + stroke_offset[0], coord[1] + stroke_offset[1]
|
||||||
|
self.draw.draw_bitmap(coord, mask, ink)
|
||||||
|
|
||||||
|
ink = getink(fill)
|
||||||
|
if ink is not None:
|
||||||
|
stroke_ink = None
|
||||||
|
if stroke_width:
|
||||||
|
stroke_ink = getink(stroke_fill) if stroke_fill is not None else ink
|
||||||
|
|
||||||
|
if stroke_ink is not None:
|
||||||
|
# Draw stroked text
|
||||||
|
draw_text(stroke_ink, stroke_width)
|
||||||
|
|
||||||
|
# Draw normal text
|
||||||
|
draw_text(ink, 0, (stroke_width, stroke_width))
|
||||||
|
else:
|
||||||
|
# Only draw normal text
|
||||||
|
draw_text(ink)
|
||||||
|
|
||||||
def multiline_text(
|
def multiline_text(
|
||||||
self,
|
self,
|
||||||
|
@ -292,14 +363,23 @@ class ImageDraw(object):
|
||||||
direction=None,
|
direction=None,
|
||||||
features=None,
|
features=None,
|
||||||
language=None,
|
language=None,
|
||||||
|
stroke_width=0,
|
||||||
|
stroke_fill=None,
|
||||||
):
|
):
|
||||||
widths = []
|
widths = []
|
||||||
max_width = 0
|
max_width = 0
|
||||||
lines = self._multiline_split(text)
|
lines = self._multiline_split(text)
|
||||||
line_spacing = self.textsize("A", font=font)[1] + spacing
|
line_spacing = (
|
||||||
|
self.textsize("A", font=font, stroke_width=stroke_width)[1] + spacing
|
||||||
|
)
|
||||||
for line in lines:
|
for line in lines:
|
||||||
line_width, line_height = self.textsize(
|
line_width, line_height = self.textsize(
|
||||||
line, font, direction=direction, features=features, language=language
|
line,
|
||||||
|
font,
|
||||||
|
direction=direction,
|
||||||
|
features=features,
|
||||||
|
language=language,
|
||||||
|
stroke_width=stroke_width,
|
||||||
)
|
)
|
||||||
widths.append(line_width)
|
widths.append(line_width)
|
||||||
max_width = max(max_width, line_width)
|
max_width = max(max_width, line_width)
|
||||||
|
@ -322,32 +402,50 @@ class ImageDraw(object):
|
||||||
direction=direction,
|
direction=direction,
|
||||||
features=features,
|
features=features,
|
||||||
language=language,
|
language=language,
|
||||||
|
stroke_width=stroke_width,
|
||||||
|
stroke_fill=stroke_fill,
|
||||||
)
|
)
|
||||||
top += line_spacing
|
top += line_spacing
|
||||||
left = xy[0]
|
left = xy[0]
|
||||||
|
|
||||||
def textsize(
|
def textsize(
|
||||||
self, text, font=None, spacing=4, direction=None, features=None, language=None
|
self,
|
||||||
|
text,
|
||||||
|
font=None,
|
||||||
|
spacing=4,
|
||||||
|
direction=None,
|
||||||
|
features=None,
|
||||||
|
language=None,
|
||||||
|
stroke_width=0,
|
||||||
):
|
):
|
||||||
"""Get the size of a given string, in pixels."""
|
"""Get the size of a given string, in pixels."""
|
||||||
if self._multiline_check(text):
|
if self._multiline_check(text):
|
||||||
return self.multiline_textsize(
|
return self.multiline_textsize(
|
||||||
text, font, spacing, direction, features, language
|
text, font, spacing, direction, features, language, stroke_width
|
||||||
)
|
)
|
||||||
|
|
||||||
if font is None:
|
if font is None:
|
||||||
font = self.getfont()
|
font = self.getfont()
|
||||||
return font.getsize(text, direction, features, language)
|
return font.getsize(text, direction, features, language, stroke_width)
|
||||||
|
|
||||||
def multiline_textsize(
|
def multiline_textsize(
|
||||||
self, text, font=None, spacing=4, direction=None, features=None, language=None
|
self,
|
||||||
|
text,
|
||||||
|
font=None,
|
||||||
|
spacing=4,
|
||||||
|
direction=None,
|
||||||
|
features=None,
|
||||||
|
language=None,
|
||||||
|
stroke_width=0,
|
||||||
):
|
):
|
||||||
max_width = 0
|
max_width = 0
|
||||||
lines = self._multiline_split(text)
|
lines = self._multiline_split(text)
|
||||||
line_spacing = self.textsize("A", font=font)[1] + spacing
|
line_spacing = (
|
||||||
|
self.textsize("A", font=font, stroke_width=stroke_width)[1] + spacing
|
||||||
|
)
|
||||||
for line in lines:
|
for line in lines:
|
||||||
line_width, line_height = self.textsize(
|
line_width, line_height = self.textsize(
|
||||||
line, font, spacing, direction, features, language
|
line, font, spacing, direction, features, language, stroke_width
|
||||||
)
|
)
|
||||||
max_width = max(max_width, line_width)
|
max_width = max(max_width, line_width)
|
||||||
return max_width, len(lines) * line_spacing - spacing
|
return max_width, len(lines) * line_spacing - spacing
|
||||||
|
@ -437,8 +535,9 @@ def floodfill(image, xy, value, border=None, thresh=0):
|
||||||
new_edge = set()
|
new_edge = set()
|
||||||
for (x, y) in edge: # 4 adjacent method
|
for (x, y) in edge: # 4 adjacent method
|
||||||
for (s, t) in ((x + 1, y), (x - 1, y), (x, y + 1), (x, y - 1)):
|
for (s, t) in ((x + 1, y), (x - 1, y), (x, y + 1), (x, y - 1)):
|
||||||
if (s, t) in full_edge:
|
# If already processed, or if a coordinate is negative, skip
|
||||||
continue # if already processed, skip
|
if (s, t) in full_edge or s < 0 or t < 0:
|
||||||
|
continue
|
||||||
try:
|
try:
|
||||||
p = pixel[s, t]
|
p = pixel[s, t]
|
||||||
except (ValueError, IndexError):
|
except (ValueError, IndexError):
|
||||||
|
|
|
@ -244,7 +244,6 @@ class ImageFile(Image.Image):
|
||||||
if LOAD_TRUNCATED_IMAGES:
|
if LOAD_TRUNCATED_IMAGES:
|
||||||
break
|
break
|
||||||
else:
|
else:
|
||||||
self.tile = []
|
|
||||||
raise IOError(
|
raise IOError(
|
||||||
"image file is truncated "
|
"image file is truncated "
|
||||||
"(%d bytes not processed)" % len(b)
|
"(%d bytes not processed)" % len(b)
|
||||||
|
|
|
@ -207,7 +207,9 @@ class FreeTypeFont(object):
|
||||||
"""
|
"""
|
||||||
return self.font.ascent, self.font.descent
|
return self.font.ascent, self.font.descent
|
||||||
|
|
||||||
def getsize(self, text, direction=None, features=None, language=None):
|
def getsize(
|
||||||
|
self, text, direction=None, features=None, language=None, stroke_width=0
|
||||||
|
):
|
||||||
"""
|
"""
|
||||||
Returns width and height (in pixels) of given text if rendered in font with
|
Returns width and height (in pixels) of given text if rendered in font with
|
||||||
provided direction, features, and language.
|
provided direction, features, and language.
|
||||||
|
@ -243,13 +245,26 @@ class FreeTypeFont(object):
|
||||||
|
|
||||||
.. versionadded:: 6.0.0
|
.. versionadded:: 6.0.0
|
||||||
|
|
||||||
|
:param stroke_width: The width of the text stroke.
|
||||||
|
|
||||||
|
.. versionadded:: 6.2.0
|
||||||
|
|
||||||
:return: (width, height)
|
:return: (width, height)
|
||||||
"""
|
"""
|
||||||
size, offset = self.font.getsize(text, direction, features, language)
|
size, offset = self.font.getsize(text, direction, features, language)
|
||||||
return (size[0] + offset[0], size[1] + offset[1])
|
return (
|
||||||
|
size[0] + stroke_width * 2 + offset[0],
|
||||||
|
size[1] + stroke_width * 2 + offset[1],
|
||||||
|
)
|
||||||
|
|
||||||
def getsize_multiline(
|
def getsize_multiline(
|
||||||
self, text, direction=None, spacing=4, features=None, language=None
|
self,
|
||||||
|
text,
|
||||||
|
direction=None,
|
||||||
|
spacing=4,
|
||||||
|
features=None,
|
||||||
|
language=None,
|
||||||
|
stroke_width=0,
|
||||||
):
|
):
|
||||||
"""
|
"""
|
||||||
Returns width and height (in pixels) of given text if rendered in font
|
Returns width and height (in pixels) of given text if rendered in font
|
||||||
|
@ -285,13 +300,19 @@ class FreeTypeFont(object):
|
||||||
|
|
||||||
.. versionadded:: 6.0.0
|
.. versionadded:: 6.0.0
|
||||||
|
|
||||||
|
:param stroke_width: The width of the text stroke.
|
||||||
|
|
||||||
|
.. versionadded:: 6.2.0
|
||||||
|
|
||||||
:return: (width, height)
|
:return: (width, height)
|
||||||
"""
|
"""
|
||||||
max_width = 0
|
max_width = 0
|
||||||
lines = self._multiline_split(text)
|
lines = self._multiline_split(text)
|
||||||
line_spacing = self.getsize("A")[1] + spacing
|
line_spacing = self.getsize("A", stroke_width=stroke_width)[1] + spacing
|
||||||
for line in lines:
|
for line in lines:
|
||||||
line_width, line_height = self.getsize(line, direction, features, language)
|
line_width, line_height = self.getsize(
|
||||||
|
line, direction, features, language, stroke_width
|
||||||
|
)
|
||||||
max_width = max(max_width, line_width)
|
max_width = max(max_width, line_width)
|
||||||
|
|
||||||
return max_width, len(lines) * line_spacing - spacing
|
return max_width, len(lines) * line_spacing - spacing
|
||||||
|
@ -308,7 +329,15 @@ class FreeTypeFont(object):
|
||||||
"""
|
"""
|
||||||
return self.font.getsize(text)[1]
|
return self.font.getsize(text)[1]
|
||||||
|
|
||||||
def getmask(self, text, mode="", direction=None, features=None, language=None):
|
def getmask(
|
||||||
|
self,
|
||||||
|
text,
|
||||||
|
mode="",
|
||||||
|
direction=None,
|
||||||
|
features=None,
|
||||||
|
language=None,
|
||||||
|
stroke_width=0,
|
||||||
|
):
|
||||||
"""
|
"""
|
||||||
Create a bitmap for the text.
|
Create a bitmap for the text.
|
||||||
|
|
||||||
|
@ -352,11 +381,20 @@ class FreeTypeFont(object):
|
||||||
|
|
||||||
.. versionadded:: 6.0.0
|
.. versionadded:: 6.0.0
|
||||||
|
|
||||||
|
:param stroke_width: The width of the text stroke.
|
||||||
|
|
||||||
|
.. versionadded:: 6.2.0
|
||||||
|
|
||||||
:return: An internal PIL storage memory instance as defined by the
|
:return: An internal PIL storage memory instance as defined by the
|
||||||
:py:mod:`PIL.Image.core` interface module.
|
:py:mod:`PIL.Image.core` interface module.
|
||||||
"""
|
"""
|
||||||
return self.getmask2(
|
return self.getmask2(
|
||||||
text, mode, direction=direction, features=features, language=language
|
text,
|
||||||
|
mode,
|
||||||
|
direction=direction,
|
||||||
|
features=features,
|
||||||
|
language=language,
|
||||||
|
stroke_width=stroke_width,
|
||||||
)[0]
|
)[0]
|
||||||
|
|
||||||
def getmask2(
|
def getmask2(
|
||||||
|
@ -367,6 +405,7 @@ class FreeTypeFont(object):
|
||||||
direction=None,
|
direction=None,
|
||||||
features=None,
|
features=None,
|
||||||
language=None,
|
language=None,
|
||||||
|
stroke_width=0,
|
||||||
*args,
|
*args,
|
||||||
**kwargs
|
**kwargs
|
||||||
):
|
):
|
||||||
|
@ -413,13 +452,20 @@ class FreeTypeFont(object):
|
||||||
|
|
||||||
.. versionadded:: 6.0.0
|
.. versionadded:: 6.0.0
|
||||||
|
|
||||||
|
:param stroke_width: The width of the text stroke.
|
||||||
|
|
||||||
|
.. versionadded:: 6.2.0
|
||||||
|
|
||||||
:return: A tuple of an internal PIL storage memory instance as defined by the
|
:return: A tuple of an internal PIL storage memory instance as defined by the
|
||||||
:py:mod:`PIL.Image.core` interface module, and the text offset, the
|
:py:mod:`PIL.Image.core` interface module, and the text offset, the
|
||||||
gap between the starting coordinate and the first marking
|
gap between the starting coordinate and the first marking
|
||||||
"""
|
"""
|
||||||
size, offset = self.font.getsize(text, direction, features, language)
|
size, offset = self.font.getsize(text, direction, features, language)
|
||||||
|
size = size[0] + stroke_width * 2, size[1] + stroke_width * 2
|
||||||
im = fill("L", size, 0)
|
im = fill("L", size, 0)
|
||||||
self.font.render(text, im.id, mode == "1", direction, features, language)
|
self.font.render(
|
||||||
|
text, im.id, mode == "1", direction, features, language, stroke_width
|
||||||
|
)
|
||||||
return im, offset
|
return im, offset
|
||||||
|
|
||||||
def font_variant(
|
def font_variant(
|
||||||
|
@ -546,18 +592,41 @@ def truetype(font=None, size=10, index=0, encoding="", layout_engine=None):
|
||||||
This function loads a font object from the given file or file-like
|
This function loads a font object from the given file or file-like
|
||||||
object, and creates a font object for a font of the given size.
|
object, and creates a font object for a font of the given size.
|
||||||
|
|
||||||
|
Pillow uses FreeType to open font files. If you are opening many fonts
|
||||||
|
simultaneously on Windows, be aware that Windows limits the number of files
|
||||||
|
that can be open in C at once to 512. If you approach that limit, an
|
||||||
|
``OSError`` may be thrown, reporting that FreeType "cannot open resource".
|
||||||
|
|
||||||
This function requires the _imagingft service.
|
This function requires the _imagingft service.
|
||||||
|
|
||||||
:param font: A filename or file-like object containing a TrueType font.
|
:param font: A filename or file-like object containing a TrueType font.
|
||||||
Under Windows, if the file is not found in this filename,
|
If the file is not found in this filename, the loader may also
|
||||||
the loader also looks in Windows :file:`fonts/` directory.
|
search in other directories, such as the :file:`fonts/`
|
||||||
|
directory on Windows or :file:`/Library/Fonts/`,
|
||||||
|
:file:`/System/Library/Fonts/` and :file:`~/Library/Fonts/` on
|
||||||
|
macOS.
|
||||||
|
|
||||||
:param size: The requested size, in points.
|
:param size: The requested size, in points.
|
||||||
:param index: Which font face to load (default is first available face).
|
:param index: Which font face to load (default is first available face).
|
||||||
:param encoding: Which font encoding to use (default is Unicode). Common
|
:param encoding: Which font encoding to use (default is Unicode). Possible
|
||||||
encodings are "unic" (Unicode), "symb" (Microsoft
|
encodings include (see the FreeType documentation for more
|
||||||
Symbol), "ADOB" (Adobe Standard), "ADBE" (Adobe Expert),
|
information):
|
||||||
and "armn" (Apple Roman). See the FreeType documentation
|
|
||||||
for more information.
|
* "unic" (Unicode)
|
||||||
|
* "symb" (Microsoft Symbol)
|
||||||
|
* "ADOB" (Adobe Standard)
|
||||||
|
* "ADBE" (Adobe Expert)
|
||||||
|
* "ADBC" (Adobe Custom)
|
||||||
|
* "armn" (Apple Roman)
|
||||||
|
* "sjis" (Shift JIS)
|
||||||
|
* "gb " (PRC)
|
||||||
|
* "big5"
|
||||||
|
* "wans" (Extended Wansung)
|
||||||
|
* "joha" (Johab)
|
||||||
|
* "lat1" (Latin-1)
|
||||||
|
|
||||||
|
This specifies the character set to use. It does not alter the
|
||||||
|
encoding of any text provided in subsequent operations.
|
||||||
:param layout_engine: Which layout engine to use, if available:
|
:param layout_engine: Which layout engine to use, if available:
|
||||||
`ImageFont.LAYOUT_BASIC` or `ImageFont.LAYOUT_RAQM`.
|
`ImageFont.LAYOUT_BASIC` or `ImageFont.LAYOUT_RAQM`.
|
||||||
:return: A font object.
|
:return: A font object.
|
||||||
|
|
|
@ -29,7 +29,7 @@ else:
|
||||||
raise ImportError("ImageGrab is macOS and Windows only")
|
raise ImportError("ImageGrab is macOS and Windows only")
|
||||||
|
|
||||||
|
|
||||||
def grab(bbox=None, include_layered_windows=False):
|
def grab(bbox=None, include_layered_windows=False, all_screens=False):
|
||||||
if sys.platform == "darwin":
|
if sys.platform == "darwin":
|
||||||
fh, filepath = tempfile.mkstemp(".png")
|
fh, filepath = tempfile.mkstemp(".png")
|
||||||
os.close(fh)
|
os.close(fh)
|
||||||
|
@ -37,8 +37,10 @@ def grab(bbox=None, include_layered_windows=False):
|
||||||
im = Image.open(filepath)
|
im = Image.open(filepath)
|
||||||
im.load()
|
im.load()
|
||||||
os.unlink(filepath)
|
os.unlink(filepath)
|
||||||
|
if bbox:
|
||||||
|
im = im.crop(bbox)
|
||||||
else:
|
else:
|
||||||
size, data = grabber(include_layered_windows)
|
offset, size, data = grabber(include_layered_windows, all_screens)
|
||||||
im = Image.frombytes(
|
im = Image.frombytes(
|
||||||
"RGB",
|
"RGB",
|
||||||
size,
|
size,
|
||||||
|
@ -50,7 +52,9 @@ def grab(bbox=None, include_layered_windows=False):
|
||||||
-1,
|
-1,
|
||||||
)
|
)
|
||||||
if bbox:
|
if bbox:
|
||||||
im = im.crop(bbox)
|
x0, y0 = offset
|
||||||
|
left, top, right, bottom = bbox
|
||||||
|
im = im.crop((left - x0, top - y0, right - x0, bottom - y0))
|
||||||
return im
|
return im
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -105,7 +105,8 @@ class Viewer(object):
|
||||||
if sys.platform == "win32":
|
if sys.platform == "win32":
|
||||||
|
|
||||||
class WindowsViewer(Viewer):
|
class WindowsViewer(Viewer):
|
||||||
format = "BMP"
|
format = "PNG"
|
||||||
|
options = {"compress_level": 1}
|
||||||
|
|
||||||
def get_command(self, file, **options):
|
def get_command(self, file, **options):
|
||||||
return (
|
return (
|
||||||
|
@ -179,7 +180,7 @@ else:
|
||||||
with open(path, "r") as f:
|
with open(path, "r") as f:
|
||||||
command = self.get_command_ex(file, **options)[0]
|
command = self.get_command_ex(file, **options)[0]
|
||||||
subprocess.Popen(
|
subprocess.Popen(
|
||||||
["im=$(cat);" + command + " $im;" "rm -f $im"], shell=True, stdin=f
|
["im=$(cat);" + command + " $im; rm -f $im"], shell=True, stdin=f
|
||||||
)
|
)
|
||||||
os.remove(path)
|
os.remove(path)
|
||||||
return 1
|
return 1
|
||||||
|
|
|
@ -158,7 +158,7 @@ def APP(self, marker):
|
||||||
# If DPI isn't in JPEG header, fetch from EXIF
|
# If DPI isn't in JPEG header, fetch from EXIF
|
||||||
if "dpi" not in self.info and "exif" in self.info:
|
if "dpi" not in self.info and "exif" in self.info:
|
||||||
try:
|
try:
|
||||||
exif = self._getexif()
|
exif = self.getexif()
|
||||||
resolution_unit = exif[0x0128]
|
resolution_unit = exif[0x0128]
|
||||||
x_resolution = exif[0x011A]
|
x_resolution = exif[0x011A]
|
||||||
try:
|
try:
|
||||||
|
@ -485,19 +485,9 @@ def _fixup_dict(src_dict):
|
||||||
|
|
||||||
|
|
||||||
def _getexif(self):
|
def _getexif(self):
|
||||||
# Use the cached version if possible
|
|
||||||
try:
|
|
||||||
return self.info["parsed_exif"]
|
|
||||||
except KeyError:
|
|
||||||
pass
|
|
||||||
|
|
||||||
if "exif" not in self.info:
|
if "exif" not in self.info:
|
||||||
return None
|
return None
|
||||||
exif = dict(self.getexif())
|
return dict(self.getexif())
|
||||||
|
|
||||||
# Cache the result for future use
|
|
||||||
self.info["parsed_exif"] = exif
|
|
||||||
return exif
|
|
||||||
|
|
||||||
|
|
||||||
def _getmp(self):
|
def _getmp(self):
|
||||||
|
|
|
@ -86,13 +86,11 @@ class MpoImageFile(JpegImagePlugin.JpegImageFile):
|
||||||
self.offset = self.__mpoffsets[frame]
|
self.offset = self.__mpoffsets[frame]
|
||||||
|
|
||||||
self.fp.seek(self.offset + 2) # skip SOI marker
|
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
|
if i16(self.fp.read(2)) == 0xFFE1: # APP1
|
||||||
n = i16(self.fp.read(2)) - 2
|
n = i16(self.fp.read(2)) - 2
|
||||||
self.info["exif"] = ImageFile._safe_read(self.fp, n)
|
self.info["exif"] = ImageFile._safe_read(self.fp, n)
|
||||||
|
|
||||||
exif = self._getexif()
|
exif = self.getexif()
|
||||||
if 40962 in exif and 40963 in exif:
|
if 40962 in exif and 40963 in exif:
|
||||||
self._size = (exif[40962], exif[40963])
|
self._size = (exif[40962], exif[40963])
|
||||||
elif "exif" in self.info:
|
elif "exif" in self.info:
|
||||||
|
|
|
@ -612,7 +612,7 @@ class PngImageFile(ImageFile.ImageFile):
|
||||||
rawmode, data = self.png.im_palette
|
rawmode, data = self.png.im_palette
|
||||||
self.palette = ImagePalette.raw(rawmode, data)
|
self.palette = ImagePalette.raw(rawmode, data)
|
||||||
|
|
||||||
self.__idat = length # used by load_read()
|
self.__prepare_idat = length # used by load_prepare()
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def text(self):
|
def text(self):
|
||||||
|
@ -645,6 +645,7 @@ class PngImageFile(ImageFile.ImageFile):
|
||||||
if self.info.get("interlace"):
|
if self.info.get("interlace"):
|
||||||
self.decoderconfig = self.decoderconfig + (1,)
|
self.decoderconfig = self.decoderconfig + (1,)
|
||||||
|
|
||||||
|
self.__idat = self.__prepare_idat # used by load_read()
|
||||||
ImageFile.ImageFile.load_prepare(self)
|
ImageFile.ImageFile.load_prepare(self)
|
||||||
|
|
||||||
def load_read(self, read_bytes):
|
def load_read(self, read_bytes):
|
||||||
|
|
|
@ -1098,6 +1098,20 @@ class TiffImageFile(ImageFile.ImageFile):
|
||||||
return super(TiffImageFile, self).load()
|
return super(TiffImageFile, self).load()
|
||||||
|
|
||||||
def load_end(self):
|
def load_end(self):
|
||||||
|
if self._tile_orientation:
|
||||||
|
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(self._tile_orientation)
|
||||||
|
if method is not None:
|
||||||
|
self.im = self.im.transpose(method)
|
||||||
|
self._size = self.im.size
|
||||||
|
|
||||||
# allow closing if we're on the first frame, there's no next
|
# allow closing if we're on the first frame, there's no next
|
||||||
# This is the ImageFile.load path only, libtiff specific below.
|
# This is the ImageFile.load path only, libtiff specific below.
|
||||||
if not self._is_animated:
|
if not self._is_animated:
|
||||||
|
@ -1151,6 +1165,7 @@ class TiffImageFile(ImageFile.ImageFile):
|
||||||
except ValueError:
|
except ValueError:
|
||||||
raise IOError("Couldn't set the image")
|
raise IOError("Couldn't set the image")
|
||||||
|
|
||||||
|
close_self_fp = self._exclusive_fp and not self._is_animated
|
||||||
if hasattr(self.fp, "getvalue"):
|
if hasattr(self.fp, "getvalue"):
|
||||||
# We've got a stringio like thing passed in. Yay for all in memory.
|
# We've got a stringio like thing passed in. Yay for all in memory.
|
||||||
# The decoder needs the entire file in one shot, so there's not
|
# The decoder needs the entire file in one shot, so there's not
|
||||||
|
@ -1164,10 +1179,11 @@ class TiffImageFile(ImageFile.ImageFile):
|
||||||
if DEBUG:
|
if DEBUG:
|
||||||
print("have getvalue. just sending in a string from getvalue")
|
print("have getvalue. just sending in a string from getvalue")
|
||||||
n, err = decoder.decode(self.fp.getvalue())
|
n, err = decoder.decode(self.fp.getvalue())
|
||||||
elif hasattr(self.fp, "fileno"):
|
elif fp:
|
||||||
# we've got a actual file on disk, pass in the fp.
|
# we've got a actual file on disk, pass in the fp.
|
||||||
if DEBUG:
|
if DEBUG:
|
||||||
print("have fileno, calling fileno version of the decoder.")
|
print("have fileno, calling fileno version of the decoder.")
|
||||||
|
if not close_self_fp:
|
||||||
self.fp.seek(0)
|
self.fp.seek(0)
|
||||||
# 4 bytes, otherwise the trace might error out
|
# 4 bytes, otherwise the trace might error out
|
||||||
n, err = decoder.decode(b"fpfp")
|
n, err = decoder.decode(b"fpfp")
|
||||||
|
@ -1175,13 +1191,17 @@ class TiffImageFile(ImageFile.ImageFile):
|
||||||
# we have something else.
|
# we have something else.
|
||||||
if DEBUG:
|
if DEBUG:
|
||||||
print("don't have fileno or getvalue. just reading")
|
print("don't have fileno or getvalue. just reading")
|
||||||
|
self.fp.seek(0)
|
||||||
# UNDONE -- so much for that buffer size thing.
|
# UNDONE -- so much for that buffer size thing.
|
||||||
n, err = decoder.decode(self.fp.read())
|
n, err = decoder.decode(self.fp.read())
|
||||||
|
|
||||||
self.tile = []
|
self.tile = []
|
||||||
self.readonly = 0
|
self.readonly = 0
|
||||||
|
|
||||||
|
self.load_end()
|
||||||
|
|
||||||
# libtiff closed the fp in a, we need to close self.fp, if possible
|
# libtiff closed the fp in a, we need to close self.fp, if possible
|
||||||
if self._exclusive_fp and not self._is_animated:
|
if close_self_fp:
|
||||||
self.fp.close()
|
self.fp.close()
|
||||||
self.fp = None # might be shared
|
self.fp = None # might be shared
|
||||||
|
|
||||||
|
@ -1387,6 +1407,8 @@ class TiffImageFile(ImageFile.ImageFile):
|
||||||
palette = [o8(b // 256) for b in self.tag_v2[COLORMAP]]
|
palette = [o8(b // 256) for b in self.tag_v2[COLORMAP]]
|
||||||
self.palette = ImagePalette.raw("RGB;L", b"".join(palette))
|
self.palette = ImagePalette.raw("RGB;L", b"".join(palette))
|
||||||
|
|
||||||
|
self._tile_orientation = self.tag_v2.get(0x0112)
|
||||||
|
|
||||||
def _close__fp(self):
|
def _close__fp(self):
|
||||||
try:
|
try:
|
||||||
if self.__fp != self.fp:
|
if self.__fp != self.fp:
|
||||||
|
|
|
@ -175,9 +175,10 @@ TAGS_V2 = {
|
||||||
530: ("YCbCrSubSampling", SHORT, 2),
|
530: ("YCbCrSubSampling", SHORT, 2),
|
||||||
531: ("YCbCrPositioning", SHORT, 1),
|
531: ("YCbCrPositioning", SHORT, 1),
|
||||||
532: ("ReferenceBlackWhite", RATIONAL, 6),
|
532: ("ReferenceBlackWhite", RATIONAL, 6),
|
||||||
700: ("XMP", BYTE, 1),
|
700: ("XMP", BYTE, 0),
|
||||||
33432: ("Copyright", ASCII, 1),
|
33432: ("Copyright", ASCII, 1),
|
||||||
34377: ("PhotoshopInfo", BYTE, 1),
|
33723: ("IptcNaaInfo", UNDEFINED, 0),
|
||||||
|
34377: ("PhotoshopInfo", BYTE, 0),
|
||||||
# FIXME add more tags here
|
# FIXME add more tags here
|
||||||
34665: ("ExifIFD", LONG, 1),
|
34665: ("ExifIFD", LONG, 1),
|
||||||
34675: ("ICCProfile", UNDEFINED, 1),
|
34675: ("ICCProfile", UNDEFINED, 1),
|
||||||
|
|
|
@ -25,6 +25,7 @@
|
||||||
#include <ft2build.h>
|
#include <ft2build.h>
|
||||||
#include FT_FREETYPE_H
|
#include FT_FREETYPE_H
|
||||||
#include FT_GLYPH_H
|
#include FT_GLYPH_H
|
||||||
|
#include FT_STROKER_H
|
||||||
#include FT_MULTIPLE_MASTERS_H
|
#include FT_MULTIPLE_MASTERS_H
|
||||||
#include FT_SFNT_NAMES_H
|
#include FT_SFNT_NAMES_H
|
||||||
|
|
||||||
|
@ -790,7 +791,13 @@ font_render(FontObject* self, PyObject* args)
|
||||||
int index, error, ascender, horizontal_dir;
|
int index, error, ascender, horizontal_dir;
|
||||||
int load_flags;
|
int load_flags;
|
||||||
unsigned char *source;
|
unsigned char *source;
|
||||||
FT_GlyphSlot glyph;
|
FT_Glyph glyph;
|
||||||
|
FT_GlyphSlot glyph_slot;
|
||||||
|
FT_Bitmap bitmap;
|
||||||
|
FT_BitmapGlyph bitmap_glyph;
|
||||||
|
int stroke_width = 0;
|
||||||
|
FT_Stroker stroker = NULL;
|
||||||
|
FT_Int left;
|
||||||
/* render string into given buffer (the buffer *must* have
|
/* render string into given buffer (the buffer *must* have
|
||||||
the right size, or this will crash) */
|
the right size, or this will crash) */
|
||||||
PyObject* string;
|
PyObject* string;
|
||||||
|
@ -806,7 +813,8 @@ font_render(FontObject* self, PyObject* args)
|
||||||
GlyphInfo *glyph_info;
|
GlyphInfo *glyph_info;
|
||||||
PyObject *features = NULL;
|
PyObject *features = NULL;
|
||||||
|
|
||||||
if (!PyArg_ParseTuple(args, "On|izOz:render", &string, &id, &mask, &dir, &features, &lang)) {
|
if (!PyArg_ParseTuple(args, "On|izOzi:render", &string, &id, &mask, &dir, &features, &lang,
|
||||||
|
&stroke_width)) {
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -819,21 +827,37 @@ font_render(FontObject* self, PyObject* args)
|
||||||
Py_RETURN_NONE;
|
Py_RETURN_NONE;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (stroke_width) {
|
||||||
|
error = FT_Stroker_New(library, &stroker);
|
||||||
|
if (error) {
|
||||||
|
return geterror(error);
|
||||||
|
}
|
||||||
|
|
||||||
|
FT_Stroker_Set(stroker, (FT_Fixed)stroke_width*64, FT_STROKER_LINECAP_ROUND, FT_STROKER_LINEJOIN_ROUND, 0);
|
||||||
|
}
|
||||||
|
|
||||||
im = (Imaging) id;
|
im = (Imaging) id;
|
||||||
/* Note: bitmap fonts within ttf fonts do not work, see #891/pr#960 */
|
/* Note: bitmap fonts within ttf fonts do not work, see #891/pr#960 */
|
||||||
load_flags = FT_LOAD_RENDER|FT_LOAD_NO_BITMAP;
|
load_flags = FT_LOAD_NO_BITMAP;
|
||||||
if (mask)
|
if (stroker == NULL) {
|
||||||
|
load_flags |= FT_LOAD_RENDER;
|
||||||
|
}
|
||||||
|
if (mask) {
|
||||||
load_flags |= FT_LOAD_TARGET_MONO;
|
load_flags |= FT_LOAD_TARGET_MONO;
|
||||||
|
}
|
||||||
|
|
||||||
ascender = 0;
|
ascender = 0;
|
||||||
for (i = 0; i < count; i++) {
|
for (i = 0; i < count; i++) {
|
||||||
index = glyph_info[i].index;
|
index = glyph_info[i].index;
|
||||||
error = FT_Load_Glyph(self->face, index, load_flags);
|
error = FT_Load_Glyph(self->face, index, load_flags);
|
||||||
if (error)
|
if (error) {
|
||||||
return geterror(error);
|
return geterror(error);
|
||||||
|
}
|
||||||
|
|
||||||
glyph = self->face->glyph;
|
glyph_slot = self->face->glyph;
|
||||||
temp = glyph->bitmap.rows - glyph->bitmap_top;
|
bitmap = glyph_slot->bitmap;
|
||||||
|
|
||||||
|
temp = bitmap.rows - glyph_slot->bitmap_top;
|
||||||
temp -= PIXEL(glyph_info[i].y_offset);
|
temp -= PIXEL(glyph_info[i].y_offset);
|
||||||
if (temp > ascender)
|
if (temp > ascender)
|
||||||
ascender = temp;
|
ascender = temp;
|
||||||
|
@ -844,37 +868,62 @@ font_render(FontObject* self, PyObject* args)
|
||||||
for (i = 0; i < count; i++) {
|
for (i = 0; i < count; i++) {
|
||||||
index = glyph_info[i].index;
|
index = glyph_info[i].index;
|
||||||
error = FT_Load_Glyph(self->face, index, load_flags);
|
error = FT_Load_Glyph(self->face, index, load_flags);
|
||||||
if (error)
|
if (error) {
|
||||||
return geterror(error);
|
return geterror(error);
|
||||||
|
}
|
||||||
|
|
||||||
glyph = self->face->glyph;
|
glyph_slot = self->face->glyph;
|
||||||
if (horizontal_dir) {
|
if (stroker != NULL) {
|
||||||
if (i == 0 && self->face->glyph->metrics.horiBearingX < 0) {
|
error = FT_Get_Glyph(glyph_slot, &glyph);
|
||||||
x = -self->face->glyph->metrics.horiBearingX;
|
if (!error) {
|
||||||
|
error = FT_Glyph_Stroke(&glyph, stroker, 1);
|
||||||
}
|
}
|
||||||
xx = PIXEL(x) + glyph->bitmap_left;
|
if (!error) {
|
||||||
xx += PIXEL(glyph_info[i].x_offset);
|
FT_Vector origin = {0, 0};
|
||||||
|
error = FT_Glyph_To_Bitmap(&glyph, FT_RENDER_MODE_NORMAL, &origin, 1);
|
||||||
|
}
|
||||||
|
if (error) {
|
||||||
|
return geterror(error);
|
||||||
|
}
|
||||||
|
|
||||||
|
bitmap_glyph = (FT_BitmapGlyph)glyph;
|
||||||
|
|
||||||
|
bitmap = bitmap_glyph->bitmap;
|
||||||
|
left = bitmap_glyph->left;
|
||||||
|
|
||||||
|
FT_Done_Glyph(glyph);
|
||||||
} else {
|
} else {
|
||||||
if (self->face->glyph->metrics.vertBearingX < 0) {
|
bitmap = glyph_slot->bitmap;
|
||||||
x = -self->face->glyph->metrics.vertBearingX;
|
left = glyph_slot->bitmap_left;
|
||||||
}
|
}
|
||||||
xx = im->xsize / 2 - glyph->bitmap.width / 2;
|
|
||||||
|
if (horizontal_dir) {
|
||||||
|
if (i == 0 && glyph_slot->metrics.horiBearingX < 0) {
|
||||||
|
x = -glyph_slot->metrics.horiBearingX;
|
||||||
|
}
|
||||||
|
xx = PIXEL(x) + left;
|
||||||
|
xx += PIXEL(glyph_info[i].x_offset) + stroke_width;
|
||||||
|
} else {
|
||||||
|
if (glyph_slot->metrics.vertBearingX < 0) {
|
||||||
|
x = -glyph_slot->metrics.vertBearingX;
|
||||||
|
}
|
||||||
|
xx = im->xsize / 2 - bitmap.width / 2;
|
||||||
}
|
}
|
||||||
|
|
||||||
x0 = 0;
|
x0 = 0;
|
||||||
x1 = glyph->bitmap.width;
|
x1 = bitmap.width;
|
||||||
if (xx < 0)
|
if (xx < 0)
|
||||||
x0 = -xx;
|
x0 = -xx;
|
||||||
if (xx + x1 > im->xsize)
|
if (xx + x1 > im->xsize)
|
||||||
x1 = im->xsize - xx;
|
x1 = im->xsize - xx;
|
||||||
|
|
||||||
source = (unsigned char*) glyph->bitmap.buffer;
|
source = (unsigned char*) bitmap.buffer;
|
||||||
for (bitmap_y = 0; bitmap_y < glyph->bitmap.rows; bitmap_y++) {
|
for (bitmap_y = 0; bitmap_y < bitmap.rows; bitmap_y++) {
|
||||||
if (horizontal_dir) {
|
if (horizontal_dir) {
|
||||||
yy = bitmap_y + im->ysize - (PIXEL(glyph->metrics.horiBearingY) + ascender);
|
yy = bitmap_y + im->ysize - (PIXEL(glyph_slot->metrics.horiBearingY) + ascender);
|
||||||
yy -= PIXEL(glyph_info[i].y_offset);
|
yy -= PIXEL(glyph_info[i].y_offset) + stroke_width * 2;
|
||||||
} else {
|
} else {
|
||||||
yy = bitmap_y + PIXEL(y + glyph->metrics.vertBearingY) + ascender;
|
yy = bitmap_y + PIXEL(y + glyph_slot->metrics.vertBearingY) + ascender;
|
||||||
yy += PIXEL(glyph_info[i].y_offset);
|
yy += PIXEL(glyph_info[i].y_offset);
|
||||||
}
|
}
|
||||||
if (yy >= 0 && yy < im->ysize) {
|
if (yy >= 0 && yy < im->ysize) {
|
||||||
|
@ -900,12 +949,13 @@ font_render(FontObject* self, PyObject* args)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
source += glyph->bitmap.pitch;
|
source += bitmap.pitch;
|
||||||
}
|
}
|
||||||
x += glyph_info[i].x_advance;
|
x += glyph_info[i].x_advance;
|
||||||
y -= glyph_info[i].y_advance;
|
y -= glyph_info[i].y_advance;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
FT_Stroker_Done(stroker);
|
||||||
PyMem_Del(glyph_info);
|
PyMem_Del(glyph_info);
|
||||||
Py_RETURN_NONE;
|
Py_RETURN_NONE;
|
||||||
}
|
}
|
||||||
|
|
|
@ -324,8 +324,8 @@ typedef HANDLE(__stdcall* Func_SetThreadDpiAwarenessContext)(HANDLE);
|
||||||
PyObject*
|
PyObject*
|
||||||
PyImaging_GrabScreenWin32(PyObject* self, PyObject* args)
|
PyImaging_GrabScreenWin32(PyObject* self, PyObject* args)
|
||||||
{
|
{
|
||||||
int width, height;
|
int x = 0, y = 0, width, height;
|
||||||
int includeLayeredWindows = 0;
|
int includeLayeredWindows = 0, all_screens = 0;
|
||||||
HBITMAP bitmap;
|
HBITMAP bitmap;
|
||||||
BITMAPCOREHEADER core;
|
BITMAPCOREHEADER core;
|
||||||
HDC screen, screen_copy;
|
HDC screen, screen_copy;
|
||||||
|
@ -335,7 +335,7 @@ PyImaging_GrabScreenWin32(PyObject* self, PyObject* args)
|
||||||
HMODULE user32;
|
HMODULE user32;
|
||||||
Func_SetThreadDpiAwarenessContext SetThreadDpiAwarenessContext_function;
|
Func_SetThreadDpiAwarenessContext SetThreadDpiAwarenessContext_function;
|
||||||
|
|
||||||
if (!PyArg_ParseTuple(args, "|i", &includeLayeredWindows))
|
if (!PyArg_ParseTuple(args, "|ii", &includeLayeredWindows, &all_screens))
|
||||||
return NULL;
|
return NULL;
|
||||||
|
|
||||||
/* step 1: create a memory DC large enough to hold the
|
/* step 1: create a memory DC large enough to hold the
|
||||||
|
@ -355,8 +355,15 @@ PyImaging_GrabScreenWin32(PyObject* self, PyObject* args)
|
||||||
dpiAwareness = SetThreadDpiAwarenessContext_function((HANDLE) -3);
|
dpiAwareness = SetThreadDpiAwarenessContext_function((HANDLE) -3);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (all_screens) {
|
||||||
|
x = GetSystemMetrics(SM_XVIRTUALSCREEN);
|
||||||
|
y = GetSystemMetrics(SM_YVIRTUALSCREEN);
|
||||||
|
width = GetSystemMetrics(SM_CXVIRTUALSCREEN);
|
||||||
|
height = GetSystemMetrics(SM_CYVIRTUALSCREEN);
|
||||||
|
} else {
|
||||||
width = GetDeviceCaps(screen, HORZRES);
|
width = GetDeviceCaps(screen, HORZRES);
|
||||||
height = GetDeviceCaps(screen, VERTRES);
|
height = GetDeviceCaps(screen, VERTRES);
|
||||||
|
}
|
||||||
|
|
||||||
if (SetThreadDpiAwarenessContext_function != NULL) {
|
if (SetThreadDpiAwarenessContext_function != NULL) {
|
||||||
SetThreadDpiAwarenessContext_function(dpiAwareness);
|
SetThreadDpiAwarenessContext_function(dpiAwareness);
|
||||||
|
@ -376,7 +383,7 @@ PyImaging_GrabScreenWin32(PyObject* self, PyObject* args)
|
||||||
rop = SRCCOPY;
|
rop = SRCCOPY;
|
||||||
if (includeLayeredWindows)
|
if (includeLayeredWindows)
|
||||||
rop |= CAPTUREBLT;
|
rop |= CAPTUREBLT;
|
||||||
if (!BitBlt(screen_copy, 0, 0, width, height, screen, 0, 0, rop))
|
if (!BitBlt(screen_copy, 0, 0, width, height, screen, x, y, rop))
|
||||||
goto error;
|
goto error;
|
||||||
|
|
||||||
/* step 3: extract bits from bitmap */
|
/* step 3: extract bits from bitmap */
|
||||||
|
@ -398,7 +405,7 @@ PyImaging_GrabScreenWin32(PyObject* self, PyObject* args)
|
||||||
DeleteDC(screen_copy);
|
DeleteDC(screen_copy);
|
||||||
DeleteDC(screen);
|
DeleteDC(screen);
|
||||||
|
|
||||||
return Py_BuildValue("(ii)N", width, height, buffer);
|
return Py_BuildValue("(ii)(ii)N", x, y, width, height, buffer);
|
||||||
|
|
||||||
error:
|
error:
|
||||||
PyErr_SetString(PyExc_IOError, "screen grab failed");
|
PyErr_SetString(PyExc_IOError, "screen grab failed");
|
||||||
|
|
|
@ -1211,6 +1211,8 @@ PyImaging_Jpeg2KEncoderNew(PyObject *self, PyObject *args)
|
||||||
PyErr_SetString(PyExc_ValueError,
|
PyErr_SetString(PyExc_ValueError,
|
||||||
"JPEG 2000 tile offset too small; top left tile must "
|
"JPEG 2000 tile offset too small; top left tile must "
|
||||||
"intersect image area");
|
"intersect image area");
|
||||||
|
Py_DECREF(encoder);
|
||||||
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (context->tile_offset_x > context->offset_x
|
if (context->tile_offset_x > context->offset_x
|
||||||
|
|
|
@ -101,6 +101,19 @@ bit2ycbcr(UINT8* out, const UINT8* in, int xsize)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
bit2hsv(UINT8* out, const UINT8* in, int xsize)
|
||||||
|
{
|
||||||
|
int x;
|
||||||
|
for (x = 0; x < xsize; x++, out += 4) {
|
||||||
|
UINT8 v = (*in++ != 0) ? 255 : 0;
|
||||||
|
out[0] = 0;
|
||||||
|
out[1] = 0;
|
||||||
|
out[2] = v;
|
||||||
|
out[3] = 255;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/* ----------------- */
|
/* ----------------- */
|
||||||
/* RGB/L conversions */
|
/* RGB/L conversions */
|
||||||
/* ----------------- */
|
/* ----------------- */
|
||||||
|
@ -175,6 +188,19 @@ l2rgb(UINT8* out, const UINT8* in, int xsize)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
l2hsv(UINT8* out, const UINT8* in, int xsize)
|
||||||
|
{
|
||||||
|
int x;
|
||||||
|
for (x = 0; x < xsize; x++, out += 4) {
|
||||||
|
UINT8 v = *in++;
|
||||||
|
out[0] = 0;
|
||||||
|
out[1] = 0;
|
||||||
|
out[2] = v;
|
||||||
|
out[3] = 255;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
static void
|
static void
|
||||||
la2l(UINT8* out, const UINT8* in, int xsize)
|
la2l(UINT8* out, const UINT8* in, int xsize)
|
||||||
{
|
{
|
||||||
|
@ -196,6 +222,19 @@ la2rgb(UINT8* out, const UINT8* in, int xsize)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
la2hsv(UINT8* out, const UINT8* in, int xsize)
|
||||||
|
{
|
||||||
|
int x;
|
||||||
|
for (x = 0; x < xsize; x++, in += 4, out += 4) {
|
||||||
|
UINT8 v = in[0];
|
||||||
|
out[0] = 0;
|
||||||
|
out[1] = 0;
|
||||||
|
out[2] = v;
|
||||||
|
out[3] = in[3];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
static void
|
static void
|
||||||
rgb2bit(UINT8* out, const UINT8* in, int xsize)
|
rgb2bit(UINT8* out, const UINT8* in, int xsize)
|
||||||
{
|
{
|
||||||
|
@ -283,26 +322,22 @@ rgb2bgr24(UINT8* out, const UINT8* in, int xsize)
|
||||||
}
|
}
|
||||||
|
|
||||||
static void
|
static void
|
||||||
rgb2hsv(UINT8* out, const UINT8* in, int xsize)
|
rgb2hsv_row(UINT8* out, const UINT8* in)
|
||||||
{ // following colorsys.py
|
{ // following colorsys.py
|
||||||
float h,s,rc,gc,bc,cr;
|
float h,s,rc,gc,bc,cr;
|
||||||
UINT8 maxc,minc;
|
UINT8 maxc,minc;
|
||||||
UINT8 r, g, b;
|
UINT8 r, g, b;
|
||||||
UINT8 uh,us,uv;
|
UINT8 uh,us,uv;
|
||||||
int x;
|
|
||||||
|
|
||||||
for (x = 0; x < xsize; x++, in += 4) {
|
|
||||||
r = in[0];
|
r = in[0];
|
||||||
g = in[1];
|
g = in[1];
|
||||||
b = in[2];
|
b = in[2];
|
||||||
|
|
||||||
maxc = MAX(r,MAX(g,b));
|
maxc = MAX(r,MAX(g,b));
|
||||||
minc = MIN(r,MIN(g,b));
|
minc = MIN(r,MIN(g,b));
|
||||||
uv = maxc;
|
uv = maxc;
|
||||||
if (minc == maxc){
|
if (minc == maxc){
|
||||||
*out++ = 0;
|
uh = 0;
|
||||||
*out++ = 0;
|
us = 0;
|
||||||
*out++ = uv;
|
|
||||||
} else {
|
} else {
|
||||||
cr = (float)(maxc-minc);
|
cr = (float)(maxc-minc);
|
||||||
s = cr/(float)maxc;
|
s = cr/(float)maxc;
|
||||||
|
@ -321,15 +356,23 @@ rgb2hsv(UINT8* out, const UINT8* in, int xsize)
|
||||||
|
|
||||||
uh = (UINT8)CLIP8((int)(h*255.0));
|
uh = (UINT8)CLIP8((int)(h*255.0));
|
||||||
us = (UINT8)CLIP8((int)(s*255.0));
|
us = (UINT8)CLIP8((int)(s*255.0));
|
||||||
|
}
|
||||||
|
out[0] = uh;
|
||||||
|
out[1] = us;
|
||||||
|
out[2] = uv;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
rgb2hsv(UINT8* out, const UINT8* in, int xsize)
|
||||||
|
{
|
||||||
|
int x;
|
||||||
|
for (x = 0; x < xsize; x++, in += 4, out += 4) {
|
||||||
|
rgb2hsv_row(out, in);
|
||||||
|
out[3] = in[3];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
*out++ = uh;
|
|
||||||
*out++ = us;
|
|
||||||
*out++ = uv;
|
|
||||||
|
|
||||||
}
|
|
||||||
*out++ = in[3];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
static void
|
static void
|
||||||
hsv2rgb(UINT8* out, const UINT8* in, int xsize)
|
hsv2rgb(UINT8* out, const UINT8* in, int xsize)
|
||||||
|
@ -562,6 +605,22 @@ cmyk2rgb(UINT8* out, const UINT8* in, int xsize)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
cmyk2hsv(UINT8* out, const UINT8* in, int xsize)
|
||||||
|
{
|
||||||
|
int x, nk, tmp;
|
||||||
|
for (x = 0; x < xsize; x++) {
|
||||||
|
nk = 255 - in[3];
|
||||||
|
out[0] = CLIP8(nk - MULDIV255(in[0], nk, tmp));
|
||||||
|
out[1] = CLIP8(nk - MULDIV255(in[1], nk, tmp));
|
||||||
|
out[2] = CLIP8(nk - MULDIV255(in[2], nk, tmp));
|
||||||
|
rgb2hsv_row(out, out);
|
||||||
|
out[3] = 255;
|
||||||
|
out += 4;
|
||||||
|
in += 4;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/* ------------- */
|
/* ------------- */
|
||||||
/* I conversions */
|
/* I conversions */
|
||||||
/* ------------- */
|
/* ------------- */
|
||||||
|
@ -631,6 +690,25 @@ i2rgb(UINT8* out, const UINT8* in_, int xsize)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
i2hsv(UINT8* out, const UINT8* in_, int xsize)
|
||||||
|
{
|
||||||
|
int x;
|
||||||
|
INT32* in = (INT32*) in_;
|
||||||
|
for (x = 0; x < xsize; x++, in++, out+=4) {
|
||||||
|
out[0] = 0;
|
||||||
|
out[1] = 0;
|
||||||
|
if (*in <= 0) {
|
||||||
|
out[2] = 0;
|
||||||
|
} else if (*in >= 255) {
|
||||||
|
out[2] = 255;
|
||||||
|
} else {
|
||||||
|
out[2] = (UINT8) *in;
|
||||||
|
}
|
||||||
|
out[3] = 255;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/* ------------- */
|
/* ------------- */
|
||||||
/* F conversions */
|
/* F conversions */
|
||||||
/* ------------- */
|
/* ------------- */
|
||||||
|
@ -861,6 +939,7 @@ static struct {
|
||||||
{ "1", "RGBX", bit2rgb },
|
{ "1", "RGBX", bit2rgb },
|
||||||
{ "1", "CMYK", bit2cmyk },
|
{ "1", "CMYK", bit2cmyk },
|
||||||
{ "1", "YCbCr", bit2ycbcr },
|
{ "1", "YCbCr", bit2ycbcr },
|
||||||
|
{ "1", "HSV", bit2hsv },
|
||||||
|
|
||||||
{ "L", "1", l2bit },
|
{ "L", "1", l2bit },
|
||||||
{ "L", "LA", l2la },
|
{ "L", "LA", l2la },
|
||||||
|
@ -871,6 +950,7 @@ static struct {
|
||||||
{ "L", "RGBX", l2rgb },
|
{ "L", "RGBX", l2rgb },
|
||||||
{ "L", "CMYK", l2cmyk },
|
{ "L", "CMYK", l2cmyk },
|
||||||
{ "L", "YCbCr", l2ycbcr },
|
{ "L", "YCbCr", l2ycbcr },
|
||||||
|
{ "L", "HSV", l2hsv },
|
||||||
|
|
||||||
{ "LA", "L", la2l },
|
{ "LA", "L", la2l },
|
||||||
{ "LA", "La", lA2la },
|
{ "LA", "La", lA2la },
|
||||||
|
@ -879,6 +959,7 @@ static struct {
|
||||||
{ "LA", "RGBX", la2rgb },
|
{ "LA", "RGBX", la2rgb },
|
||||||
{ "LA", "CMYK", la2cmyk },
|
{ "LA", "CMYK", la2cmyk },
|
||||||
{ "LA", "YCbCr", la2ycbcr },
|
{ "LA", "YCbCr", la2ycbcr },
|
||||||
|
{ "LA", "HSV", la2hsv },
|
||||||
|
|
||||||
{ "La", "LA", la2lA },
|
{ "La", "LA", la2lA },
|
||||||
|
|
||||||
|
@ -887,6 +968,7 @@ static struct {
|
||||||
{ "I", "RGB", i2rgb },
|
{ "I", "RGB", i2rgb },
|
||||||
{ "I", "RGBA", i2rgb },
|
{ "I", "RGBA", i2rgb },
|
||||||
{ "I", "RGBX", i2rgb },
|
{ "I", "RGBX", i2rgb },
|
||||||
|
{ "I", "HSV", i2hsv },
|
||||||
|
|
||||||
{ "F", "L", f2l },
|
{ "F", "L", f2l },
|
||||||
{ "F", "I", f2i },
|
{ "F", "I", f2i },
|
||||||
|
@ -915,6 +997,7 @@ static struct {
|
||||||
{ "RGBA", "RGBX", rgb2rgba },
|
{ "RGBA", "RGBX", rgb2rgba },
|
||||||
{ "RGBA", "CMYK", rgb2cmyk },
|
{ "RGBA", "CMYK", rgb2cmyk },
|
||||||
{ "RGBA", "YCbCr", ImagingConvertRGB2YCbCr },
|
{ "RGBA", "YCbCr", ImagingConvertRGB2YCbCr },
|
||||||
|
{ "RGBA", "HSV", rgb2hsv },
|
||||||
|
|
||||||
{ "RGBa", "RGBA", rgba2rgbA },
|
{ "RGBa", "RGBA", rgba2rgbA },
|
||||||
|
|
||||||
|
@ -926,10 +1009,12 @@ static struct {
|
||||||
{ "RGBX", "RGB", rgba2rgb },
|
{ "RGBX", "RGB", rgba2rgb },
|
||||||
{ "RGBX", "CMYK", rgb2cmyk },
|
{ "RGBX", "CMYK", rgb2cmyk },
|
||||||
{ "RGBX", "YCbCr", ImagingConvertRGB2YCbCr },
|
{ "RGBX", "YCbCr", ImagingConvertRGB2YCbCr },
|
||||||
|
{ "RGBX", "HSV", rgb2hsv },
|
||||||
|
|
||||||
{ "CMYK", "RGB", cmyk2rgb },
|
{ "CMYK", "RGB", cmyk2rgb },
|
||||||
{ "CMYK", "RGBA", cmyk2rgb },
|
{ "CMYK", "RGBA", cmyk2rgb },
|
||||||
{ "CMYK", "RGBX", cmyk2rgb },
|
{ "CMYK", "RGBX", cmyk2rgb },
|
||||||
|
{ "CMYK", "HSV", cmyk2hsv },
|
||||||
|
|
||||||
{ "YCbCr", "L", ycbcr2l },
|
{ "YCbCr", "L", ycbcr2l },
|
||||||
{ "YCbCr", "LA", ycbcr2la },
|
{ "YCbCr", "LA", ycbcr2la },
|
||||||
|
@ -1101,6 +1186,28 @@ pa2rgb(UINT8* out, const UINT8* in, int xsize, const UINT8* palette)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
p2hsv(UINT8* out, const UINT8* in, int xsize, const UINT8* palette)
|
||||||
|
{
|
||||||
|
int x;
|
||||||
|
for (x = 0; x < xsize; x++, out += 4) {
|
||||||
|
const UINT8* rgb = &palette[*in++ * 4];
|
||||||
|
rgb2hsv_row(out, rgb);
|
||||||
|
out[3] = 255;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
pa2hsv(UINT8* out, const UINT8* in, int xsize, const UINT8* palette)
|
||||||
|
{
|
||||||
|
int x;
|
||||||
|
for (x = 0; x < xsize; x++, in += 4, out += 4) {
|
||||||
|
const UINT8* rgb = &palette[in[0] * 4];
|
||||||
|
rgb2hsv_row(out, rgb);
|
||||||
|
out[3] = 255;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
static void
|
static void
|
||||||
p2rgba(UINT8* out, const UINT8* in, int xsize, const UINT8* palette)
|
p2rgba(UINT8* out, const UINT8* in, int xsize, const UINT8* palette)
|
||||||
{
|
{
|
||||||
|
@ -1192,6 +1299,8 @@ frompalette(Imaging imOut, Imaging imIn, const char *mode)
|
||||||
convert = alpha ? pa2cmyk : p2cmyk;
|
convert = alpha ? pa2cmyk : p2cmyk;
|
||||||
else if (strcmp(mode, "YCbCr") == 0)
|
else if (strcmp(mode, "YCbCr") == 0)
|
||||||
convert = alpha ? pa2ycbcr : p2ycbcr;
|
convert = alpha ? pa2ycbcr : p2ycbcr;
|
||||||
|
else if (strcmp(mode, "HSV") == 0)
|
||||||
|
convert = alpha ? pa2hsv : p2hsv;
|
||||||
else
|
else
|
||||||
return (Imaging) ImagingError_ValueError("conversion not supported");
|
return (Imaging) ImagingError_ValueError("conversion not supported");
|
||||||
|
|
||||||
|
|
|
@ -834,7 +834,7 @@ ellipse(Imaging im, int x0, int y0, int x1, int y1,
|
||||||
|
|
||||||
// Build edge list
|
// Build edge list
|
||||||
// malloc check UNDONE, FLOAT?
|
// malloc check UNDONE, FLOAT?
|
||||||
maxEdgeCount = end - start;
|
maxEdgeCount = ceil(end - start);
|
||||||
if (inner) {
|
if (inner) {
|
||||||
maxEdgeCount *= 2;
|
maxEdgeCount *= 2;
|
||||||
}
|
}
|
||||||
|
|
|
@ -132,7 +132,7 @@ ImagingNewPrologueSubtype(const char *mode, int xsize, int ysize, int size)
|
||||||
|
|
||||||
} else if (strcmp(mode, "BGR;15") == 0) {
|
} else if (strcmp(mode, "BGR;15") == 0) {
|
||||||
/* EXPERIMENTAL */
|
/* EXPERIMENTAL */
|
||||||
/* 15-bit true colour */
|
/* 15-bit reversed true colour */
|
||||||
im->bands = 1;
|
im->bands = 1;
|
||||||
im->pixelsize = 2;
|
im->pixelsize = 2;
|
||||||
im->linesize = (xsize*2 + 3) & -4;
|
im->linesize = (xsize*2 + 3) & -4;
|
||||||
|
|
3
winbuild/appveyor_install_pypy3.cmd
Normal file
3
winbuild/appveyor_install_pypy3.cmd
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
curl -fsSL -o pypy3.zip http://buildbot.pypy.org/nightly/py3.6/pypy-c-jit-97588-7392d01b93d0-win32.zip
|
||||||
|
7z x pypy3.zip -oc:\
|
||||||
|
c:\Python37\Scripts\virtualenv.exe -p c:\pypy-c-jit-97588-7392d01b93d0-win32\pypy3.exe c:\vp\pypy3
|
|
@ -84,7 +84,8 @@ def vc_setup(compiler, bit):
|
||||||
arch = "x86" if bit == 32 else "x86_amd64"
|
arch = "x86" if bit == 32 else "x86_amd64"
|
||||||
script = (
|
script = (
|
||||||
r"""
|
r"""
|
||||||
call "C:\Program Files (x86)\Microsoft Visual Studio 14.0\VC\vcvarsall.bat" %s"""
|
call "C:\Program Files (x86)\Microsoft Visual Studio 14.0\VC\vcvarsall.bat" %s
|
||||||
|
echo on"""
|
||||||
% arch
|
% arch
|
||||||
)
|
)
|
||||||
return script
|
return script
|
||||||
|
@ -191,16 +192,14 @@ def run_one(op):
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
opts, args = getopt.getopt(sys.argv[1:], "", ["clean", "dist", "wheel"])
|
opts, args = getopt.getopt(sys.argv[1:], "", ["clean", "wheel"])
|
||||||
opts = dict(opts)
|
opts = dict(opts)
|
||||||
|
|
||||||
if "--clean" in opts:
|
if "--clean" in opts:
|
||||||
clean()
|
clean()
|
||||||
|
|
||||||
op = "install"
|
op = "install"
|
||||||
if "--dist" in opts:
|
if "--wheel" in opts:
|
||||||
op = "bdist_wininst --user-access-control=auto"
|
|
||||||
elif "--wheel" in opts:
|
|
||||||
op = "bdist_wheel"
|
op = "bdist_wheel"
|
||||||
|
|
||||||
if "PYTHON" in os.environ:
|
if "PYTHON" in os.environ:
|
||||||
|
|
|
@ -37,8 +37,8 @@ virtualenv as well, reducing the number of packages that we need to
|
||||||
install.)
|
install.)
|
||||||
|
|
||||||
Download the rest of the Pythons by opening a command window, changing
|
Download the rest of the Pythons by opening a command window, changing
|
||||||
to the `winbuild` directory, and running `python
|
to the ``winbuild`` directory, and running ``python
|
||||||
get_pythons.py`.
|
get_pythons.py``.
|
||||||
|
|
||||||
UNDONE -- gpg verify the signatures (note that we can download from
|
UNDONE -- gpg verify the signatures (note that we can download from
|
||||||
https)
|
https)
|
||||||
|
@ -65,8 +65,8 @@ Dependencies
|
||||||
------------
|
------------
|
||||||
|
|
||||||
The script 'build_dep.py' downloads and builds the dependencies. Open
|
The script 'build_dep.py' downloads and builds the dependencies. Open
|
||||||
a command window, change directory into `winbuild` and run `python
|
a command window, change directory into ``winbuild`` and run ``python
|
||||||
build_dep.py`.
|
build_dep.py``.
|
||||||
|
|
||||||
This will download libjpeg, libtiff, libz, and freetype. It will then
|
This will download libjpeg, libtiff, libz, and freetype. It will then
|
||||||
compile 32 and 64-bit versions of the libraries, with both versions of
|
compile 32 and 64-bit versions of the libraries, with both versions of
|
||||||
|
@ -78,9 +78,9 @@ UNDONE -- webp, jpeg2k not recognized
|
||||||
Building Pillow
|
Building Pillow
|
||||||
---------------
|
---------------
|
||||||
|
|
||||||
Once the dependencies are built, run `python build.py --clean` to
|
Once the dependencies are built, run ``python build.py --clean`` to
|
||||||
build and install Pillow in virtualenvs for each python
|
build and install Pillow in virtualenvs for each Python
|
||||||
build. `build.py --dist` will build Windows installers instead of
|
build. ``build.py --wheel`` will build wheels instead of
|
||||||
installing into virtualenvs.
|
installing into virtualenvs.
|
||||||
|
|
||||||
UNDONE -- suppressed output, what about failures.
|
UNDONE -- suppressed output, what about failures.
|
||||||
|
@ -88,6 +88,6 @@ UNDONE -- suppressed output, what about failures.
|
||||||
Testing Pillow
|
Testing Pillow
|
||||||
--------------
|
--------------
|
||||||
|
|
||||||
Build and install Pillow, then run `python test.py` from the
|
Build and install Pillow, then run ``python test.py`` from the
|
||||||
`winbuild` directory.
|
``winbuild`` directory.
|
||||||
|
|
||||||
|
|
|
@ -67,7 +67,6 @@ def extract_openjpeg(compiler):
|
||||||
r"""
|
r"""
|
||||||
rem build openjpeg
|
rem build openjpeg
|
||||||
setlocal
|
setlocal
|
||||||
@echo on
|
|
||||||
cd %%BUILD%%
|
cd %%BUILD%%
|
||||||
mkdir %%INCLIB%%\openjpeg-2.0
|
mkdir %%INCLIB%%\openjpeg-2.0
|
||||||
copy /Y /B openjpeg-2.0.0-win32-x86\include\openjpeg-2.0 %%INCLIB%%\openjpeg-2.0
|
copy /Y /B openjpeg-2.0.0-win32-x86\include\openjpeg-2.0 %%INCLIB%%\openjpeg-2.0
|
||||||
|
@ -114,6 +113,7 @@ def setup_compiler(compiler):
|
||||||
return (
|
return (
|
||||||
r"""setlocal EnableDelayedExpansion
|
r"""setlocal EnableDelayedExpansion
|
||||||
call "%%ProgramFiles%%\Microsoft SDKs\Windows\%(env_version)s\Bin\SetEnv.Cmd" /Release %(env_flags)s
|
call "%%ProgramFiles%%\Microsoft SDKs\Windows\%(env_version)s\Bin\SetEnv.Cmd" /Release %(env_flags)s
|
||||||
|
echo on
|
||||||
set INCLIB=%%INCLIB%%\%(inc_dir)s
|
set INCLIB=%%INCLIB%%\%(inc_dir)s
|
||||||
""" # noqa: E501
|
""" # noqa: E501
|
||||||
% compiler
|
% compiler
|
||||||
|
@ -139,12 +139,11 @@ setlocal
|
||||||
"""
|
"""
|
||||||
+ vc_setup(compiler, bit)
|
+ vc_setup(compiler, bit)
|
||||||
+ r"""
|
+ r"""
|
||||||
@echo on
|
|
||||||
cd /D %%OPENJPEG%%%(inc_dir)s
|
cd /D %%OPENJPEG%%%(inc_dir)s
|
||||||
|
|
||||||
%%CMAKE%% -DBUILD_THIRDPARTY:BOOL=OFF -DBUILD_SHARED_LIBS:BOOL=OFF -G "NMake Makefiles" .
|
%%CMAKE%% -DBUILD_THIRDPARTY:BOOL=OFF -DBUILD_SHARED_LIBS:BOOL=OFF -DCMAKE_BUILD_TYPE=Release -G "NMake Makefiles" .
|
||||||
nmake -f Makefile clean
|
nmake -nologo -f Makefile clean
|
||||||
nmake -f Makefile
|
nmake -nologo -f Makefile
|
||||||
copy /Y /B bin\* %%INCLIB%%
|
copy /Y /B bin\* %%INCLIB%%
|
||||||
mkdir %%INCLIB%%\openjpeg-%(op_ver)s
|
mkdir %%INCLIB%%\openjpeg-%(op_ver)s
|
||||||
copy /Y /B src\lib\openjp2\*.h %%INCLIB%%\openjpeg-%(op_ver)s
|
copy /Y /B src\lib\openjp2\*.h %%INCLIB%%\openjpeg-%(op_ver)s
|
||||||
|
@ -164,9 +163,9 @@ setlocal
|
||||||
+ vc_setup(compiler, bit)
|
+ vc_setup(compiler, bit)
|
||||||
+ r"""
|
+ r"""
|
||||||
cd /D %%JPEG%%
|
cd /D %%JPEG%%
|
||||||
nmake -f makefile.vc setup-vc6
|
nmake -nologo -f makefile.vc setup-vc6
|
||||||
nmake -f makefile.vc clean
|
nmake -nologo -f makefile.vc clean
|
||||||
nmake -f makefile.vc libjpeg.lib
|
nmake -nologo -f makefile.vc nodebug=1 libjpeg.lib
|
||||||
copy /Y /B *.dll %%INCLIB%%
|
copy /Y /B *.dll %%INCLIB%%
|
||||||
copy /Y /B *.lib %%INCLIB%%
|
copy /Y /B *.lib %%INCLIB%%
|
||||||
copy /Y /B j*.h %%INCLIB%%
|
copy /Y /B j*.h %%INCLIB%%
|
||||||
|
@ -175,8 +174,8 @@ endlocal
|
||||||
rem Build zlib
|
rem Build zlib
|
||||||
setlocal
|
setlocal
|
||||||
cd /D %%ZLIB%%
|
cd /D %%ZLIB%%
|
||||||
nmake -f win32\Makefile.msc clean
|
nmake -nologo -f win32\Makefile.msc clean
|
||||||
nmake -f win32\Makefile.msc zlib.lib
|
nmake -nologo -f win32\Makefile.msc zlib.lib
|
||||||
copy /Y /B *.dll %%INCLIB%%
|
copy /Y /B *.dll %%INCLIB%%
|
||||||
copy /Y /B *.lib %%INCLIB%%
|
copy /Y /B *.lib %%INCLIB%%
|
||||||
copy /Y /B zlib.lib %%INCLIB%%\z.lib
|
copy /Y /B zlib.lib %%INCLIB%%\z.lib
|
||||||
|
@ -191,7 +190,7 @@ setlocal
|
||||||
+ r"""
|
+ r"""
|
||||||
cd /D %%WEBP%%
|
cd /D %%WEBP%%
|
||||||
rd /S /Q %%WEBP%%\output\release-static
|
rd /S /Q %%WEBP%%\output\release-static
|
||||||
nmake -f Makefile.vc CFG=release-static RTLIBCFG=static OBJDIR=output all
|
nmake -nologo -f Makefile.vc CFG=release-static RTLIBCFG=static OBJDIR=output all
|
||||||
copy /Y /B output\release-static\%(webp_platform)s\lib\* %%INCLIB%%
|
copy /Y /B output\release-static\%(webp_platform)s\lib\* %%INCLIB%%
|
||||||
mkdir %%INCLIB%%\webp
|
mkdir %%INCLIB%%\webp
|
||||||
copy /Y /B src\webp\*.h %%INCLIB%%\\webp
|
copy /Y /B src\webp\*.h %%INCLIB%%\\webp
|
||||||
|
@ -206,8 +205,8 @@ rem do after building jpeg and zlib
|
||||||
copy %%~dp0\nmake.opt %%TIFF%%
|
copy %%~dp0\nmake.opt %%TIFF%%
|
||||||
|
|
||||||
cd /D %%TIFF%%
|
cd /D %%TIFF%%
|
||||||
nmake -f makefile.vc clean
|
nmake -nologo -f makefile.vc clean
|
||||||
nmake -f makefile.vc lib
|
nmake -nologo -f makefile.vc lib
|
||||||
copy /Y /B libtiff\*.dll %%INCLIB%%
|
copy /Y /B libtiff\*.dll %%INCLIB%%
|
||||||
copy /Y /B libtiff\*.lib %%INCLIB%%
|
copy /Y /B libtiff\*.lib %%INCLIB%%
|
||||||
copy /Y /B libtiff\tiff*.h %%INCLIB%%
|
copy /Y /B libtiff\tiff*.h %%INCLIB%%
|
||||||
|
@ -320,7 +319,7 @@ cd /D %%GHOSTSCRIPT%%
|
||||||
set WIN64=""
|
set WIN64=""
|
||||||
"""
|
"""
|
||||||
script += r"""
|
script += r"""
|
||||||
nmake -f psi/msvc.mak
|
nmake -nologo -f psi/msvc.mak
|
||||||
copy /Y /B bin\ C:\Python27\
|
copy /Y /B bin\ C:\Python27\
|
||||||
endlocal
|
endlocal
|
||||||
"""
|
"""
|
||||||
|
|
|
@ -8,6 +8,7 @@ pythons = {
|
||||||
"pypy2": {"compiler": 7, "vc": 2010},
|
"pypy2": {"compiler": 7, "vc": 2010},
|
||||||
"35": {"compiler": 7.1, "vc": 2015},
|
"35": {"compiler": 7.1, "vc": 2015},
|
||||||
"36": {"compiler": 7.1, "vc": 2015},
|
"36": {"compiler": 7.1, "vc": 2015},
|
||||||
|
"pypy3": {"compiler": 7.1, "vc": 2015},
|
||||||
"37": {"compiler": 7.1, "vc": 2015},
|
"37": {"compiler": 7.1, "vc": 2015},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -35,9 +36,9 @@ libs = {
|
||||||
"dir": "tiff-4.0.10",
|
"dir": "tiff-4.0.10",
|
||||||
},
|
},
|
||||||
"freetype": {
|
"freetype": {
|
||||||
"url": "https://download.savannah.gnu.org/releases/freetype/freetype-2.10.0.tar.gz", # noqa: E501
|
"url": "https://download.savannah.gnu.org/releases/freetype/freetype-2.10.1.tar.gz", # noqa: E501
|
||||||
"filename": PILLOW_DEPENDS_DIR + "freetype-2.10.0.tar.gz",
|
"filename": PILLOW_DEPENDS_DIR + "freetype-2.10.1.tar.gz",
|
||||||
"dir": "freetype-2.10.0",
|
"dir": "freetype-2.10.1",
|
||||||
},
|
},
|
||||||
"lcms": {
|
"lcms": {
|
||||||
"url": SF_MIRROR + "/project/lcms/lcms/2.7/lcms2-2.7.zip",
|
"url": SF_MIRROR + "/project/lcms/lcms/2.7/lcms2-2.7.zip",
|
||||||
|
|
Loading…
Reference in New Issue
Block a user