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
radarhere committed Jun 30, 2021
2 parents c3dc6fd + 23b2151 commit 94eb2b3
Show file tree
Hide file tree
Showing 83 changed files with 1,318 additions and 369 deletions.
1 change: 1 addition & 0 deletions .ci/install.sh
Expand Up @@ -24,6 +24,7 @@ sudo apt-get -qq install libfreetype6-dev liblcms2-dev python3-tk\
python3 -m pip install --upgrade pip
PYTHONOPTIMIZE=0 python3 -m pip install cffi
python3 -m pip install coverage
python3 -m pip install defusedxml
python3 -m pip install olefile
python3 -m pip install -U pytest
python3 -m pip install -U pytest-cov
Expand Down
2 changes: 1 addition & 1 deletion .ci/test.sh
Expand Up @@ -4,4 +4,4 @@ set -e

python3 -c "from PIL import Image"

python3 -bb -m pytest -v -x -W always --cov PIL --cov Tests --cov-report term Tests
python3 -bb -m pytest -v -x -W always --cov PIL --cov Tests --cov-report term Tests $REVERSE
1 change: 1 addition & 0 deletions .github/workflows/macos-install.sh
Expand Up @@ -6,6 +6,7 @@ brew install libtiff libjpeg openjpeg libimagequant webp little-cms2 freetype op

PYTHONOPTIMIZE=0 python3 -m pip install cffi
python3 -m pip install coverage
python3 -m pip install defusedxml
python3 -m pip install olefile
python3 -m pip install -U pytest
python3 -m pip install -U pytest-cov
Expand Down
4 changes: 2 additions & 2 deletions .github/workflows/test-windows.yml
Expand Up @@ -51,8 +51,8 @@ jobs:
- name: Print build system information
run: python .github/workflows/system-info.py

- name: python -m pip install wheel pytest pytest-cov pytest-timeout
run: python -m pip install wheel pytest pytest-cov pytest-timeout
- name: python -m pip install wheel pytest pytest-cov pytest-timeout defusedxml
run: python -m pip install wheel pytest pytest-cov pytest-timeout defusedxml

- name: Install dependencies
id: install
Expand Down
5 changes: 5 additions & 0 deletions .github/workflows/test.yml
Expand Up @@ -24,6 +24,7 @@ jobs:
include:
- python-version: "3.6"
PYTHONOPTIMIZE: 1
REVERSE: "--reverse"
- python-version: "3.7"
PYTHONOPTIMIZE: 2
# Include new variables for Codecov
Expand Down Expand Up @@ -80,13 +81,17 @@ jobs:
- name: Test
run: |
if [ $REVERSE ]; then
python3 -m pip install pytest-reverse
fi
if [ "${{ matrix.os }}" = "ubuntu-latest" ]; then
xvfb-run -s '-screen 0 1024x768x24' .ci/test.sh
else
.ci/test.sh
fi
env:
PYTHONOPTIMIZE: ${{ matrix.PYTHONOPTIMIZE }}
REVERSE: ${{ matrix.REVERSE }}

- name: Prepare to upload errors
if: failure()
Expand Down
72 changes: 72 additions & 0 deletions CHANGES.rst
Expand Up @@ -5,6 +5,78 @@ Changelog (Pillow)
8.3.0 (unreleased)
------------------

- Limit TIFF strip size when saving with LibTIFF #5514
[kmilos]

- Allow ICNS save on all operating systems #4526
[baletu, radarhere, newpanjing, hugovk]

- De-zigzag JPEG's DQT when loading; deprecate convert_dict_qtables #4989
[gofr, radarhere]

- Replaced xml.etree.ElementTree #5565
[radarhere]

- Moved CVE image to pillow-depends #5561
[radarhere]

- Added tag data for IFD groups #5554
[radarhere]

- Improved ImagePalette #5552
[radarhere]

- Add DDS saving #5402
[radarhere]

- Improved getxmp() #5455
[radarhere]

- Convert to float for comparison with float in IFDRational __eq__ #5412
[radarhere]

- Allow getexif() to access TIFF tag_v2 data #5416
[radarhere]

- Read FITS image mode and size #5405
[radarhere]

- Merge parallel horizontal edges in ImagingDrawPolygon #5347
[radarhere, hrdrq]

- Use transparency behind first GIF frame and when disposing to background #5557
[radarhere, zewt]

- Avoid unstable nature of qsort in Quant.c #5367
[radarhere]

- Copy palette to new images in ImageOps expand #5551
[radarhere]

- Ensure palette string matches RGB mode #5549
[radarhere]

- Do not modify EXIF of original image instance in exif_transpose() #5547
[radarhere]

- Fixed default numresolution for small JPEG2000 images #5540
[radarhere]

- Added DDS BC5 reading #5501
[radarhere]

- Raise an error if ImageDraw.textbbox is used without a TrueType font #5510
[radarhere]

- Added ICO saving in BMP format #5513
[radarhere]

- Ensure PNG seeks to end of previous chunk at start of load_end #5493
[radarhere]

- Do not allow TIFF to seek to a past frame #5473
[radarhere]

- Avoid race condition when displaying images with eog #5507
[mconst]

Expand Down
Binary file added Tests/images/bc5_snorm.dds
Binary file not shown.
Binary file added Tests/images/bc5_typeless.dds
Binary file not shown.
Binary file added Tests/images/bc5_unorm.dds
Binary file not shown.
Binary file added Tests/images/bc5_unorm.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/bc5s.dds
Binary file not shown.
Binary file added Tests/images/bc5s.png
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file not shown.
Binary file added Tests/images/first_frame_transparency.gif
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/hopper_naxis_zero.fits
Binary file not shown.
Binary file added Tests/images/hopper_resized.gif
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
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/multipage_multiple_frame_loop.tiff
Binary file not shown.
Binary file added Tests/images/multipage_out_of_order.tiff
Binary file not shown.
Binary file added Tests/images/multipage_single_frame_loop.tiff
Binary file not shown.
Binary file added Tests/images/padded_idat.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/transparent_dispose.gif
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
1 change: 1 addition & 0 deletions Tests/oss-fuzz/fuzz_font.py
Expand Up @@ -34,6 +34,7 @@ def main():
fuzzers.enable_decompressionbomb_error()
atheris.Setup(sys.argv, TestOneInput, enable_python_coverage=True)
atheris.Fuzz()
fuzzers.disable_decompressionbomb_error()


if __name__ == "__main__":
Expand Down
1 change: 1 addition & 0 deletions Tests/oss-fuzz/fuzz_pillow.py
Expand Up @@ -34,6 +34,7 @@ def main():
fuzzers.enable_decompressionbomb_error()
atheris.Setup(sys.argv, TestOneInput, enable_python_coverage=True)
atheris.Fuzz()
fuzzers.disable_decompressionbomb_error()


if __name__ == "__main__":
Expand Down
5 changes: 5 additions & 0 deletions Tests/oss-fuzz/fuzzers.py
Expand Up @@ -10,6 +10,11 @@ def enable_decompressionbomb_error():
warnings.simplefilter("error", Image.DecompressionBombWarning)


def disable_decompressionbomb_error():
ImageFile.LOAD_TRUNCATED_IMAGES = False
warnings.resetwarnings()


def fuzz_image(data):
# This will fail on some images in the corpus, as we have many
# invalid images in the test suite.
Expand Down
2 changes: 2 additions & 0 deletions Tests/oss-fuzz/test_fuzzers.py
Expand Up @@ -44,6 +44,8 @@ def test_fuzz_images(path):
):
# Known Image.* exceptions
assert True
finally:
fuzzers.disable_decompressionbomb_error()


@pytest.mark.parametrize(
Expand Down
3 changes: 1 addition & 2 deletions Tests/test_decompression_bomb.py
Expand Up @@ -10,8 +10,7 @@


class TestDecompressionBomb:
@classmethod
def teardown_class(cls):
def teardown_method(self, method):
Image.MAX_IMAGE_PIXELS = ORIGINAL_LIMIT

def test_no_warning_small_file(self):
Expand Down
4 changes: 2 additions & 2 deletions Tests/test_file_apng.py
Expand Up @@ -249,8 +249,8 @@ def test_apng_mode():
assert im.mode == "P"
im.seek(im.n_frames - 1)
im = im.convert("RGBA")
assert im.getpixel((0, 0)) == (0, 255, 0, 255)
assert im.getpixel((64, 32)) == (0, 255, 0, 255)
assert im.getpixel((0, 0)) == (255, 0, 0, 0)
assert im.getpixel((64, 32)) == (255, 0, 0, 0)

with Image.open("Tests/images/apng/mode_palette_1bit_alpha.png") as im:
assert im.mode == "P"
Expand Down
66 changes: 59 additions & 7 deletions Tests/test_file_dds.py
Expand Up @@ -5,11 +5,15 @@

from PIL import DdsImagePlugin, Image

from .helper import assert_image_equal, assert_image_equal_tofile
from .helper import assert_image_equal, assert_image_equal_tofile, hopper

TEST_FILE_DXT1 = "Tests/images/dxt1-rgb-4bbp-noalpha_MipMaps-1.dds"
TEST_FILE_DXT3 = "Tests/images/dxt3-argb-8bbp-explicitalpha_MipMaps-1.dds"
TEST_FILE_DXT5 = "Tests/images/dxt5-argb-8bbp-interpolatedalpha_MipMaps-1.dds"
TEST_FILE_DX10_BC5_TYPELESS = "Tests/images/bc5_typeless.dds"
TEST_FILE_DX10_BC5_UNORM = "Tests/images/bc5_unorm.dds"
TEST_FILE_DX10_BC5_SNORM = "Tests/images/bc5_snorm.dds"
TEST_FILE_BC5S = "Tests/images/bc5s.dds"
TEST_FILE_DX10_BC7 = "Tests/images/bc7-argb-8bpp_MipMaps-1.dds"
TEST_FILE_DX10_BC7_UNORM_SRGB = "Tests/images/DXGI_FORMAT_BC7_UNORM_SRGB.dds"
TEST_FILE_DX10_R8G8B8A8 = "Tests/images/argb-32bpp_MipMaps-1.dds"
Expand All @@ -32,6 +36,19 @@ def test_sanity_dxt1():
assert_image_equal(im, target)


def test_sanity_dxt3():
"""Check DXT3 images can be opened"""

with Image.open(TEST_FILE_DXT3) as im:
im.load()

assert im.format == "DDS"
assert im.mode == "RGBA"
assert im.size == (256, 256)

assert_image_equal_tofile(im, TEST_FILE_DXT3.replace(".dds", ".png"))


def test_sanity_dxt5():
"""Check DXT5 images can be opened"""

Expand All @@ -45,17 +62,28 @@ def test_sanity_dxt5():
assert_image_equal_tofile(im, TEST_FILE_DXT5.replace(".dds", ".png"))


def test_sanity_dxt3():
"""Check DXT3 images can be opened"""

with Image.open(TEST_FILE_DXT3) as im:
@pytest.mark.parametrize(
("image_path", "expected_path"),
(
# hexeditted to be typeless
(TEST_FILE_DX10_BC5_TYPELESS, TEST_FILE_DX10_BC5_UNORM),
(TEST_FILE_DX10_BC5_UNORM, TEST_FILE_DX10_BC5_UNORM),
# hexeditted to use DX10 FourCC
(TEST_FILE_DX10_BC5_SNORM, TEST_FILE_BC5S),
(TEST_FILE_BC5S, TEST_FILE_BC5S),
),
)
def test_dx10_bc5(image_path, expected_path):
"""Check DX10 BC5 images can be opened"""

with Image.open(image_path) as im:
im.load()

assert im.format == "DDS"
assert im.mode == "RGBA"
assert im.mode == "RGB"
assert im.size == (256, 256)

assert_image_equal_tofile(im, TEST_FILE_DXT3.replace(".dds", ".png"))
assert_image_equal_tofile(im, expected_path.replace(".dds", ".png"))


def test_dx10_bc7():
Expand Down Expand Up @@ -214,3 +242,27 @@ def test_unimplemented_pixel_format():
with pytest.raises(NotImplementedError):
with Image.open("Tests/images/unimplemented_pixel_format.dds"):
pass


def test_save_unsupported_mode(tmp_path):
out = str(tmp_path / "temp.dds")
im = hopper("HSV")
with pytest.raises(OSError):
im.save(out)


@pytest.mark.parametrize(
("mode", "test_file"),
[
("RGB", "Tests/images/hopper.png"),
("RGBA", "Tests/images/pil123rgba.png"),
],
)
def test_save(mode, test_file, tmp_path):
out = str(tmp_path / "temp.dds")
with Image.open(test_file) as im:
assert im.mode == mode
im.save(out)

with Image.open(out) as reloaded:
assert_image_equal(im, reloaded)
23 changes: 19 additions & 4 deletions Tests/test_file_fitsstub.py
@@ -1,3 +1,5 @@
from io import BytesIO

import pytest

from PIL import FitsStubImagePlugin, Image
Expand All @@ -11,10 +13,8 @@ def test_open():

# Assert
assert im.format == "FITS"

# Dummy data from the stub
assert im.mode == "F"
assert im.size == (1, 1)
assert im.size == (128, 128)
assert im.mode == "L"


def test_invalid_file():
Expand All @@ -35,6 +35,21 @@ def test_load():
im.load()


def test_truncated_fits():
# No END to headers
image_data = b"SIMPLE = T" + b" " * 50 + b"TRUNCATE"
with pytest.raises(OSError):
FitsStubImagePlugin.FITSStubImageFile(BytesIO(image_data))


def test_naxis_zero():
# This test image has been manually hexedited
# to set the number of data axes to zero
with pytest.raises(ValueError):
with Image.open("Tests/images/hopper_naxis_zero.fits"):
pass


def test_save():
# Arrange
with Image.open(TEST_FILE) as im:
Expand Down
25 changes: 21 additions & 4 deletions Tests/test_file_gif.py
Expand Up @@ -298,6 +298,12 @@ def test_eoferror():
im.seek(n_frames - 1)


def test_first_frame_transparency():
with Image.open("Tests/images/first_frame_transparency.gif") as im:
px = im.load()
assert px[0, 0] == im.info["transparency"]


def test_dispose_none():
with Image.open("Tests/images/dispose_none.gif") as img:
try:
Expand Down Expand Up @@ -331,6 +337,16 @@ def test_dispose_background():
pass


def test_transparent_dispose():
expected_colors = [(2, 1, 2), (0, 1, 0), (2, 1, 2)]
with Image.open("Tests/images/transparent_dispose.gif") as img:
for frame in range(3):
img.seek(frame)
for x in range(3):
color = img.getpixel((x, 0))
assert color == expected_colors[frame][x]


def test_dispose_previous():
with Image.open("Tests/images/dispose_prev.gif") as img:
try:
Expand Down Expand Up @@ -392,14 +408,15 @@ def test_save_dispose(tmp_path):
def test_dispose2_palette(tmp_path):
out = str(tmp_path / "temp.gif")

# 4 backgrounds: White, Grey, Black, Red
# Four colors: white, grey, black, red
circles = [(255, 255, 255), (153, 153, 153), (0, 0, 0), (255, 0, 0)]

im_list = []
for circle in circles:
# Red background
img = Image.new("RGB", (100, 100), (255, 0, 0))

# Red circle in center of each frame
# Circle in center of each frame
d = ImageDraw.Draw(img)
d.ellipse([(40, 40), (60, 60)], fill=circle)

Expand Down Expand Up @@ -749,10 +766,10 @@ def test_rgb_transparency(tmp_path):
# Single frame
im = Image.new("RGB", (1, 1))
im.info["transparency"] = (255, 0, 0)
pytest.warns(UserWarning, im.save, out)
im.save(out)

with Image.open(out) as reloaded:
assert "transparency" not in reloaded.info
assert "transparency" in reloaded.info

# Multiple frames
im = Image.new("RGB", (1, 1))
Expand Down

0 comments on commit 94eb2b3

Please sign in to comment.