Skip to content

Commit

Permalink
Merge branch 'master' into patch-1
Browse files Browse the repository at this point in the history
  • Loading branch information
hugovk committed Sep 20, 2019
2 parents 19ab3c3 + 736b843 commit f5aed1a
Show file tree
Hide file tree
Showing 59 changed files with 938 additions and 210 deletions.
29 changes: 29 additions & 0 deletions .github/workflows/lint.yml
@@ -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
44 changes: 34 additions & 10 deletions CHANGES.rst
Expand Up @@ -7,13 +7,37 @@ Changelog (Pillow)

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

- 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
- Depends: Update libwebp to 1.0.3 #3983, libimagequant to 2.12.5 #3993, freetype to 2.10.1 #3991
[radarhere]

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

- Added an `image.entropy()` method (second revision) #3608
- Added an ``image.entropy()`` method (second revision) #3608
[fish2000]

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

- Remove unnecessary `#if 1` directive #3072
- Remove unnecessary ``#if 1`` directive #3072
[jdufresne]

- Remove unused Python class, Path #3070
Expand Down Expand Up @@ -1237,7 +1261,7 @@ Changelog (Pillow)
- Add decompression bomb check to Image.crop #2410
[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]

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

- Prevent `nose -v` printing docstrings #2369
- Prevent ``nose -v`` printing docstrings #2369
[hugovk]

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

- Allow ICC profile from `encoderinfo` while saving PNGs #1909
- Allow ICC profile from ``encoderinfo`` while saving PNGs #1909
[homm]

- Fix integer overflow on ILP32 systems (32-bit Linux). #1975
Expand Down Expand Up @@ -2362,7 +2386,7 @@ Changelog (Pillow)
- Added PDF multipage saving #1445
[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]

- Load more broken images #1428
Expand Down Expand Up @@ -2854,7 +2878,7 @@ Changelog (Pillow)
- Doc cleanup
[wiredfool]

- Fix `ImageStat` docs #796
- Fix ``ImageStat`` docs #796
[akx]

- Added docs for ExifTags #794
Expand Down Expand Up @@ -3291,7 +3315,7 @@ Changelog (Pillow)
- Add RGBA support to ImageColor #309
[yoavweiss]

- Test for `str`, not `"utf-8"` #306 (fixes #304)
- Test for ``str``, not ``"utf-8"`` #306 (fixes #304)
[mjpieters]

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

- 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]

- Support explicitly disabling features via --disable-* options #240
Expand Down
2 changes: 1 addition & 1 deletion README.rst
Expand Up @@ -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
: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

.. |version| image:: https://img.shields.io/pypi/v/pillow.svg
Expand Down
Binary file added Tests/images/g4_orientation_1.tif
Binary file not shown.
Binary file added Tests/images/g4_orientation_2.tif
Binary file not shown.
Binary file added Tests/images/g4_orientation_3.tif
Binary file not shown.
Binary file added Tests/images/g4_orientation_4.tif
Binary file not shown.
Binary file added Tests/images/g4_orientation_5.tif
Binary file not shown.
Binary file added Tests/images/g4_orientation_6.tif
Binary file not shown.
Binary file added Tests/images/g4_orientation_7.tif
Binary file not shown.
Binary file added Tests/images/g4_orientation_8.tif
Binary file not shown.
Binary file added Tests/images/imagedraw_arc_width_non_whole_angle.png
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added Tests/images/imagedraw_floodfill_not_negative.png
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added Tests/images/imagedraw_stroke_different.png
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added Tests/images/imagedraw_stroke_multiline.png
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added Tests/images/imagedraw_stroke_same.png
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added Tests/images/test_direction_ttb_stroke.png
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
20 changes: 20 additions & 0 deletions Tests/test_file_gif.py
Expand Up @@ -495,6 +495,26 @@ def test_identical_frames(self):
# Assert that the new duration is the total of the identical frames
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):
number_of_loops = 2

Expand Down
4 changes: 4 additions & 0 deletions Tests/test_file_jpeg.py
Expand Up @@ -369,6 +369,10 @@ def test_truncated_jpeg_throws_IOError(self):
with self.assertRaises(IOError):
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):
im = Image.open(test_file)
f = self.tempfile("temp.jpg")
Expand Down
6 changes: 6 additions & 0 deletions Tests/test_file_jpeg2k.py
Expand Up @@ -91,6 +91,12 @@ def test_tiled_offset_rt(self):
)
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):
im = self.roundtrip(test_card, irreversible=True, quality_layers=[20])
self.assert_image_similar(im, test_card, 2.0)
Expand Down
22 changes: 22 additions & 0 deletions Tests/test_file_libtiff.py
Expand Up @@ -81,6 +81,19 @@ def test_g4_tiff_bytesio(self):
self.assertEqual(im.size, (500, 500))
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):
""" Checking that we're actually getting the data that we expect"""
png = Image.open("Tests/images/hopper_bw_500.png")
Expand Down Expand Up @@ -825,3 +838,12 @@ def test_no_rows_per_strip(self):
im = Image.open(infile)
im.load()
self.assertEqual(im.size, (950, 975))

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)
8 changes: 5 additions & 3 deletions Tests/test_file_tiff_metadata.py
Expand Up @@ -222,7 +222,7 @@ def test_exif_div_zero(self):
self.assertEqual(0, reloaded.tag_v2[41988].numerator)
self.assertEqual(0, reloaded.tag_v2[41988].denominator)

def test_expty_values(self):
def test_empty_values(self):
data = io.BytesIO(
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"
Expand All @@ -239,11 +239,13 @@ def test_expty_values(self):
def test_PhotoshopInfo(self):
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")
im.save(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):
ifd = TiffImagePlugin.ImageFileDirectory_v2()
Expand Down
1 change: 1 addition & 0 deletions Tests/test_image_convert.py
Expand Up @@ -23,6 +23,7 @@ def convert(im, mode):
"RGBX",
"CMYK",
"YCbCr",
"HSV",
)

for mode in modes:
Expand Down
84 changes: 82 additions & 2 deletions Tests/test_imagedraw.py
@@ -1,8 +1,8 @@
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)
WHITE = (255, 255, 255)
Expand All @@ -29,6 +29,8 @@

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

HAS_FREETYPE = features.check("freetype2")


class TestImageDraw(PillowTestCase):
def test_sanity(self):
Expand Down Expand Up @@ -140,6 +142,18 @@ def test_arc_width_fill(self):
# Assert
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):
# Arrange
small = Image.open("Tests/images/pil123rgba.png").resize((50, 50))
Expand Down Expand Up @@ -559,6 +573,24 @@ def test_floodfill_thresh(self):
# Assert
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(
self, size, mode=DEFAULT_MODE, background1=WHITE, background2=GRAY
):
Expand Down Expand Up @@ -771,6 +803,54 @@ def test_textsize_empty_string(self):
draw.textsize("\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):
# Prepare shape
x0, y0 = 5, 5
Expand Down

0 comments on commit f5aed1a

Please sign in to comment.