Skip to content

Commit f5aed1a

Browse files
authored
Merge branch 'master' into patch-1
2 parents 19ab3c3 + 736b843 commit f5aed1a

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

59 files changed

+938
-210
lines changed

.github/workflows/lint.yml

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
name: Lint
2+
3+
on: [push, pull_request]
4+
5+
jobs:
6+
build:
7+
8+
runs-on: ubuntu-latest
9+
strategy:
10+
matrix:
11+
python: [3.7]
12+
13+
name: Python ${{ matrix.python }}
14+
15+
steps:
16+
- uses: actions/checkout@v1
17+
18+
- name: Set up Python ${{ matrix.python }}
19+
uses: actions/setup-python@v1
20+
with:
21+
python-version: ${{ matrix.python }}
22+
23+
- name: Install dependencies
24+
run: |
25+
python -m pip install --upgrade pip
26+
python -m pip install --upgrade tox
27+
28+
- name: Lint
29+
run: tox -e lint

CHANGES.rst

Lines changed: 34 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -7,13 +7,37 @@ Changelog (Pillow)
77

88
- This is the last Pillow release to support Python 2.7 #3642
99

10+
- Lazily use ImageFileDirectory_v1 values from Exif #4031
11+
[radarhere]
12+
13+
- Improved HSV conversion #4004
14+
[radarhere]
15+
16+
- Added text stroking #3978
17+
[radarhere, hugovk]
18+
19+
- No more deprecated bdist_wininst .exe installers #4029
20+
[hugovk]
21+
22+
- Do not allow floodfill to extend into negative coordinates #4017
23+
[radarhere]
24+
25+
- Fixed arc drawing bug for a non-whole number of degrees #4014
26+
[radarhere]
27+
28+
- Fix bug when merging identical images to GIF with a list of durations #4003
29+
[djy0, radarhere]
30+
31+
- Fix bug in TIFF loading of BufferedReader #3998
32+
[chadawagner]
33+
1034
- Added fallback for finding ld on MinGW Cygwin #4019
1135
[radarhere]
1236

1337
- Remove indirect dependencies from requirements.txt #3976
1438
[hugovk]
1539

16-
- Depends: Update libwebp to 1.0.3 #3983, libimagequant to 2.12.5 #3993
40+
- Depends: Update libwebp to 1.0.3 #3983, libimagequant to 2.12.5 #3993, freetype to 2.10.1 #3991
1741
[radarhere]
1842

1943
- Change overflow check to use PY_SSIZE_T_MAX #3964
@@ -64,7 +88,7 @@ Changelog (Pillow)
6488
- Updated TIFF tile descriptors to match current decoding functionality #3795
6589
[dmnisson]
6690

67-
- Added an `image.entropy()` method (second revision) #3608
91+
- Added an ``image.entropy()`` method (second revision) #3608
6892
[fish2000]
6993

7094
- Pass the correct types to PyArg_ParseTuple #3880
@@ -700,7 +724,7 @@ Changelog (Pillow)
700724
- Enable background colour parameter on rotate #3057
701725
[storesource]
702726

703-
- Remove unnecessary `#if 1` directive #3072
727+
- Remove unnecessary ``#if 1`` directive #3072
704728
[jdufresne]
705729

706730
- Remove unused Python class, Path #3070
@@ -1237,7 +1261,7 @@ Changelog (Pillow)
12371261
- Add decompression bomb check to Image.crop #2410
12381262
[wiredfool]
12391263

1240-
- ImageFile: Ensure that the `err_code` variable is initialized in case of exception. #2363
1264+
- ImageFile: Ensure that the ``err_code`` variable is initialized in case of exception. #2363
12411265
[alexkiro]
12421266

12431267
- Tiff: Support append_images for saving multipage TIFFs #2406
@@ -1474,7 +1498,7 @@ Changelog (Pillow)
14741498
- Removed PIL 1.0 era TK readme that concerns Windows 95/NT #2360
14751499
[wiredfool]
14761500

1477-
- Prevent `nose -v` printing docstrings #2369
1501+
- Prevent ``nose -v`` printing docstrings #2369
14781502
[hugovk]
14791503

14801504
- Replaced absolute PIL imports with relative imports #2349
@@ -1919,7 +1943,7 @@ Changelog (Pillow)
19191943
- Changed depends/install_*.sh urls to point to github pillow-depends repo #1983
19201944
[wiredfool]
19211945

1922-
- Allow ICC profile from `encoderinfo` while saving PNGs #1909
1946+
- Allow ICC profile from ``encoderinfo`` while saving PNGs #1909
19231947
[homm]
19241948

19251949
- Fix integer overflow on ILP32 systems (32-bit Linux). #1975
@@ -2362,7 +2386,7 @@ Changelog (Pillow)
23622386
- Added PDF multipage saving #1445
23632387
[radarhere]
23642388

2365-
- 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
2389+
- 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
23662390
[radarhere]
23672391

23682392
- Load more broken images #1428
@@ -2854,7 +2878,7 @@ Changelog (Pillow)
28542878
- Doc cleanup
28552879
[wiredfool]
28562880

2857-
- Fix `ImageStat` docs #796
2881+
- Fix ``ImageStat`` docs #796
28582882
[akx]
28592883

28602884
- Added docs for ExifTags #794
@@ -3291,7 +3315,7 @@ Changelog (Pillow)
32913315
- Add RGBA support to ImageColor #309
32923316
[yoavweiss]
32933317

3294-
- Test for `str`, not `"utf-8"` #306 (fixes #304)
3318+
- Test for ``str``, not ``"utf-8"`` #306 (fixes #304)
32953319
[mjpieters]
32963320

32973321
- Fix missing import os in _util.py #303
@@ -3397,7 +3421,7 @@ Changelog (Pillow)
33973421

33983422
- Partial work to add a wrapper for WebPGetFeatures to correctly support #220 (fixes #204)
33993423

3400-
- Significant performance improvement of `alpha_composite` function #156
3424+
- Significant performance improvement of ``alpha_composite`` function #156
34013425
[homm]
34023426

34033427
- Support explicitly disabling features via --disable-* options #240

README.rst

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -67,7 +67,7 @@ To report a security vulnerability, please follow the procedure described in the
6767
.. |zenodo| image:: https://zenodo.org/badge/17549/python-pillow/Pillow.svg
6868
:target: https://zenodo.org/badge/latestdoi/17549/python-pillow/Pillow
6969

70-
.. |tidelift| image:: https://tidelift.com/badges/github/python-pillow/Pillow?style=flat
70+
.. |tidelift| image:: https://tidelift.com/badges/package/pypi/Pillow?style=flat
7171
:target: https://tidelift.com/subscription/pkg/pypi-pillow?utm_source=pypi-pillow&utm_medium=referral&utm_campaign=readme
7272

7373
.. |version| image:: https://img.shields.io/pypi/v/pillow.svg

Tests/images/g4_orientation_1.tif

708 Bytes
Binary file not shown.

Tests/images/g4_orientation_2.tif

930 Bytes
Binary file not shown.

Tests/images/g4_orientation_3.tif

926 Bytes
Binary file not shown.

Tests/images/g4_orientation_4.tif

928 Bytes
Binary file not shown.

Tests/images/g4_orientation_5.tif

1 KB
Binary file not shown.

Tests/images/g4_orientation_6.tif

1 KB
Binary file not shown.

Tests/images/g4_orientation_7.tif

1022 Bytes
Binary file not shown.

Tests/images/g4_orientation_8.tif

1 KB
Binary file not shown.
Loading
214 Bytes
Loading
2.21 KB
Loading
3.97 KB
Loading
1.3 KB
Loading
3.66 KB
Loading

Tests/test_file_gif.py

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -495,6 +495,26 @@ def test_identical_frames(self):
495495
# Assert that the new duration is the total of the identical frames
496496
self.assertEqual(reread.info["duration"], 4500)
497497

498+
def test_identical_frames_to_single_frame(self):
499+
for duration in ([1000, 1500, 2000, 4000], (1000, 1500, 2000, 4000), 8500):
500+
out = self.tempfile("temp.gif")
501+
im_list = [
502+
Image.new("L", (100, 100), "#000"),
503+
Image.new("L", (100, 100), "#000"),
504+
Image.new("L", (100, 100), "#000"),
505+
]
506+
507+
im_list[0].save(
508+
out, save_all=True, append_images=im_list[1:], duration=duration
509+
)
510+
reread = Image.open(out)
511+
512+
# Assert that all frames were combined
513+
self.assertEqual(reread.n_frames, 1)
514+
515+
# Assert that the new duration is the total of the identical frames
516+
self.assertEqual(reread.info["duration"], 8500)
517+
498518
def test_number_of_loops(self):
499519
number_of_loops = 2
500520

Tests/test_file_jpeg.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -369,6 +369,10 @@ def test_truncated_jpeg_throws_IOError(self):
369369
with self.assertRaises(IOError):
370370
im.load()
371371

372+
# Test that the error is raised if loaded a second time
373+
with self.assertRaises(IOError):
374+
im.load()
375+
372376
def _n_qtables_helper(self, n, test_file):
373377
im = Image.open(test_file)
374378
f = self.tempfile("temp.jpg")

Tests/test_file_jpeg2k.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -91,6 +91,12 @@ def test_tiled_offset_rt(self):
9191
)
9292
self.assert_image_equal(im, test_card)
9393

94+
def test_tiled_offset_too_small(self):
95+
with self.assertRaises(ValueError):
96+
self.roundtrip(
97+
test_card, tile_size=(128, 128), tile_offset=(0, 0), offset=(128, 32)
98+
)
99+
94100
def test_irreversible_rt(self):
95101
im = self.roundtrip(test_card, irreversible=True, quality_layers=[20])
96102
self.assert_image_similar(im, test_card, 2.0)

Tests/test_file_libtiff.py

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,19 @@ def test_g4_tiff_bytesio(self):
8181
self.assertEqual(im.size, (500, 500))
8282
self._assert_noerr(im)
8383

84+
def test_g4_non_disk_file_object(self):
85+
"""Testing loading from non-disk non-BytesIO file object"""
86+
test_file = "Tests/images/hopper_g4_500.tif"
87+
s = io.BytesIO()
88+
with open(test_file, "rb") as f:
89+
s.write(f.read())
90+
s.seek(0)
91+
r = io.BufferedReader(s)
92+
im = Image.open(r)
93+
94+
self.assertEqual(im.size, (500, 500))
95+
self._assert_noerr(im)
96+
8497
def test_g4_eq_png(self):
8598
""" Checking that we're actually getting the data that we expect"""
8699
png = Image.open("Tests/images/hopper_bw_500.png")
@@ -825,3 +838,12 @@ def test_no_rows_per_strip(self):
825838
im = Image.open(infile)
826839
im.load()
827840
self.assertEqual(im.size, (950, 975))
841+
842+
def test_orientation(self):
843+
base_im = Image.open("Tests/images/g4_orientation_1.tif")
844+
845+
for i in range(2, 9):
846+
im = Image.open("Tests/images/g4_orientation_" + str(i) + ".tif")
847+
im.load()
848+
849+
self.assert_image_similar(base_im, im, 0.7)

Tests/test_file_tiff_metadata.py

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -222,7 +222,7 @@ def test_exif_div_zero(self):
222222
self.assertEqual(0, reloaded.tag_v2[41988].numerator)
223223
self.assertEqual(0, reloaded.tag_v2[41988].denominator)
224224

225-
def test_expty_values(self):
225+
def test_empty_values(self):
226226
data = io.BytesIO(
227227
b"II*\x00\x08\x00\x00\x00\x03\x00\x1a\x01\x05\x00\x00\x00\x00\x00"
228228
b"\x00\x00\x00\x00\x1b\x01\x05\x00\x00\x00\x00\x00\x00\x00\x00\x00"
@@ -239,11 +239,13 @@ def test_expty_values(self):
239239
def test_PhotoshopInfo(self):
240240
im = Image.open("Tests/images/issue_2278.tif")
241241

242-
self.assertIsInstance(im.tag_v2[34377], bytes)
242+
self.assertEqual(len(im.tag_v2[34377]), 1)
243+
self.assertIsInstance(im.tag_v2[34377][0], bytes)
243244
out = self.tempfile("temp.tiff")
244245
im.save(out)
245246
reloaded = Image.open(out)
246-
self.assertIsInstance(reloaded.tag_v2[34377], bytes)
247+
self.assertEqual(len(reloaded.tag_v2[34377]), 1)
248+
self.assertIsInstance(reloaded.tag_v2[34377][0], bytes)
247249

248250
def test_too_many_entries(self):
249251
ifd = TiffImagePlugin.ImageFileDirectory_v2()

Tests/test_image_convert.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ def convert(im, mode):
2323
"RGBX",
2424
"CMYK",
2525
"YCbCr",
26+
"HSV",
2627
)
2728

2829
for mode in modes:

Tests/test_imagedraw.py

Lines changed: 82 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
import os.path
22

3-
from PIL import Image, ImageColor, ImageDraw
3+
from PIL import Image, ImageColor, ImageDraw, ImageFont, features
44

5-
from .helper import PillowTestCase, hopper
5+
from .helper import PillowTestCase, hopper, unittest
66

77
BLACK = (0, 0, 0)
88
WHITE = (255, 255, 255)
@@ -29,6 +29,8 @@
2929

3030
KITE_POINTS = [(10, 50), (70, 10), (90, 50), (70, 90), (10, 50)]
3131

32+
HAS_FREETYPE = features.check("freetype2")
33+
3234

3335
class TestImageDraw(PillowTestCase):
3436
def test_sanity(self):
@@ -140,6 +142,18 @@ def test_arc_width_fill(self):
140142
# Assert
141143
self.assert_image_similar(im, Image.open(expected), 1)
142144

145+
def test_arc_width_non_whole_angle(self):
146+
# Arrange
147+
im = Image.new("RGB", (W, H))
148+
draw = ImageDraw.Draw(im)
149+
expected = "Tests/images/imagedraw_arc_width_non_whole_angle.png"
150+
151+
# Act
152+
draw.arc(BBOX1, 10, 259.5, width=5)
153+
154+
# Assert
155+
self.assert_image_similar(im, Image.open(expected), 1)
156+
143157
def test_bitmap(self):
144158
# Arrange
145159
small = Image.open("Tests/images/pil123rgba.png").resize((50, 50))
@@ -559,6 +573,24 @@ def test_floodfill_thresh(self):
559573
# Assert
560574
self.assert_image_equal(im, Image.open("Tests/images/imagedraw_floodfill2.png"))
561575

576+
def test_floodfill_not_negative(self):
577+
# floodfill() is experimental
578+
# Test that floodfill does not extend into negative coordinates
579+
580+
# Arrange
581+
im = Image.new("RGB", (W, H))
582+
draw = ImageDraw.Draw(im)
583+
draw.line((W / 2, 0, W / 2, H / 2), fill="green")
584+
draw.line((0, H / 2, W / 2, H / 2), fill="green")
585+
586+
# Act
587+
ImageDraw.floodfill(im, (int(W / 4), int(H / 4)), ImageColor.getrgb("red"))
588+
589+
# Assert
590+
self.assert_image_equal(
591+
im, Image.open("Tests/images/imagedraw_floodfill_not_negative.png")
592+
)
593+
562594
def create_base_image_draw(
563595
self, size, mode=DEFAULT_MODE, background1=WHITE, background2=GRAY
564596
):
@@ -771,6 +803,54 @@ def test_textsize_empty_string(self):
771803
draw.textsize("\n")
772804
draw.textsize("test\n")
773805

806+
@unittest.skipUnless(HAS_FREETYPE, "ImageFont not available")
807+
def test_textsize_stroke(self):
808+
# Arrange
809+
im = Image.new("RGB", (W, H))
810+
draw = ImageDraw.Draw(im)
811+
font = ImageFont.truetype("Tests/fonts/FreeMono.ttf", 20)
812+
813+
# Act / Assert
814+
self.assertEqual(draw.textsize("A", font, stroke_width=2), (16, 20))
815+
self.assertEqual(
816+
draw.multiline_textsize("ABC\nAaaa", font, stroke_width=2), (52, 44)
817+
)
818+
819+
@unittest.skipUnless(HAS_FREETYPE, "ImageFont not available")
820+
def test_stroke(self):
821+
for suffix, stroke_fill in {"same": None, "different": "#0f0"}.items():
822+
# Arrange
823+
im = Image.new("RGB", (120, 130))
824+
draw = ImageDraw.Draw(im)
825+
font = ImageFont.truetype("Tests/fonts/FreeMono.ttf", 120)
826+
827+
# Act
828+
draw.text(
829+
(10, 10), "A", "#f00", font, stroke_width=2, stroke_fill=stroke_fill
830+
)
831+
832+
# Assert
833+
self.assert_image_similar(
834+
im, Image.open("Tests/images/imagedraw_stroke_" + suffix + ".png"), 3.1
835+
)
836+
837+
@unittest.skipUnless(HAS_FREETYPE, "ImageFont not available")
838+
def test_stroke_multiline(self):
839+
# Arrange
840+
im = Image.new("RGB", (100, 250))
841+
draw = ImageDraw.Draw(im)
842+
font = ImageFont.truetype("Tests/fonts/FreeMono.ttf", 120)
843+
844+
# Act
845+
draw.multiline_text(
846+
(10, 10), "A\nB", "#f00", font, stroke_width=2, stroke_fill="#0f0"
847+
)
848+
849+
# Assert
850+
self.assert_image_similar(
851+
im, Image.open("Tests/images/imagedraw_stroke_multiline.png"), 3.3
852+
)
853+
774854
def test_same_color_outline(self):
775855
# Prepare shape
776856
x0, y0 = 5, 5

0 commit comments

Comments
 (0)