Merge branch 'master' into pre-commit-diff

This commit is contained in:
Andrew Murray 2020-06-28 16:01:39 +10:00 committed by GitHub
commit 2d3fd6ea02
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
80 changed files with 775 additions and 466 deletions

View File

@ -5,6 +5,15 @@ Changelog (Pillow)
7.2.0 (unreleased) 7.2.0 (unreleased)
------------------ ------------------
- Deprecated _showxv #4714
[radarhere]
- Deprecate Image.show(command="...") #4646
[nulano, hugovk, radarhere]
- Updated JPEG magic number #4707
[Cykooz, radarhere]
- Change STRIPBYTECOUNTS to LONG if necessary when saving #4626 - Change STRIPBYTECOUNTS to LONG if necessary when saving #4626
[radarhere, hugovk] [radarhere, hugovk]

View File

@ -165,12 +165,6 @@ def assert_tuple_approx_equal(actuals, targets, threshold, msg):
assert value, msg + ": " + repr(actuals) + " != " + repr(targets) assert value, msg + ": " + repr(actuals) + " != " + repr(targets)
def skip_known_bad_test(msg=None):
# Skip if PILLOW_RUN_KNOWN_BAD is not true in the environment.
if not os.environ.get("PILLOW_RUN_KNOWN_BAD", False):
pytest.skip(msg or "Known bad test")
def skip_unless_feature(feature): def skip_unless_feature(feature):
reason = "%s not available" % feature reason = "%s not available" % feature
return pytest.mark.skipif(not features.check(feature), reason=reason) return pytest.mark.skipif(not features.check(feature), reason=reason)

Binary file not shown.

After

Width:  |  Height:  |  Size: 557 B

View File

@ -3,7 +3,14 @@ import re
from io import BytesIO from io import BytesIO
import pytest import pytest
from PIL import ExifTags, Image, ImageFile, JpegImagePlugin, features from PIL import (
ExifTags,
Image,
ImageFile,
JpegImagePlugin,
UnidentifiedImageError,
features,
)
from .helper import ( from .helper import (
assert_image, assert_image,
@ -709,6 +716,24 @@ class TestFileJpeg:
with Image.open("Tests/images/icc-after-SOF.jpg") as im: with Image.open("Tests/images/icc-after-SOF.jpg") as im:
assert im.info["icc_profile"] == b"profile" assert im.info["icc_profile"] == b"profile"
def test_jpeg_magic_number(self):
size = 4097
buffer = BytesIO(b"\xFF" * size) # Many xFF bytes
buffer.max_pos = 0
orig_read = buffer.read
def read(n=-1):
res = orig_read(n)
buffer.max_pos = max(buffer.max_pos, buffer.tell())
return res
buffer.read = read
with pytest.raises(UnidentifiedImageError):
Image.open(buffer)
# Assert the entire file has not been read
assert 0 < buffer.max_pos < size
@pytest.mark.skipif(not is_win32(), reason="Windows only") @pytest.mark.skipif(not is_win32(), reason="Windows only")
@skip_unless_feature("jpg") @skip_unless_feature("jpg")

View File

@ -207,6 +207,7 @@ class TestFileLibTiff(LibTiffTestCase):
del core_items[tag] del core_items[tag]
except KeyError: except KeyError:
pass pass
del core_items[320] # colormap is special, tested below
# Type codes: # Type codes:
# 2: "ascii", # 2: "ascii",
@ -491,6 +492,18 @@ class TestFileLibTiff(LibTiffTestCase):
with Image.open(out) as im2: with Image.open(out) as im2:
assert_image_equal(im, im2) assert_image_equal(im, im2)
def test_palette_save(self, tmp_path):
im = hopper("P")
out = str(tmp_path / "temp.tif")
TiffImagePlugin.WRITE_LIBTIFF = True
im.save(out)
TiffImagePlugin.WRITE_LIBTIFF = False
with Image.open(out) as reloaded:
# colormap/palette tag
assert len(reloaded.tag_v2[320]) == 768
def xtest_bw_compression_w_rgb(self, tmp_path): def xtest_bw_compression_w_rgb(self, tmp_path):
""" This test passes, but when running all tests causes a failure due """ This test passes, but when running all tests causes a failure due
to output on stderr from the error thrown by libtiff. We need to to output on stderr from the error thrown by libtiff. We need to

View File

@ -4,13 +4,7 @@ import subprocess
import pytest import pytest
from PIL import Image from PIL import Image
from .helper import ( from .helper import IMCONVERT, assert_image_equal, hopper, imagemagick_available
IMCONVERT,
assert_image_equal,
hopper,
imagemagick_available,
skip_known_bad_test,
)
_roundtrip = imagemagick_available() _roundtrip = imagemagick_available()
@ -62,13 +56,13 @@ def test_monochrome(tmp_path):
roundtrip(tmp_path, mode) roundtrip(tmp_path, mode)
@pytest.mark.xfail(reason="Palm P image is wrong")
def test_p_mode(tmp_path): def test_p_mode(tmp_path):
# Arrange # Arrange
mode = "P" mode = "P"
# Act / Assert # Act / Assert
helper_save_as_palm(tmp_path, mode) helper_save_as_palm(tmp_path, mode)
skip_known_bad_test("Palm P image is wrong")
roundtrip(tmp_path, mode) roundtrip(tmp_path, mode)

View File

@ -5,7 +5,7 @@ import tempfile
import PIL import PIL
import pytest import pytest
from PIL import Image, ImageDraw, ImagePalette, UnidentifiedImageError from PIL import Image, ImageDraw, ImagePalette, ImageShow, UnidentifiedImageError
from .helper import ( from .helper import (
assert_image_equal, assert_image_equal,
@ -585,6 +585,22 @@ class TestImage:
expected = Image.new(mode, (100, 100), color) expected = Image.new(mode, (100, 100), color)
assert_image_equal(im.convert(mode), expected) assert_image_equal(im.convert(mode), expected)
def test_showxv_deprecation(self):
class TestViewer(ImageShow.Viewer):
def show_image(self, image, **options):
return True
viewer = TestViewer()
ImageShow.register(viewer, -1)
im = Image.new("RGB", (50, 50), "white")
with pytest.warns(DeprecationWarning):
Image._showxv(im)
# Restore original state
ImageShow._viewers.pop(0)
def test_no_resource_warning_on_save(self, tmp_path): def test_no_resource_warning_on_save(self, tmp_path):
# https://github.com/python-pillow/Pillow/issues/835 # https://github.com/python-pillow/Pillow/issues/835
# Arrange # Arrange
@ -664,6 +680,18 @@ class TestImage:
except OSError as e: except OSError as e:
assert str(e) == "buffer overrun when reading image file" assert str(e) == "buffer overrun when reading image file"
def test_show_deprecation(self, monkeypatch):
monkeypatch.setattr(Image, "_show", lambda *args, **kwargs: None)
im = Image.new("RGB", (50, 50), "white")
with pytest.warns(None) as raised:
im.show()
assert not raised
with pytest.warns(DeprecationWarning):
im.show(command="mock")
class MockEncoder: class MockEncoder:
pass pass

View File

@ -1,7 +1,7 @@
import pytest import pytest
from PIL import Image, ImageMath, ImageMode from PIL import Image, ImageMath, ImageMode
from .helper import convert_to_comparable from .helper import convert_to_comparable, skip_unless_feature
codecs = dir(Image.core) codecs = dir(Image.core)
@ -254,9 +254,7 @@ def test_mode_F():
compare_reduce_with_box(im, factor) compare_reduce_with_box(im, factor)
@pytest.mark.skipif( @skip_unless_feature("jpg_2000")
"jpeg2k_decoder" not in codecs, reason="JPEG 2000 support not available"
)
def test_jpeg2k(): def test_jpeg2k():
with Image.open("Tests/images/test-card-lossless.jp2") as im: with Image.open("Tests/images/test-card-lossless.jp2") as im:
assert im.reduce(2).size == (320, 240) assert im.reduce(2).size == (320, 240)

View File

@ -218,7 +218,7 @@ class TestImagingCoreResampleAccuracy:
assert_image_equal(im, ref) assert_image_equal(im, ref)
class CoreResampleConsistencyTest: class TestCoreResampleConsistency:
def make_case(self, mode, fill): def make_case(self, mode, fill):
im = Image.new(mode, (512, 9), fill) im = Image.new(mode, (512, 9), fill)
return im.resize((9, 512), Image.LANCZOS), im.load()[0, 0] return im.resize((9, 512), Image.LANCZOS), im.load()[0, 0]
@ -253,7 +253,7 @@ class CoreResampleConsistencyTest:
self.run_case(self.make_case("F", 1.192093e-07)) self.run_case(self.make_case("F", 1.192093e-07))
class CoreResampleAlphaCorrectTest: class TestCoreResampleAlphaCorrect:
def make_levels_case(self, mode): def make_levels_case(self, mode):
i = Image.new(mode, (256, 16)) i = Image.new(mode, (256, 16))
px = i.load() px = i.load()
@ -274,7 +274,7 @@ class CoreResampleAlphaCorrectTest:
len(used_colors), y len(used_colors), y
) )
@pytest.mark.skip("Current implementation isn't precise enough") @pytest.mark.xfail(reason="Current implementation isn't precise enough")
def test_levels_rgba(self): def test_levels_rgba(self):
case = self.make_levels_case("RGBA") case = self.make_levels_case("RGBA")
self.run_levels_case(case.resize((512, 32), Image.BOX)) self.run_levels_case(case.resize((512, 32), Image.BOX))
@ -283,7 +283,7 @@ class CoreResampleAlphaCorrectTest:
self.run_levels_case(case.resize((512, 32), Image.BICUBIC)) self.run_levels_case(case.resize((512, 32), Image.BICUBIC))
self.run_levels_case(case.resize((512, 32), Image.LANCZOS)) self.run_levels_case(case.resize((512, 32), Image.LANCZOS))
@pytest.mark.skip("Current implementation isn't precise enough") @pytest.mark.xfail(reason="Current implementation isn't precise enough")
def test_levels_la(self): def test_levels_la(self):
case = self.make_levels_case("LA") case = self.make_levels_case("LA")
self.run_levels_case(case.resize((512, 32), Image.BOX)) self.run_levels_case(case.resize((512, 32), Image.BOX))
@ -329,7 +329,7 @@ class CoreResampleAlphaCorrectTest:
self.run_dirty_case(case.resize((20, 20), Image.LANCZOS), (255,)) self.run_dirty_case(case.resize((20, 20), Image.LANCZOS), (255,))
class CoreResamplePassesTest: class TestCoreResamplePasses:
@contextmanager @contextmanager
def count(self, diff): def count(self, diff):
count = Image.core.get_stats()["new_count"] count = Image.core.get_stats()["new_count"]
@ -372,7 +372,7 @@ class CoreResamplePassesTest:
assert_image_similar(with_box, cropped, 0.1) assert_image_similar(with_box, cropped, 0.1)
class CoreResampleCoefficientsTest: class TestCoreResampleCoefficients:
def test_reduce(self): def test_reduce(self):
test_color = 254 test_color = 254
@ -401,7 +401,7 @@ class CoreResampleCoefficientsTest:
assert histogram[0x100 * 3 + 0xFF] == 0x10000 assert histogram[0x100 * 3 + 0xFF] == 0x10000
class CoreResampleBoxTest: class TestCoreResampleBox:
def test_wrong_arguments(self): def test_wrong_arguments(self):
im = hopper() im = hopper()
for resample in ( for resample in (

View File

@ -5,7 +5,7 @@ from PIL import Image, ImageColor, ImageDraw, ImageFont
from .helper import ( from .helper import (
assert_image_equal, assert_image_equal,
assert_image_similar, assert_image_similar_tofile,
hopper, hopper,
skip_unless_feature, skip_unless_feature,
) )
@ -71,7 +71,7 @@ def helper_arc(bbox, start, end):
draw.arc(bbox, start, end) draw.arc(bbox, start, end)
# Assert # Assert
assert_image_similar(im, Image.open("Tests/images/imagedraw_arc.png"), 1) assert_image_similar_tofile(im, "Tests/images/imagedraw_arc.png", 1)
def test_arc1(): def test_arc1():
@ -110,20 +110,19 @@ def test_arc_no_loops():
draw.arc(BBOX1, start=start, end=end) draw.arc(BBOX1, start=start, end=end)
# Assert # Assert
assert_image_similar(im, Image.open("Tests/images/imagedraw_arc_no_loops.png"), 1) assert_image_similar_tofile(im, "Tests/images/imagedraw_arc_no_loops.png", 1)
def test_arc_width(): def test_arc_width():
# Arrange # Arrange
im = Image.new("RGB", (W, H)) im = Image.new("RGB", (W, H))
draw = ImageDraw.Draw(im) draw = ImageDraw.Draw(im)
expected = "Tests/images/imagedraw_arc_width.png"
# Act # Act
draw.arc(BBOX1, 10, 260, width=5) draw.arc(BBOX1, 10, 260, width=5)
# Assert # Assert
assert_image_similar(im, Image.open(expected), 1) assert_image_similar_tofile(im, "Tests/images/imagedraw_arc_width.png", 1)
def test_arc_width_pieslice_large(): def test_arc_width_pieslice_large():
@ -131,26 +130,24 @@ def test_arc_width_pieslice_large():
# Arrange # Arrange
im = Image.new("RGB", (W, H)) im = Image.new("RGB", (W, H))
draw = ImageDraw.Draw(im) draw = ImageDraw.Draw(im)
expected = "Tests/images/imagedraw_arc_width_pieslice.png"
# Act # Act
draw.arc(BBOX1, 10, 260, fill="yellow", width=100) draw.arc(BBOX1, 10, 260, fill="yellow", width=100)
# Assert # Assert
assert_image_similar(im, Image.open(expected), 1) assert_image_similar_tofile(im, "Tests/images/imagedraw_arc_width_pieslice.png", 1)
def test_arc_width_fill(): def test_arc_width_fill():
# Arrange # Arrange
im = Image.new("RGB", (W, H)) im = Image.new("RGB", (W, H))
draw = ImageDraw.Draw(im) draw = ImageDraw.Draw(im)
expected = "Tests/images/imagedraw_arc_width_fill.png"
# Act # Act
draw.arc(BBOX1, 10, 260, fill="yellow", width=5) draw.arc(BBOX1, 10, 260, fill="yellow", width=5)
# Assert # Assert
assert_image_similar(im, Image.open(expected), 1) assert_image_similar_tofile(im, "Tests/images/imagedraw_arc_width_fill.png", 1)
def test_arc_width_non_whole_angle(): def test_arc_width_non_whole_angle():
@ -163,7 +160,7 @@ def test_arc_width_non_whole_angle():
draw.arc(BBOX1, 10, 259.5, width=5) draw.arc(BBOX1, 10, 259.5, width=5)
# Assert # Assert
assert_image_similar(im, Image.open(expected), 1) assert_image_similar_tofile(im, expected, 1)
def test_bitmap(): def test_bitmap():
@ -190,7 +187,7 @@ def helper_chord(mode, bbox, start, end):
draw.chord(bbox, start, end, fill="red", outline="yellow") draw.chord(bbox, start, end, fill="red", outline="yellow")
# Assert # Assert
assert_image_similar(im, Image.open(expected), 1) assert_image_similar_tofile(im, expected, 1)
def test_chord1(): def test_chord1():
@ -209,26 +206,24 @@ def test_chord_width():
# Arrange # Arrange
im = Image.new("RGB", (W, H)) im = Image.new("RGB", (W, H))
draw = ImageDraw.Draw(im) draw = ImageDraw.Draw(im)
expected = "Tests/images/imagedraw_chord_width.png"
# Act # Act
draw.chord(BBOX1, 10, 260, outline="yellow", width=5) draw.chord(BBOX1, 10, 260, outline="yellow", width=5)
# Assert # Assert
assert_image_similar(im, Image.open(expected), 1) assert_image_similar_tofile(im, "Tests/images/imagedraw_chord_width.png", 1)
def test_chord_width_fill(): def test_chord_width_fill():
# Arrange # Arrange
im = Image.new("RGB", (W, H)) im = Image.new("RGB", (W, H))
draw = ImageDraw.Draw(im) draw = ImageDraw.Draw(im)
expected = "Tests/images/imagedraw_chord_width_fill.png"
# Act # Act
draw.chord(BBOX1, 10, 260, fill="red", outline="yellow", width=5) draw.chord(BBOX1, 10, 260, fill="red", outline="yellow", width=5)
# Assert # Assert
assert_image_similar(im, Image.open(expected), 1) assert_image_similar_tofile(im, "Tests/images/imagedraw_chord_width_fill.png", 1)
def test_chord_zero_width(): def test_chord_zero_width():
@ -254,7 +249,7 @@ def helper_ellipse(mode, bbox):
draw.ellipse(bbox, fill="green", outline="blue") draw.ellipse(bbox, fill="green", outline="blue")
# Assert # Assert
assert_image_similar(im, Image.open(expected), 1) assert_image_similar_tofile(im, expected, 1)
def test_ellipse1(): def test_ellipse1():
@ -276,8 +271,8 @@ def test_ellipse_translucent():
draw.ellipse(BBOX1, fill=(0, 255, 0, 127)) draw.ellipse(BBOX1, fill=(0, 255, 0, 127))
# Assert # Assert
expected = Image.open("Tests/images/imagedraw_ellipse_translucent.png") expected = "Tests/images/imagedraw_ellipse_translucent.png"
assert_image_similar(im, expected, 1) assert_image_similar_tofile(im, expected, 1)
def test_ellipse_edge(): def test_ellipse_edge():
@ -289,7 +284,7 @@ def test_ellipse_edge():
draw.ellipse(((0, 0), (W - 1, H)), fill="white") draw.ellipse(((0, 0), (W - 1, H)), fill="white")
# Assert # Assert
assert_image_similar(im, Image.open("Tests/images/imagedraw_ellipse_edge.png"), 1) assert_image_similar_tofile(im, "Tests/images/imagedraw_ellipse_edge.png", 1)
def test_ellipse_symmetric(): def test_ellipse_symmetric():
@ -304,39 +299,36 @@ def test_ellipse_width():
# Arrange # Arrange
im = Image.new("RGB", (W, H)) im = Image.new("RGB", (W, H))
draw = ImageDraw.Draw(im) draw = ImageDraw.Draw(im)
expected = "Tests/images/imagedraw_ellipse_width.png"
# Act # Act
draw.ellipse(BBOX1, outline="blue", width=5) draw.ellipse(BBOX1, outline="blue", width=5)
# Assert # Assert
assert_image_similar(im, Image.open(expected), 1) assert_image_similar_tofile(im, "Tests/images/imagedraw_ellipse_width.png", 1)
def test_ellipse_width_large(): def test_ellipse_width_large():
# Arrange # Arrange
im = Image.new("RGB", (500, 500)) im = Image.new("RGB", (500, 500))
draw = ImageDraw.Draw(im) draw = ImageDraw.Draw(im)
expected = "Tests/images/imagedraw_ellipse_width_large.png"
# Act # Act
draw.ellipse((25, 25, 475, 475), outline="blue", width=75) draw.ellipse((25, 25, 475, 475), outline="blue", width=75)
# Assert # Assert
assert_image_similar(im, Image.open(expected), 1) assert_image_similar_tofile(im, "Tests/images/imagedraw_ellipse_width_large.png", 1)
def test_ellipse_width_fill(): def test_ellipse_width_fill():
# Arrange # Arrange
im = Image.new("RGB", (W, H)) im = Image.new("RGB", (W, H))
draw = ImageDraw.Draw(im) draw = ImageDraw.Draw(im)
expected = "Tests/images/imagedraw_ellipse_width_fill.png"
# Act # Act
draw.ellipse(BBOX1, fill="green", outline="blue", width=5) draw.ellipse(BBOX1, fill="green", outline="blue", width=5)
# Assert # Assert
assert_image_similar(im, Image.open(expected), 1) assert_image_similar_tofile(im, "Tests/images/imagedraw_ellipse_width_fill.png", 1)
def test_ellipse_zero_width(): def test_ellipse_zero_width():
@ -423,7 +415,7 @@ def helper_pieslice(bbox, start, end):
draw.pieslice(bbox, start, end, fill="white", outline="blue") draw.pieslice(bbox, start, end, fill="white", outline="blue")
# Assert # Assert
assert_image_similar(im, Image.open("Tests/images/imagedraw_pieslice.png"), 1) assert_image_similar_tofile(im, "Tests/images/imagedraw_pieslice.png", 1)
def test_pieslice1(): def test_pieslice1():
@ -440,13 +432,12 @@ def test_pieslice_width():
# Arrange # Arrange
im = Image.new("RGB", (W, H)) im = Image.new("RGB", (W, H))
draw = ImageDraw.Draw(im) draw = ImageDraw.Draw(im)
expected = "Tests/images/imagedraw_pieslice_width.png"
# Act # Act
draw.pieslice(BBOX1, 10, 260, outline="blue", width=5) draw.pieslice(BBOX1, 10, 260, outline="blue", width=5)
# Assert # Assert
assert_image_similar(im, Image.open(expected), 1) assert_image_similar_tofile(im, "Tests/images/imagedraw_pieslice_width.png", 1)
def test_pieslice_width_fill(): def test_pieslice_width_fill():
@ -459,7 +450,7 @@ def test_pieslice_width_fill():
draw.pieslice(BBOX1, 10, 260, fill="white", outline="blue", width=5) draw.pieslice(BBOX1, 10, 260, fill="white", outline="blue", width=5)
# Assert # Assert
assert_image_similar(im, Image.open(expected), 1) assert_image_similar_tofile(im, expected, 1)
def test_pieslice_zero_width(): def test_pieslice_zero_width():
@ -571,13 +562,12 @@ def test_big_rectangle():
im = Image.new("RGB", (W, H)) im = Image.new("RGB", (W, H))
bbox = [(-1, -1), (W + 1, H + 1)] bbox = [(-1, -1), (W + 1, H + 1)]
draw = ImageDraw.Draw(im) draw = ImageDraw.Draw(im)
expected = "Tests/images/imagedraw_big_rectangle.png"
# Act # Act
draw.rectangle(bbox, fill="orange") draw.rectangle(bbox, fill="orange")
# Assert # Assert
assert_image_similar(im, Image.open(expected), 1) assert_image_similar_tofile(im, "Tests/images/imagedraw_big_rectangle.png", 1)
def test_rectangle_width(): def test_rectangle_width():
@ -878,13 +868,25 @@ def test_wide_line_dot():
# Arrange # Arrange
im = Image.new("RGB", (W, H)) im = Image.new("RGB", (W, H))
draw = ImageDraw.Draw(im) draw = ImageDraw.Draw(im)
expected = "Tests/images/imagedraw_wide_line_dot.png"
# Act # Act
draw.line([(50, 50), (50, 50)], width=3) draw.line([(50, 50), (50, 50)], width=3)
# Assert # Assert
assert_image_similar(im, Image.open(expected), 1) assert_image_similar_tofile(im, "Tests/images/imagedraw_wide_line_dot.png", 1)
def test_wide_line_larger_than_int():
# Arrange
im = Image.new("RGB", (W, H))
draw = ImageDraw.Draw(im)
expected = "Tests/images/imagedraw_wide_line_larger_than_int.png"
# Act
draw.line([(0, 0), (32768, 32768)], width=3)
# Assert
assert_image_similar_tofile(im, expected, 1)
@pytest.mark.parametrize( @pytest.mark.parametrize(
@ -971,13 +973,12 @@ def test_wide_line_dot():
def test_line_joint(xy): def test_line_joint(xy):
im = Image.new("RGB", (500, 325)) im = Image.new("RGB", (500, 325))
draw = ImageDraw.Draw(im) draw = ImageDraw.Draw(im)
expected = "Tests/images/imagedraw_line_joint_curve.png"
# Act # Act
draw.line(xy, GRAY, 50, "curve") draw.line(xy, GRAY, 50, "curve")
# Assert # Assert
assert_image_similar(im, Image.open(expected), 3) assert_image_similar_tofile(im, "Tests/images/imagedraw_line_joint_curve.png", 3)
def test_textsize_empty_string(): def test_textsize_empty_string():
@ -1018,8 +1019,8 @@ def test_stroke():
draw.text((10, 10), "A", "#f00", font, stroke_width=2, stroke_fill=stroke_fill) draw.text((10, 10), "A", "#f00", font, stroke_width=2, stroke_fill=stroke_fill)
# Assert # Assert
assert_image_similar( assert_image_similar_tofile(
im, Image.open("Tests/images/imagedraw_stroke_" + suffix + ".png"), 3.1 im, "Tests/images/imagedraw_stroke_" + suffix + ".png", 3.1
) )
@ -1034,9 +1035,7 @@ def test_stroke_descender():
draw.text((10, 0), "y", "#f00", font, stroke_width=2, stroke_fill="#0f0") draw.text((10, 0), "y", "#f00", font, stroke_width=2, stroke_fill="#0f0")
# Assert # Assert
assert_image_similar( assert_image_similar_tofile(im, "Tests/images/imagedraw_stroke_descender.png", 6.76)
im, Image.open("Tests/images/imagedraw_stroke_descender.png"), 6.76
)
@skip_unless_feature("freetype2") @skip_unless_feature("freetype2")
@ -1052,9 +1051,7 @@ def test_stroke_multiline():
) )
# Assert # Assert
assert_image_similar( assert_image_similar_tofile(im, "Tests/images/imagedraw_stroke_multiline.png", 3.3)
im, Image.open("Tests/images/imagedraw_stroke_multiline.png"), 3.3
)
def test_same_color_outline(): def test_same_color_outline():
@ -1093,4 +1090,4 @@ def test_same_color_outline():
expected = "Tests/images/imagedraw_outline_{}_{}.png".format( expected = "Tests/images/imagedraw_outline_{}_{}.png".format(
operation, mode operation, mode
) )
assert_image_similar(im, Image.open(expected), 1) assert_image_similar_tofile(im, expected, 1)

View File

@ -455,7 +455,7 @@ class TestImageFont:
with pytest.raises(UnicodeEncodeError): with pytest.raises(UnicodeEncodeError):
font.getsize("") font.getsize("")
@pytest.mark.skipif(is_pypy(), reason="failing on PyPy") @pytest.mark.xfail(is_pypy(), reason="failing on PyPy with Raqm")
def test_unicode_extended(self): def test_unicode_extended(self):
# issue #3777 # issue #3777
text = "A\u278A\U0001F12B" text = "A\u278A\U0001F12B"

View File

@ -5,7 +5,7 @@ import sys
import pytest import pytest
from PIL import Image, ImageGrab from PIL import Image, ImageGrab
from .helper import assert_image, assert_image_equal_tofile from .helper import assert_image, assert_image_equal_tofile, skip_unless_feature
class TestImageGrab: class TestImageGrab:
@ -23,7 +23,7 @@ class TestImageGrab:
im = ImageGrab.grab(bbox=(10, 20, 50, 80)) im = ImageGrab.grab(bbox=(10, 20, 50, 80))
assert_image(im, im.mode, (40, 60)) assert_image(im, im.mode, (40, 60))
@pytest.mark.skipif(not Image.core.HAVE_XCB, reason="requires XCB") @skip_unless_feature("xcb")
def test_grab_x11(self): def test_grab_x11(self):
try: try:
if sys.platform not in ("win32", "darwin"): if sys.platform not in ("win32", "darwin"):
@ -46,7 +46,7 @@ class TestImageGrab:
ImageGrab.grab(xdisplay="") ImageGrab.grab(xdisplay="")
assert str(e.value).startswith("Pillow was built without XCB support") assert str(e.value).startswith("Pillow was built without XCB support")
@pytest.mark.skipif(not Image.core.HAVE_XCB, reason="requires XCB") @skip_unless_feature("xcb")
def test_grab_invalid_xdisplay(self): def test_grab_invalid_xdisplay(self):
with pytest.raises(OSError) as e: with pytest.raises(OSError) as e:
ImageGrab.grab(xdisplay="error.test:0.0") ImageGrab.grab(xdisplay="error.test:0.0")

View File

@ -4,6 +4,14 @@ PIL Package (autodoc of remaining modules)
Reference for modules whose documentation has not yet been ported or written Reference for modules whose documentation has not yet been ported or written
can be found here. can be found here.
:mod:`PIL` Module
-----------------
.. py:module:: PIL
.. autoexception:: UnidentifiedImageError
:show-inheritance:
:mod:`BdfFontFile` Module :mod:`BdfFontFile` Module
------------------------- -------------------------
@ -52,21 +60,12 @@ can be found here.
:undoc-members: :undoc-members:
:show-inheritance: :show-inheritance:
.. intentionally skipped documenting this because it's not documented anywhere
:mod:`ImageDraw2` Module :mod:`ImageDraw2` Module
------------------------ ------------------------
.. automodule:: PIL.ImageDraw2 .. automodule:: PIL.ImageDraw2
:members: :members:
:undoc-members: :member-order: bysource
:show-inheritance:
:mod:`ImageShow` Module
-----------------------
.. automodule:: PIL.ImageShow
:members:
:undoc-members: :undoc-members:
:show-inheritance: :show-inheritance:
@ -78,14 +77,6 @@ can be found here.
:undoc-members: :undoc-members:
:show-inheritance: :show-inheritance:
:mod:`JpegPresets` Module
-------------------------
.. automodule:: PIL.JpegPresets
:members:
:undoc-members:
:show-inheritance:
:mod:`PaletteFile` Module :mod:`PaletteFile` Module
------------------------- -------------------------
@ -140,12 +131,3 @@ can be found here.
:members: :members:
:undoc-members: :undoc-members:
:show-inheritance: :show-inheritance:
:mod:`_binary` Module
---------------------
.. automodule:: PIL._binary
:members:
:undoc-members:
:show-inheritance:

View File

@ -22,7 +22,7 @@ import sphinx_rtd_theme
# -- General configuration ------------------------------------------------ # -- General configuration ------------------------------------------------
# If your documentation needs a minimal Sphinx version, state it here. # If your documentation needs a minimal Sphinx version, state it here.
# needs_sphinx = '1.0' needs_sphinx = "2.4"
# Add any Sphinx extension module names here, as strings. They can be # Add any Sphinx extension module names here, as strings. They can be
# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom

View File

@ -12,6 +12,23 @@ Deprecated features
Below are features which are considered deprecated. Where appropriate, Below are features which are considered deprecated. Where appropriate,
a ``DeprecationWarning`` is issued. a ``DeprecationWarning`` is issued.
Image.show command parameter
~~~~~~~~~~~~~~~~~~~~~~~~~~~~
.. deprecated:: 7.2.0
The ``command`` parameter was deprecated and will be removed in a future release.
Use a subclass of ``ImageShow.Viewer`` instead.
Image._showxv
~~~~~~~~~~~~~
.. deprecated:: 7.2.0
``Image._showxv`` has been deprecated. Use :py:meth:`~PIL.Image.Image.show`
instead. If custom behaviour is required, use :py:meth:`~PIL.ImageShow.register` to add
a custom :py:class:`~PIL.ImageShow.Viewer` class.
ImageFile.raise_ioerror ImageFile.raise_ioerror
~~~~~~~~~~~~~~~~~~~~~~~ ~~~~~~~~~~~~~~~~~~~~~~~

View File

@ -249,8 +249,8 @@ class DXT1Decoder(ImageFile.PyDecoder):
def decode(self, buffer): def decode(self, buffer):
try: try:
self.set_as_raw(_dxt1(self.fd, self.state.xsize, self.state.ysize)) self.set_as_raw(_dxt1(self.fd, self.state.xsize, self.state.ysize))
except struct.error: except struct.error as e:
raise OSError("Truncated DDS file") raise OSError("Truncated DDS file") from e
return 0, 0 return 0, 0
@ -260,8 +260,8 @@ class DXT5Decoder(ImageFile.PyDecoder):
def decode(self, buffer): def decode(self, buffer):
try: try:
self.set_as_raw(_dxt5(self.fd, self.state.xsize, self.state.ysize)) self.set_as_raw(_dxt5(self.fd, self.state.xsize, self.state.ysize))
except struct.error: except struct.error as e:
raise OSError("Truncated DDS file") raise OSError("Truncated DDS file") from e
return 0, 0 return 0, 0

View File

@ -8,7 +8,7 @@ Over 30 different file formats can be identified and read by the library.
Write support is less extensive, but most common interchange and presentation Write support is less extensive, but most common interchange and presentation
formats are supported. formats are supported.
The :py:meth:`~PIL.Image.Image.open` function identifies files from their The :py:meth:`~PIL.Image.open` function identifies files from their
contents, not their names, but the :py:meth:`~PIL.Image.Image.save` method contents, not their names, but the :py:meth:`~PIL.Image.Image.save` method
looks at the name to determine which format to use, unless the format is given looks at the name to determine which format to use, unless the format is given
explicitly. explicitly.
@ -25,7 +25,7 @@ Pillow reads and writes Windows and OS/2 BMP files containing ``1``, ``L``, ``P`
or ``RGB`` data. 16-colour images are read as ``P`` images. Run-length encoding or ``RGB`` data. 16-colour images are read as ``P`` images. Run-length encoding
is not supported. is not supported.
The :py:meth:`~PIL.Image.Image.open` method sets the following The :py:meth:`~PIL.Image.open` method sets the following
:py:attr:`~PIL.Image.Image.info` properties: :py:attr:`~PIL.Image.Image.info` properties:
**compression** **compression**
@ -74,7 +74,7 @@ are used or GIF89a is already in use.
Note that GIF files are always read as grayscale (``L``) Note that GIF files are always read as grayscale (``L``)
or palette mode (``P``) images. or palette mode (``P``) images.
The :py:meth:`~PIL.Image.Image.open` method sets the following The :py:meth:`~PIL.Image.open` method sets the following
:py:attr:`~PIL.Image.Image.info` properties: :py:attr:`~PIL.Image.Image.info` properties:
**background** **background**
@ -203,7 +203,7 @@ ICNS
Pillow reads and (macOS only) writes macOS ``.icns`` files. By default, the Pillow reads and (macOS only) writes macOS ``.icns`` files. By default, the
largest available icon is read, though you can override this by setting the largest available icon is read, though you can override this by setting the
:py:attr:`~PIL.Image.Image.size` property before calling :py:attr:`~PIL.Image.Image.size` property before calling
:py:meth:`~PIL.Image.Image.load`. The :py:meth:`~PIL.Image.Image.open` method :py:meth:`~PIL.Image.Image.load`. The :py:meth:`~PIL.Image.open` method
sets the following :py:attr:`~PIL.Image.Image.info` property: sets the following :py:attr:`~PIL.Image.Image.info` property:
**sizes** **sizes**
@ -257,7 +257,7 @@ Using the :py:meth:`~PIL.Image.Image.draft` method, you can speed things up by
converting ``RGB`` images to ``L``, and resize images to 1/2, 1/4 or 1/8 of converting ``RGB`` images to ``L``, and resize images to 1/2, 1/4 or 1/8 of
their original size while loading them. their original size while loading them.
The :py:meth:`~PIL.Image.Image.open` method may set the following The :py:meth:`~PIL.Image.open` method may set the following
:py:attr:`~PIL.Image.Image.info` properties if available: :py:attr:`~PIL.Image.Image.info` properties if available:
**jfif** **jfif**
@ -697,7 +697,7 @@ Pillow also reads SPIDER stack files containing sequences of SPIDER images. The
:py:meth:`~PIL.Image.Image.seek` and :py:meth:`~PIL.Image.Image.tell` methods are supported, and :py:meth:`~PIL.Image.Image.seek` and :py:meth:`~PIL.Image.Image.tell` methods are supported, and
random access is allowed. random access is allowed.
The :py:meth:`~PIL.Image.Image.open` method sets the following attributes: The :py:meth:`~PIL.Image.open` method sets the following attributes:
**format** **format**
Set to ``SPIDER`` Set to ``SPIDER``
@ -750,7 +750,7 @@ uncompressed files.
support for reading Packbits, LZW and JPEG compressed TIFFs support for reading Packbits, LZW and JPEG compressed TIFFs
without using libtiff. without using libtiff.
The :py:meth:`~PIL.Image.Image.open` method sets the following The :py:meth:`~PIL.Image.open` method sets the following
:py:attr:`~PIL.Image.Image.info` properties: :py:attr:`~PIL.Image.Image.info` properties:
**compression** **compression**
@ -1021,7 +1021,7 @@ FLI, FLC
Pillow reads Autodesk FLI and FLC animations. Pillow reads Autodesk FLI and FLC animations.
The :py:meth:`~PIL.Image.Image.open` method sets the following The :py:meth:`~PIL.Image.open` method sets the following
:py:attr:`~PIL.Image.Image.info` properties: :py:attr:`~PIL.Image.Image.info` properties:
**duration** **duration**
@ -1054,7 +1054,7 @@ GBR
The GBR decoder reads GIMP brush files, version 1 and 2. The GBR decoder reads GIMP brush files, version 1 and 2.
The :py:meth:`~PIL.Image.Image.open` method sets the following The :py:meth:`~PIL.Image.open` method sets the following
:py:attr:`~PIL.Image.Image.info` properties: :py:attr:`~PIL.Image.Image.info` properties:
**comment** **comment**
@ -1069,7 +1069,7 @@ GD
Pillow reads uncompressed GD2 files. Note that you must use Pillow reads uncompressed GD2 files. Note that you must use
:py:func:`PIL.GdImageFile.open` to read such a file. :py:func:`PIL.GdImageFile.open` to read such a file.
The :py:meth:`~PIL.Image.Image.open` method sets the following The :py:meth:`~PIL.Image.open` method sets the following
:py:attr:`~PIL.Image.Image.info` properties: :py:attr:`~PIL.Image.Image.info` properties:
**transparency** **transparency**
@ -1185,7 +1185,7 @@ XPM
Pillow reads X pixmap files (mode ``P``) with 256 colors or less. Pillow reads X pixmap files (mode ``P``) with 256 colors or less.
The :py:meth:`~PIL.Image.Image.open` method sets the following The :py:meth:`~PIL.Image.open` method sets the following
:py:attr:`~PIL.Image.Image.info` properties: :py:attr:`~PIL.Image.Image.info` properties:
**transparency** **transparency**

View File

@ -7,7 +7,8 @@
The :py:mod:`ExifTags` module exposes two dictionaries which The :py:mod:`ExifTags` module exposes two dictionaries which
provide constants and clear-text names for various well-known EXIF tags. provide constants and clear-text names for various well-known EXIF tags.
.. py:class:: PIL.ExifTags.TAGS .. py:data:: TAGS
:type: dict
The TAG dictionary maps 16-bit integer EXIF tag enumerations to The TAG dictionary maps 16-bit integer EXIF tag enumerations to
descriptive string names. For instance: descriptive string names. For instance:
@ -16,7 +17,8 @@ provide constants and clear-text names for various well-known EXIF tags.
>>> TAGS[0x010e] >>> TAGS[0x010e]
'ImageDescription' 'ImageDescription'
.. py:class:: PIL.ExifTags.GPSTAGS .. py:data:: GPSTAGS
:type: dict
The GPSTAGS dictionary maps 8-bit integer EXIF gps enumerations to The GPSTAGS dictionary maps 8-bit integer EXIF gps enumerations to
descriptive string names. For instance: descriptive string names. For instance:

View File

@ -76,9 +76,16 @@ Constructing images
.. autofunction:: new .. autofunction:: new
.. autofunction:: fromarray .. autofunction:: fromarray
.. autofunction:: frombytes .. autofunction:: frombytes
.. autofunction:: fromstring
.. autofunction:: frombuffer .. autofunction:: frombuffer
Generating images
^^^^^^^^^^^^^^^^^
.. autofunction:: effect_mandelbrot
.. autofunction:: effect_noise
.. autofunction:: linear_gradient
.. autofunction:: radial_gradient
Registering plugins Registering plugins
^^^^^^^^^^^^^^^^^^^ ^^^^^^^^^^^^^^^^^^^
@ -88,12 +95,14 @@ Registering plugins
ignore them. ignore them.
.. autofunction:: register_open .. autofunction:: register_open
.. autofunction:: register_decoder
.. autofunction:: register_mime .. autofunction:: register_mime
.. autofunction:: register_save .. autofunction:: register_save
.. autofunction:: register_encoder .. autofunction:: register_save_all
.. autofunction:: register_extension .. autofunction:: register_extension
.. autofunction:: register_extensions
.. autofunction:: registered_extensions
.. autofunction:: register_decoder
.. autofunction:: register_encoder
The Image Class The Image Class
--------------- ---------------
@ -140,6 +149,8 @@ This crops the input image with the provided coordinates:
.. automethod:: PIL.Image.Image.draft .. automethod:: PIL.Image.Image.draft
.. automethod:: PIL.Image.Image.effect_spread
.. automethod:: PIL.Image.Image.entropy
.. automethod:: PIL.Image.Image.filter .. automethod:: PIL.Image.Image.filter
This blurs the input image using a filter from the ``ImageFilter`` module: This blurs the input image using a filter from the ``ImageFilter`` module:
@ -176,12 +187,14 @@ This helps to get the bounding box coordinates of the input image:
print(im.getbbox()) print(im.getbbox())
# Returns four coordinates in the format (left, upper, right, lower) # Returns four coordinates in the format (left, upper, right, lower)
.. automethod:: PIL.Image.Image.getchannel
.. automethod:: PIL.Image.Image.getcolors .. automethod:: PIL.Image.Image.getcolors
.. automethod:: PIL.Image.Image.getdata .. automethod:: PIL.Image.Image.getdata
.. automethod:: PIL.Image.Image.getextrema
.. automethod:: PIL.Image.Image.getexif .. automethod:: PIL.Image.Image.getexif
.. automethod:: PIL.Image.Image.getextrema
.. automethod:: PIL.Image.Image.getpalette .. automethod:: PIL.Image.Image.getpalette
.. automethod:: PIL.Image.Image.getpixel .. automethod:: PIL.Image.Image.getpixel
.. automethod:: PIL.Image.Image.getprojection
.. automethod:: PIL.Image.Image.histogram .. automethod:: PIL.Image.Image.histogram
.. automethod:: PIL.Image.Image.offset .. automethod:: PIL.Image.Image.offset
.. automethod:: PIL.Image.Image.paste .. automethod:: PIL.Image.Image.paste
@ -191,6 +204,8 @@ This helps to get the bounding box coordinates of the input image:
.. automethod:: PIL.Image.Image.putpalette .. automethod:: PIL.Image.Image.putpalette
.. automethod:: PIL.Image.Image.putpixel .. automethod:: PIL.Image.Image.putpixel
.. automethod:: PIL.Image.Image.quantize .. automethod:: PIL.Image.Image.quantize
.. automethod:: PIL.Image.Image.reduce
.. automethod:: PIL.Image.Image.remap_palette
.. automethod:: PIL.Image.Image.resize .. automethod:: PIL.Image.Image.resize
This resizes the given image from ``(width, height)`` to ``(width/2, height/2)``: This resizes the given image from ``(width, height)`` to ``(width/2, height/2)``:
@ -205,7 +220,6 @@ This resizes the given image from ``(width, height)`` to ``(width/2, height/2)``
(width, height) = (im.width // 2, im.height // 2) (width, height) = (im.width // 2, im.height // 2)
im_resized = im.resize((width, height)) im_resized = im.resize((width, height))
.. automethod:: PIL.Image.Image.remap_palette
.. automethod:: PIL.Image.Image.rotate .. automethod:: PIL.Image.Image.rotate
This rotates the input image by ``theta`` degrees counter clockwise: This rotates the input image by ``theta`` degrees counter clockwise:
@ -225,7 +239,6 @@ This rotates the input image by ``theta`` degrees counter clockwise:
.. automethod:: PIL.Image.Image.seek .. automethod:: PIL.Image.Image.seek
.. automethod:: PIL.Image.Image.show .. automethod:: PIL.Image.Image.show
.. automethod:: PIL.Image.Image.split .. automethod:: PIL.Image.Image.split
.. automethod:: PIL.Image.Image.getchannel
.. automethod:: PIL.Image.Image.tell .. automethod:: PIL.Image.Image.tell
.. automethod:: PIL.Image.Image.thumbnail .. automethod:: PIL.Image.Image.thumbnail
.. automethod:: PIL.Image.Image.tobitmap .. automethod:: PIL.Image.Image.tobitmap

View File

@ -124,7 +124,7 @@ Example: Draw Multiline Text
Functions Functions
--------- ---------
.. py:class:: PIL.ImageDraw.Draw(im, mode=None) .. py:method:: Draw(im, mode=None)
Creates an object that can be used to draw in the given image. Creates an object that can be used to draw in the given image.
@ -140,13 +140,13 @@ Functions
Methods Methods
------- -------
.. py:method:: PIL.ImageDraw.ImageDraw.getfont() .. py:method:: ImageDraw.getfont()
Get the current default font. Get the current default font.
:returns: An image font. :returns: An image font.
.. py:method:: PIL.ImageDraw.ImageDraw.arc(xy, start, end, fill=None, width=0) .. py:method:: ImageDraw.arc(xy, start, end, fill=None, width=0)
Draws an arc (a portion of a circle outline) between the start and end Draws an arc (a portion of a circle outline) between the start and end
angles, inside the given bounding box. angles, inside the given bounding box.
@ -162,7 +162,7 @@ Methods
.. versionadded:: 5.3.0 .. versionadded:: 5.3.0
.. py:method:: PIL.ImageDraw.ImageDraw.bitmap(xy, bitmap, fill=None) .. py:method:: ImageDraw.bitmap(xy, bitmap, fill=None)
Draws a bitmap (mask) at the given position, using the current fill color Draws a bitmap (mask) at the given position, using the current fill color
for the non-zero portions. The bitmap should be a valid transparency mask for the non-zero portions. The bitmap should be a valid transparency mask
@ -173,7 +173,7 @@ Methods
To paste pixel data into an image, use the To paste pixel data into an image, use the
:py:meth:`~PIL.Image.Image.paste` method on the image itself. :py:meth:`~PIL.Image.Image.paste` method on the image itself.
.. py:method:: PIL.ImageDraw.ImageDraw.chord(xy, start, end, fill=None, outline=None, width=1) .. py:method:: ImageDraw.chord(xy, start, end, fill=None, outline=None, width=1)
Same as :py:meth:`~PIL.ImageDraw.ImageDraw.arc`, but connects the end points Same as :py:meth:`~PIL.ImageDraw.ImageDraw.arc`, but connects the end points
with a straight line. with a straight line.
@ -187,7 +187,7 @@ Methods
.. versionadded:: 5.3.0 .. versionadded:: 5.3.0
.. py:method:: PIL.ImageDraw.ImageDraw.ellipse(xy, fill=None, outline=None, width=1) .. py:method:: ImageDraw.ellipse(xy, fill=None, outline=None, width=1)
Draws an ellipse inside the given bounding box. Draws an ellipse inside the given bounding box.
@ -200,9 +200,9 @@ Methods
.. versionadded:: 5.3.0 .. versionadded:: 5.3.0
.. py:method:: PIL.ImageDraw.ImageDraw.line(xy, fill=None, width=0, joint=None) .. py:method:: ImageDraw.line(xy, fill=None, width=0, joint=None)
Draws a line between the coordinates in the **xy** list. Draws a line between the coordinates in the ``xy`` list.
:param xy: Sequence of either 2-tuples like ``[(x, y), (x, y), ...]`` or :param xy: Sequence of either 2-tuples like ``[(x, y), (x, y), ...]`` or
numeric values like ``[x, y, x, y, ...]``. numeric values like ``[x, y, x, y, ...]``.
@ -216,7 +216,7 @@ Methods
.. versionadded:: 5.3.0 .. versionadded:: 5.3.0
.. py:method:: PIL.ImageDraw.ImageDraw.pieslice(xy, start, end, fill=None, outline=None, width=1) .. py:method:: ImageDraw.pieslice(xy, start, end, fill=None, outline=None, width=1)
Same as arc, but also draws straight lines between the end points and the Same as arc, but also draws straight lines between the end points and the
center of the bounding box. center of the bounding box.
@ -233,7 +233,7 @@ Methods
.. versionadded:: 5.3.0 .. versionadded:: 5.3.0
.. py:method:: PIL.ImageDraw.ImageDraw.point(xy, fill=None) .. py:method:: ImageDraw.point(xy, fill=None)
Draws points (individual pixels) at the given coordinates. Draws points (individual pixels) at the given coordinates.
@ -241,7 +241,7 @@ Methods
numeric values like ``[x, y, x, y, ...]``. numeric values like ``[x, y, x, y, ...]``.
:param fill: Color to use for the point. :param fill: Color to use for the point.
.. py:method:: PIL.ImageDraw.ImageDraw.polygon(xy, fill=None, outline=None) .. py:method:: ImageDraw.polygon(xy, fill=None, outline=None)
Draws a polygon. Draws a polygon.
@ -254,7 +254,7 @@ Methods
:param outline: Color to use for the outline. :param outline: Color to use for the outline.
:param fill: Color to use for the fill. :param fill: Color to use for the fill.
.. py:method:: PIL.ImageDraw.ImageDraw.rectangle(xy, fill=None, outline=None, width=1) .. py:method:: ImageDraw.rectangle(xy, fill=None, outline=None, width=1)
Draws a rectangle. Draws a rectangle.
@ -267,13 +267,13 @@ Methods
.. versionadded:: 5.3.0 .. versionadded:: 5.3.0
.. py:method:: PIL.ImageDraw.ImageDraw.shape(shape, fill=None, outline=None) .. py:method:: ImageDraw.shape(shape, fill=None, outline=None)
.. warning:: This method is experimental. .. warning:: This method is experimental.
Draw a shape. Draw a shape.
.. py:method:: PIL.ImageDraw.ImageDraw.text(xy, text, fill=None, font=None, anchor=None, spacing=4, align="left", direction=None, features=None, language=None, stroke_width=0, stroke_fill=None) .. py:method:: ImageDraw.text(xy, text, fill=None, font=None, anchor=None, spacing=4, 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.
@ -325,7 +325,7 @@ Methods
.. versionadded:: 6.2.0 .. versionadded:: 6.2.0
.. py:method:: PIL.ImageDraw.ImageDraw.multiline_text(xy, text, fill=None, font=None, anchor=None, spacing=4, align="left", direction=None, features=None, language=None) .. py:method:: ImageDraw.multiline_text(xy, text, fill=None, font=None, anchor=None, spacing=4, align="left", direction=None, features=None, language=None)
Draws the string at the given position. Draws the string at the given position.
@ -362,7 +362,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, stroke_width=0) .. py:method:: 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.
@ -401,7 +401,7 @@ Methods
.. versionadded:: 6.2.0 .. 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) .. py:method:: 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.
@ -439,7 +439,7 @@ Methods
.. versionadded:: 6.2.0 .. versionadded:: 6.2.0
.. py:method:: PIL.ImageDraw.getdraw(im=None, hints=None) .. py:method:: getdraw(im=None, hints=None)
.. warning:: This method is experimental. .. warning:: This method is experimental.
@ -450,7 +450,7 @@ Methods
:param hints: An optional list of hints. :param hints: An optional list of hints.
:returns: A (drawing context, drawing resource factory) tuple. :returns: A (drawing context, drawing resource factory) tuple.
.. py:method:: PIL.ImageDraw.floodfill(image, xy, value, border=None, thresh=0) .. py:method:: floodfill(image, xy, value, border=None, thresh=0)
.. warning:: This method is experimental. .. warning:: This method is experimental.

View File

@ -29,7 +29,8 @@ Classes
All enhancement classes implement a common interface, containing a single All enhancement classes implement a common interface, containing a single
method: method:
.. py:class:: PIL.ImageEnhance._Enhance .. py:class:: _Enhance
.. py:method:: enhance(factor) .. py:method:: enhance(factor)
Returns an enhanced image. Returns an enhanced image.
@ -40,7 +41,7 @@ method:
etc), and higher values more. There are no restrictions etc), and higher values more. There are no restrictions
on this value. on this value.
.. py:class:: PIL.ImageEnhance.Color(image) .. py:class:: Color(image)
Adjust image color balance. Adjust image color balance.
@ -49,7 +50,7 @@ method:
factor of 0.0 gives a black and white image. A factor of 1.0 gives factor of 0.0 gives a black and white image. A factor of 1.0 gives
the original image. the original image.
.. py:class:: PIL.ImageEnhance.Contrast(image) .. py:class:: Contrast(image)
Adjust image contrast. Adjust image contrast.
@ -57,7 +58,7 @@ method:
to the contrast control on a TV set. An enhancement factor of 0.0 to the contrast control on a TV set. An enhancement factor of 0.0
gives a solid grey image. A factor of 1.0 gives the original image. gives a solid grey image. A factor of 1.0 gives the original image.
.. py:class:: PIL.ImageEnhance.Brightness(image) .. py:class:: Brightness(image)
Adjust image brightness. Adjust image brightness.
@ -65,7 +66,7 @@ method:
enhancement factor of 0.0 gives a black image. A factor of 1.0 gives the enhancement factor of 0.0 gives a black image. A factor of 1.0 gives the
original image. original image.
.. py:class:: PIL.ImageEnhance.Sharpness(image) .. py:class:: Sharpness(image)
Adjust image sharpness. Adjust image sharpness.

View File

@ -0,0 +1,27 @@
.. py:module:: PIL.ImageShow
.. py:currentmodule:: PIL.ImageShow
:py:mod:`ImageShow` Module
==========================
The :py:mod:`ImageShow` Module is used to display images.
All default viewers convert the image to be shown to PNG format.
.. autofunction:: PIL.ImageShow.show
.. autoclass:: WindowsViewer
.. autoclass:: MacViewer
.. class:: UnixViewer
The following viewers may be registered on Unix-based systems, if the given command is found:
.. autoclass:: PIL.ImageShow.DisplayViewer
.. autoclass:: PIL.ImageShow.EogViewer
.. autoclass:: PIL.ImageShow.XVViewer
.. autofunction:: PIL.ImageShow.register
.. autoclass:: PIL.ImageShow.Viewer
:member-order: bysource
:members:
:undoc-members:

View File

@ -7,7 +7,7 @@
The :py:mod:`ImageStat` module calculates global statistics for an image, or The :py:mod:`ImageStat` module calculates global statistics for an image, or
for a region of an image. for a region of an image.
.. py:class:: PIL.ImageStat.Stat(image_or_list, mask=None) .. py:class:: Stat(image_or_list, mask=None)
Calculate statistics for the given image. If a mask is included, Calculate statistics for the given image. If a mask is included,
only the regions covered by that mask are included in the only the regions covered by that mask are included in the
@ -22,13 +22,13 @@ for a region of an image.
.. note:: .. note::
This relies on the :py:meth:`~PIL.Image.histogram` method, and This relies on the :py:meth:`~PIL.Image.Image.histogram` method, and
simply returns the low and high bins used. This is correct for simply returns the low and high bins used. This is correct for
images with 8 bits per channel, but fails for other modes such as images with 8 bits per channel, but fails for other modes such as
``I`` or ``F``. Instead, use :py:meth:`~PIL.Image.getextrema` to ``I`` or ``F``. Instead, use :py:meth:`~PIL.Image.Image.getextrema` to
return per-band extrema for the image. This is more correct and return per-band extrema for the image. This is more correct and
efficient because, for non-8-bit modes, the histogram method uses efficient because, for non-8-bit modes, the histogram method uses
:py:meth:`~PIL.Image.getextrema` to determine the bins used. :py:meth:`~PIL.Image.Image.getextrema` to determine the bins used.
.. py:attribute:: count .. py:attribute:: count

View File

@ -0,0 +1,11 @@
.. py:currentmodule:: PIL.JpegPresets
:py:mod:`JpegPresets` Module
============================
.. automodule:: PIL.JpegPresets
.. data:: presets
:type: dict
A dictionary of all supported presets.

View File

@ -10,8 +10,8 @@ metadata tag numbers, names, and type information.
.. method:: lookup(tag) .. method:: lookup(tag)
:param tag: Integer tag number :param tag: Integer tag number
:returns: Taginfo namedtuple, From the ``TAGS_V2`` info if possible, :returns: Taginfo namedtuple, From the :py:data:`~PIL.TiffTags.TAGS_V2` info if possible,
otherwise just populating the value and name from ``TAGS``. otherwise just populating the value and name from :py:data:`~PIL.TiffTags.TAGS`.
If the tag is not recognized, "unknown" is returned for the name If the tag is not recognized, "unknown" is returned for the name
.. versionadded:: 3.1.0 .. versionadded:: 3.1.0
@ -22,7 +22,7 @@ metadata tag numbers, names, and type information.
:param value: Integer Tag Number :param value: Integer Tag Number
:param name: Tag Name :param name: Tag Name
:param type: Integer type from :py:attr:`PIL.TiffTags.TYPES` :param type: Integer type from :py:data:`PIL.TiffTags.TYPES`
:param length: Array length: 0 == variable, 1 == single value, n = fixed :param length: Array length: 0 == variable, 1 == single value, n = fixed
:param enum: Dict of name:integer value options for an enumeration :param enum: Dict of name:integer value options for an enumeration
@ -33,15 +33,17 @@ metadata tag numbers, names, and type information.
.. versionadded:: 3.0.0 .. versionadded:: 3.0.0
.. py:attribute:: PIL.TiffTags.TAGS_V2 .. py:data:: PIL.TiffTags.TAGS_V2
:type: dict
The ``TAGS_V2`` dictionary maps 16-bit integer tag numbers to The ``TAGS_V2`` dictionary maps 16-bit integer tag numbers to
:py:class:`PIL.TagTypes.TagInfo` tuples for metadata fields defined in the TIFF :py:class:`PIL.TiffTags.TagInfo` tuples for metadata fields defined in the TIFF
spec. spec.
.. versionadded:: 3.0.0 .. versionadded:: 3.0.0
.. py:attribute:: PIL.TiffTags.TAGS .. py:data:: PIL.TiffTags.TAGS
:type: dict
The ``TAGS`` dictionary maps 16-bit integer TIFF tag number to The ``TAGS`` dictionary maps 16-bit integer TIFF tag number to
descriptive string names. For instance: descriptive string names. For instance:
@ -50,10 +52,11 @@ metadata tag numbers, names, and type information.
>>> TAGS[0x010e] >>> TAGS[0x010e]
'ImageDescription' 'ImageDescription'
This dictionary contains a superset of the tags in TAGS_V2, common This dictionary contains a superset of the tags in :py:data:`~PIL.TiffTags.TAGS_V2`, common
EXIF tags, and other well known metadata tags. EXIF tags, and other well known metadata tags.
.. py:attribute:: PIL.TiffTags.TYPES .. py:data:: PIL.TiffTags.TYPES
:type: dict
The ``TYPES`` dictionary maps the TIFF type short integer to a The ``TYPES`` dictionary maps the TIFF type short integer to a
human readable type name. human readable type name.

View File

@ -7,8 +7,8 @@ Reference
Image Image
ImageChops ImageChops
ImageColor
ImageCms ImageCms
ImageColor
ImageDraw ImageDraw
ImageEnhance ImageEnhance
ImageFile ImageFile
@ -22,11 +22,13 @@ Reference
ImagePath ImagePath
ImageQt ImageQt
ImageSequence ImageSequence
ImageShow
ImageStat ImageStat
ImageTk ImageTk
ImageWin ImageWin
ExifTags ExifTags
TiffTags TiffTags
JpegPresets
PSDraw PSDraw
PixelAccess PixelAccess
PyAccess PyAccess

View File

@ -7,4 +7,4 @@ Internal Reference Docs
open_files open_files
limits limits
block_allocator block_allocator
internal_modules

View File

@ -0,0 +1,38 @@
Internal Modules
================
:mod:`_binary` Module
---------------------
.. automodule:: PIL._binary
:members:
:undoc-members:
:show-inheritance:
:mod:`_tkinter_finder` Module
-----------------------------
.. automodule:: PIL._tkinter_finder
:members:
:undoc-members:
:show-inheritance:
:mod:`_util` Module
-------------------
.. automodule:: PIL._util
:members:
:undoc-members:
:show-inheritance:
:mod:`_version` Module
----------------------
.. module:: PIL._version
.. data:: __version__
:annotation:
:type: str
This is the master version number for Pillow,
all other uses reference this module.

View File

@ -37,6 +37,19 @@ are now read as just a single bytestring.
Deprecations Deprecations
^^^^^^^^^^^^ ^^^^^^^^^^^^
Image.show command parameter
~~~~~~~~~~~~~~~~~~~~~~~~~~~~
The ``command`` parameter was deprecated and will be removed in a future release.
Use a subclass of :py:class:`PIL.ImageShow.Viewer` instead.
Image._showxv
~~~~~~~~~~~~~
``Image._showxv`` has been deprecated. Use :py:meth:`~PIL.Image.Image.show`
instead. If custom behaviour is required, use :py:meth:`~PIL.ImageShow.register` to add
a custom :py:class:`~PIL.ImageShow.Viewer` class.
ImageFile.raise_ioerror ImageFile.raise_ioerror
~~~~~~~~~~~~~~~~~~~~~~~ ~~~~~~~~~~~~~~~~~~~~~~~

View File

@ -9,4 +9,5 @@ pyflakes
pyroma pyroma
pytest pytest
pytest-cov pytest-cov
sphinx>=2.4
sphinx-rtd-theme sphinx-rtd-theme

View File

@ -9,5 +9,5 @@ line_length = 88
multi_line_output = 3 multi_line_output = 3
[tool:pytest] [tool:pytest]
addopts = -rs --color=yes addopts = -ra --color=yes
testpaths = Tests testpaths = Tests

View File

@ -17,13 +17,13 @@
# See the README file for information on usage and redistribution. # See the README file for information on usage and redistribution.
# #
"""
Parse X Bitmap Distribution Format (BDF)
"""
from . import FontFile, Image from . import FontFile, Image
# --------------------------------------------------------------------
# parse X Bitmap Distribution Format (BDF)
# --------------------------------------------------------------------
bdf_slant = { bdf_slant = {
"R": "Roman", "R": "Roman",
"I": "Italic", "I": "Italic",
@ -78,11 +78,9 @@ def bdf_char(f):
return id, int(props["ENCODING"]), bbox, im return id, int(props["ENCODING"]), bbox, im
##
# Font file plugin for the X11 BDF format.
class BdfFontFile(FontFile.FontFile): class BdfFontFile(FontFile.FontFile):
"""Font file plugin for the X11 BDF format."""
def __init__(self, fp): def __init__(self, fp):
super().__init__() super().__init__()

View File

@ -282,8 +282,8 @@ class _BLPBaseDecoder(ImageFile.PyDecoder):
self.magic = self.fd.read(4) self.magic = self.fd.read(4)
self._read_blp_header() self._read_blp_header()
self._load() self._load()
except struct.error: except struct.error as e:
raise OSError("Truncated Blp file") raise OSError("Truncated Blp file") from e
return 0, 0 return 0, 0
def _read_palette(self): def _read_palette(self):

View File

@ -263,7 +263,7 @@ class BmpImageFile(ImageFile.ImageFile):
# read 14 bytes: magic number, filesize, reserved, header final offset # read 14 bytes: magic number, filesize, reserved, header final offset
head_data = self.fp.read(14) head_data = self.fp.read(14)
# choke if the file does not have the required magic bytes # choke if the file does not have the required magic bytes
if head_data[0:2] != b"BM": if not _accept(head_data):
raise SyntaxError("Not a BMP file") raise SyntaxError("Not a BMP file")
# read the start position of the BMP image data (u32) # read the start position of the BMP image data (u32)
offset = i32(head_data[10:14]) offset = i32(head_data[10:14])
@ -304,8 +304,8 @@ def _dib_save(im, fp, filename):
def _save(im, fp, filename, bitmap_header=True): def _save(im, fp, filename, bitmap_header=True):
try: try:
rawmode, bits, colors = SAVE[im.mode] rawmode, bits, colors = SAVE[im.mode]
except KeyError: except KeyError as e:
raise OSError("cannot write mode %s as BMP" % im.mode) raise OSError("cannot write mode %s as BMP" % im.mode) from e
info = im.encoderinfo info = im.encoderinfo

View File

@ -14,14 +14,16 @@
# See the README file for information on usage and redistribution. # See the README file for information on usage and redistribution.
# #
##
# A file object that provides read access to a part of an existing
# file (for example a TAR file).
import io import io
class ContainerIO: class ContainerIO:
"""
A file object that provides read access to a part of an existing
file (for example a TAR file).
"""
def __init__(self, file, offset, length): def __init__(self, file, offset, length):
""" """
Create file object. Create file object.

View File

@ -46,7 +46,7 @@ class DcxImageFile(PcxImageFile):
# Header # Header
s = self.fp.read(4) s = self.fp.read(4)
if i32(s) != MAGIC: if not _accept(s):
raise SyntaxError("not a DCX file") raise SyntaxError("not a DCX file")
# Component directory # Component directory

View File

@ -231,8 +231,8 @@ class EpsImageFile(ImageFile.ImageFile):
try: try:
m = split.match(s) m = split.match(s)
except re.error: except re.error as e:
raise SyntaxError("not an EPS file") raise SyntaxError("not an EPS file") from e
if m: if m:
k, v = m.group(1, 2) k, v = m.group(1, 2)

View File

@ -9,13 +9,11 @@
# See the README file for information on usage and redistribution. # See the README file for information on usage and redistribution.
# #
## """
# This module provides constants and clear-text names for various This module provides constants and clear-text names for various
# well-known EXIF tags. well-known EXIF tags.
## """
##
# Maps EXIF tags to tag names.
TAGS = { TAGS = {
# possibly incomplete # possibly incomplete
@ -280,9 +278,8 @@ TAGS = {
0xC74E: "OpcodeList3", 0xC74E: "OpcodeList3",
0xC761: "NoiseProfile", 0xC761: "NoiseProfile",
} }
"""Maps EXIF tags to tag names."""
##
# Maps EXIF GPS tags to tag names.
GPSTAGS = { GPSTAGS = {
0: "GPSVersionID", 0: "GPSVersionID",
@ -318,3 +315,4 @@ GPSTAGS = {
30: "GPSDifferential", 30: "GPSDifferential",
31: "GPSHPositioningError", 31: "GPSHPositioningError",
} }
"""Maps EXIF GPS tags to tag names."""

View File

@ -42,9 +42,8 @@ class FliImageFile(ImageFile.ImageFile):
# HEAD # HEAD
s = self.fp.read(128) s = self.fp.read(128)
magic = i16(s[4:6])
if not ( if not (
magic in [0xAF11, 0xAF12] _accept(s)
and i16(s[14:16]) in [0, 3] # flags and i16(s[14:16]) in [0, 3] # flags
and s[20:22] == b"\x00\x00" # reserved and s[20:22] == b"\x00\x00" # reserved
): ):
@ -60,6 +59,7 @@ class FliImageFile(ImageFile.ImageFile):
# animation speed # animation speed
duration = i32(s[16:20]) duration = i32(s[16:20])
magic = i16(s[4:6])
if magic == 0xAF11: if magic == 0xAF11:
duration = (duration * 1000) // 70 duration = (duration * 1000) // 70
self.info["duration"] = duration self.info["duration"] = duration

View File

@ -23,18 +23,15 @@ WIDTH = 800
def puti16(fp, values): def puti16(fp, values):
# write network order (big-endian) 16-bit sequence """Write network order (big-endian) 16-bit sequence"""
for v in values: for v in values:
if v < 0: if v < 0:
v += 65536 v += 65536
fp.write(_binary.o16be(v)) fp.write(_binary.o16be(v))
##
# Base class for raster font file handlers.
class FontFile: class FontFile:
"""Base class for raster font file handlers."""
bitmap = None bitmap = None

View File

@ -59,8 +59,8 @@ class FpxImageFile(ImageFile.ImageFile):
try: try:
self.ole = olefile.OleFileIO(self.fp) self.ole = olefile.OleFileIO(self.fp)
except OSError: except OSError as e:
raise SyntaxError("not an FPX file; invalid OLE file") raise SyntaxError("not an FPX file; invalid OLE file") from e
if self.ole.root.clsid != "56616700-C154-11CE-8553-00AA00A1F95B": if self.ole.root.clsid != "56616700-C154-11CE-8553-00AA00A1F95B":
raise SyntaxError("not an FPX file; bad root CLSID") raise SyntaxError("not an FPX file; bad root CLSID")

View File

@ -14,26 +14,30 @@
# #
# NOTE: This format cannot be automatically recognized, so the """
# class is not registered for use with Image.open(). To open a .. note::
# gd file, use the GdImageFile.open() function instead. This format cannot be automatically recognized, so the
class is not registered for use with :py:func:`PIL.Image.open()`. To open a
gd file, use the :py:func:`PIL.GdImageFile.open()` function instead.
# THE GD FORMAT IS NOT DESIGNED FOR DATA INTERCHANGE. This .. warning::
# implementation is provided for convenience and demonstrational THE GD FORMAT IS NOT DESIGNED FOR DATA INTERCHANGE. This
# purposes only. implementation is provided for convenience and demonstrational
purposes only.
"""
from . import ImageFile, ImagePalette, UnidentifiedImageError from . import ImageFile, ImagePalette, UnidentifiedImageError
from ._binary import i8, i16be as i16, i32be as i32 from ._binary import i8, i16be as i16, i32be as i32
##
# Image plugin for the GD uncompressed format. Note that this format
# is not supported by the standard <b>Image.open</b> function. To use
# this plugin, you have to import the <b>GdImageFile</b> module and
# use the <b>GdImageFile.open</b> function.
class GdImageFile(ImageFile.ImageFile): class GdImageFile(ImageFile.ImageFile):
"""
Image plugin for the GD uncompressed format. Note that this format
is not supported by the standard :py:func:`PIL.Image.open()` function. To use
this plugin, you have to import the :py:mod:`PIL.GdImageFile` module and
use the :py:func:`PIL.GdImageFile.open()` function.
"""
format = "GD" format = "GD"
format_description = "GD uncompressed images" format_description = "GD uncompressed images"
@ -81,5 +85,5 @@ def open(fp, mode="r"):
try: try:
return GdImageFile(fp) return GdImageFile(fp)
except SyntaxError: except SyntaxError as e:
raise UnidentifiedImageError("cannot identify this image file") raise UnidentifiedImageError("cannot identify this image file") from e

View File

@ -63,7 +63,7 @@ class GifImageFile(ImageFile.ImageFile):
# Screen # Screen
s = self.fp.read(13) s = self.fp.read(13)
if s[:6] not in [b"GIF87a", b"GIF89a"]: if not _accept(s):
raise SyntaxError("not a GIF file") raise SyntaxError("not a GIF file")
self.info["version"] = s[:6] self.info["version"] = s[:6]
@ -130,9 +130,9 @@ class GifImageFile(ImageFile.ImageFile):
for f in range(self.__frame + 1, frame + 1): for f in range(self.__frame + 1, frame + 1):
try: try:
self._seek(f) self._seek(f)
except EOFError: except EOFError as e:
self.seek(last_frame) self.seek(last_frame)
raise EOFError("no more images in GIF file") raise EOFError("no more images in GIF file") from e
def _seek(self, frame): def _seek(self, frame):

View File

@ -13,17 +13,19 @@
# See the README file for information on usage and redistribution. # See the README file for information on usage and redistribution.
# #
"""
Stuff to translate curve segments to palette values (derived from
the corresponding code in GIMP, written by Federico Mena Quintero.
See the GIMP distribution for more information.)
"""
from math import log, pi, sin, sqrt from math import log, pi, sin, sqrt
from ._binary import o8 from ._binary import o8
# --------------------------------------------------------------------
# Stuff to translate curve segments to palette values (derived from
# the corresponding code in GIMP, written by Federico Mena Quintero.
# See the GIMP distribution for more information.)
#
EPSILON = 1e-10 EPSILON = 1e-10
"""""" # Enable auto-doc for data member
def linear(middle, pos): def linear(middle, pos):
@ -58,6 +60,7 @@ def sphere_decreasing(middle, pos):
SEGMENTS = [linear, curved, sine, sphere_increasing, sphere_decreasing] SEGMENTS = [linear, curved, sine, sphere_increasing, sphere_decreasing]
"""""" # Enable auto-doc for data member
class GradientFile: class GradientFile:
@ -98,11 +101,9 @@ class GradientFile:
return b"".join(palette), "RGBA" return b"".join(palette), "RGBA"
##
# File handler for GIMP's gradient format.
class GimpGradientFile(GradientFile): class GimpGradientFile(GradientFile):
"""File handler for GIMP's gradient format."""
def __init__(self, fp): def __init__(self, fp):
if fp.readline()[:13] != b"GIMP Gradient": if fp.readline()[:13] != b"GIMP Gradient":

View File

@ -18,11 +18,9 @@ import re
from ._binary import o8 from ._binary import o8
##
# File handler for GIMP's palette format.
class GimpPaletteFile: class GimpPaletteFile:
"""File handler for GIMP's palette format."""
rawmode = "RGB" rawmode = "RGB"

View File

@ -163,8 +163,8 @@ class ImImageFile(ImageFile.ImageFile):
try: try:
m = split.match(s) m = split.match(s)
except re.error: except re.error as e:
raise SyntaxError("not an IM file") raise SyntaxError("not an IM file") from e
if m: if m:
@ -341,8 +341,8 @@ def _save(im, fp, filename):
try: try:
image_type, rawmode = SAVE[im.mode] image_type, rawmode = SAVE[im.mode]
except KeyError: except KeyError as e:
raise ValueError("Cannot save %s images as IM" % im.mode) raise ValueError("Cannot save %s images as IM" % im.mode) from e
frames = im.encoderinfo.get("frames", 1) frames = im.encoderinfo.get("frames", 1)

View File

@ -434,8 +434,8 @@ def _getdecoder(mode, decoder_name, args, extra=()):
try: try:
# get decoder # get decoder
decoder = getattr(core, decoder_name + "_decoder") decoder = getattr(core, decoder_name + "_decoder")
except AttributeError: except AttributeError as e:
raise OSError("decoder %s not available" % decoder_name) raise OSError("decoder %s not available" % decoder_name) from e
return decoder(mode, *args + extra) return decoder(mode, *args + extra)
@ -457,8 +457,8 @@ def _getencoder(mode, encoder_name, args, extra=()):
try: try:
# get encoder # get encoder
encoder = getattr(core, encoder_name + "_encoder") encoder = getattr(core, encoder_name + "_encoder")
except AttributeError: except AttributeError as e:
raise OSError("encoder %s not available" % encoder_name) raise OSError("encoder %s not available" % encoder_name) from e
return encoder(mode, *args + extra) return encoder(mode, *args + extra)
@ -971,10 +971,10 @@ class Image:
if isinstance(t, tuple): if isinstance(t, tuple):
try: try:
t = trns_im.palette.getcolor(t) t = trns_im.palette.getcolor(t)
except Exception: except Exception as e:
raise ValueError( raise ValueError(
"Couldn't allocate a palette color for transparency" "Couldn't allocate a palette color for transparency"
) ) from e
trns_im.putpixel((0, 0), t) trns_im.putpixel((0, 0), t)
if mode in ("L", "RGB"): if mode in ("L", "RGB"):
@ -1027,8 +1027,8 @@ class Image:
# normalize source image and try again # normalize source image and try again
im = self.im.convert(getmodebase(self.mode)) im = self.im.convert(getmodebase(self.mode))
im = im.convert(mode, dither) im = im.convert(mode, dither)
except KeyError: except KeyError as e:
raise ValueError("illegal conversion") raise ValueError("illegal conversion") from e
new_im = self._new(im) new_im = self._new(im)
if delete_trns: if delete_trns:
@ -1625,16 +1625,16 @@ class Image:
mode = getmodebase(self.mode) + "A" mode = getmodebase(self.mode) + "A"
try: try:
self.im.setmode(mode) self.im.setmode(mode)
except (AttributeError, ValueError): except (AttributeError, ValueError) as e:
# do things the hard way # do things the hard way
im = self.im.convert(mode) im = self.im.convert(mode)
if im.mode not in ("LA", "PA", "RGBA"): if im.mode not in ("LA", "PA", "RGBA"):
raise ValueError # sanity check raise ValueError from e # sanity check
self.im = im self.im = im
self.pyaccess = None self.pyaccess = None
self.mode = self.im.mode self.mode = self.im.mode
except (KeyError, ValueError): except (KeyError, ValueError) as e:
raise ValueError("illegal image mode") raise ValueError("illegal image mode") from e
if self.mode in ("LA", "PA"): if self.mode in ("LA", "PA"):
band = 1 band = 1
@ -2136,8 +2136,8 @@ class Image:
init() init()
try: try:
format = EXTENSION[ext] format = EXTENSION[ext]
except KeyError: except KeyError as e:
raise ValueError("unknown file extension: {}".format(ext)) raise ValueError("unknown file extension: {}".format(ext)) from e
if format.upper() not in SAVE: if format.upper() not in SAVE:
init() init()
@ -2181,8 +2181,10 @@ class Image:
def show(self, title=None, command=None): def show(self, title=None, command=None):
""" """
Displays this image. This method is mainly intended for Displays this image. This method is mainly intended for debugging purposes.
debugging purposes.
This method calls :py:func:`PIL.ImageShow.show` internally. You can use
:py:func:`PIL.ImageShow.register` to override its default behaviour.
The image is first saved to a temporary file. By default, it will be in The image is first saved to a temporary file. By default, it will be in
PNG format. PNG format.
@ -2194,11 +2196,16 @@ class Image:
On Windows, the image is opened with the standard PNG display utility. 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.
:param command: command used to show the image
""" """
if command is not None:
warnings.warn(
"The command parameter is deprecated and will be removed in a future "
"release. Use a subclass of ImageShow.Viewer instead.",
DeprecationWarning,
)
_show(self, title=title, command=command) _show(self, title=title, command=command)
def split(self): def split(self):
@ -2238,8 +2245,8 @@ class Image:
if isinstance(channel, str): if isinstance(channel, str):
try: try:
channel = self.getbands().index(channel) channel = self.getbands().index(channel)
except ValueError: except ValueError as e:
raise ValueError('The image has no channel "{}"'.format(channel)) raise ValueError('The image has no channel "{}"'.format(channel)) from e
return self._new(self.im.getband(channel)) return self._new(self.im.getband(channel))
@ -2736,12 +2743,12 @@ def fromarray(obj, mode=None):
if mode is None: if mode is None:
try: try:
typekey = (1, 1) + shape[2:], arr["typestr"] typekey = (1, 1) + shape[2:], arr["typestr"]
except KeyError: except KeyError as e:
raise TypeError("Cannot handle this data type") raise TypeError("Cannot handle this data type") from e
try: try:
mode, rawmode = _fromarray_typemap[typekey] mode, rawmode = _fromarray_typemap[typekey]
except KeyError: except KeyError as e:
raise TypeError("Cannot handle this data type: %s, %s" % typekey) raise TypeError("Cannot handle this data type: %s, %s" % typekey) from e
else: else:
rawmode = mode rawmode = mode
if mode in ["1", "L", "I", "P", "F"]: if mode in ["1", "L", "I", "P", "F"]:
@ -3143,12 +3150,21 @@ def register_encoder(name, encoder):
def _show(image, **options): def _show(image, **options):
options["_internal_pillow"] = True
_showxv(image, **options) _showxv(image, **options)
def _showxv(image, title=None, **options): def _showxv(image, title=None, **options):
from . import ImageShow from . import ImageShow
if "_internal_pillow" in options:
del options["_internal_pillow"]
else:
warnings.warn(
"_showxv is deprecated and will be removed in a future release. "
"Use Image.show instead.",
DeprecationWarning,
)
ImageShow.show(image, title, **options) ImageShow.show(image, title, **options)

View File

@ -369,7 +369,7 @@ def profileToProfile(
else: else:
imOut = transform.apply(im) imOut = transform.apply(im)
except (OSError, TypeError, ValueError) as v: except (OSError, TypeError, ValueError) as v:
raise PyCMSError(v) raise PyCMSError(v) from v
return imOut return imOut
@ -393,7 +393,7 @@ def getOpenProfile(profileFilename):
try: try:
return ImageCmsProfile(profileFilename) return ImageCmsProfile(profileFilename)
except (OSError, TypeError, ValueError) as v: except (OSError, TypeError, ValueError) as v:
raise PyCMSError(v) raise PyCMSError(v) from v
def buildTransform( def buildTransform(
@ -474,7 +474,7 @@ def buildTransform(
inputProfile, outputProfile, inMode, outMode, renderingIntent, flags=flags inputProfile, outputProfile, inMode, outMode, renderingIntent, flags=flags
) )
except (OSError, TypeError, ValueError) as v: except (OSError, TypeError, ValueError) as v:
raise PyCMSError(v) raise PyCMSError(v) from v
def buildProofTransform( def buildProofTransform(
@ -585,7 +585,7 @@ def buildProofTransform(
flags, flags,
) )
except (OSError, TypeError, ValueError) as v: except (OSError, TypeError, ValueError) as v:
raise PyCMSError(v) raise PyCMSError(v) from v
buildTransformFromOpenProfiles = buildTransform buildTransformFromOpenProfiles = buildTransform
@ -640,7 +640,7 @@ def applyTransform(im, transform, inPlace=False):
else: else:
imOut = transform.apply(im) imOut = transform.apply(im)
except (TypeError, ValueError) as v: except (TypeError, ValueError) as v:
raise PyCMSError(v) raise PyCMSError(v) from v
return imOut return imOut
@ -682,15 +682,15 @@ def createProfile(colorSpace, colorTemp=-1):
if colorSpace == "LAB": if colorSpace == "LAB":
try: try:
colorTemp = float(colorTemp) colorTemp = float(colorTemp)
except (TypeError, ValueError): except (TypeError, ValueError) as e:
raise PyCMSError( raise PyCMSError(
'Color temperature must be numeric, "%s" not valid' % colorTemp 'Color temperature must be numeric, "%s" not valid' % colorTemp
) ) from e
try: try:
return core.createProfile(colorSpace, colorTemp) return core.createProfile(colorSpace, colorTemp)
except (TypeError, ValueError) as v: except (TypeError, ValueError) as v:
raise PyCMSError(v) raise PyCMSError(v) from v
def getProfileName(profile): def getProfileName(profile):
@ -732,7 +732,7 @@ def getProfileName(profile):
return "{} - {}\n".format(model, manufacturer) return "{} - {}\n".format(model, manufacturer)
except (AttributeError, OSError, TypeError, ValueError) as v: except (AttributeError, OSError, TypeError, ValueError) as v:
raise PyCMSError(v) raise PyCMSError(v) from v
def getProfileInfo(profile): def getProfileInfo(profile):
@ -772,7 +772,7 @@ def getProfileInfo(profile):
return "\r\n\r\n".join(arr) + "\r\n\r\n" return "\r\n\r\n".join(arr) + "\r\n\r\n"
except (AttributeError, OSError, TypeError, ValueError) as v: except (AttributeError, OSError, TypeError, ValueError) as v:
raise PyCMSError(v) raise PyCMSError(v) from v
def getProfileCopyright(profile): def getProfileCopyright(profile):
@ -800,7 +800,7 @@ def getProfileCopyright(profile):
profile = ImageCmsProfile(profile) profile = ImageCmsProfile(profile)
return (profile.profile.copyright or "") + "\n" return (profile.profile.copyright or "") + "\n"
except (AttributeError, OSError, TypeError, ValueError) as v: except (AttributeError, OSError, TypeError, ValueError) as v:
raise PyCMSError(v) raise PyCMSError(v) from v
def getProfileManufacturer(profile): def getProfileManufacturer(profile):
@ -828,7 +828,7 @@ def getProfileManufacturer(profile):
profile = ImageCmsProfile(profile) profile = ImageCmsProfile(profile)
return (profile.profile.manufacturer or "") + "\n" return (profile.profile.manufacturer or "") + "\n"
except (AttributeError, OSError, TypeError, ValueError) as v: except (AttributeError, OSError, TypeError, ValueError) as v:
raise PyCMSError(v) raise PyCMSError(v) from v
def getProfileModel(profile): def getProfileModel(profile):
@ -857,7 +857,7 @@ def getProfileModel(profile):
profile = ImageCmsProfile(profile) profile = ImageCmsProfile(profile)
return (profile.profile.model or "") + "\n" return (profile.profile.model or "") + "\n"
except (AttributeError, OSError, TypeError, ValueError) as v: except (AttributeError, OSError, TypeError, ValueError) as v:
raise PyCMSError(v) raise PyCMSError(v) from v
def getProfileDescription(profile): def getProfileDescription(profile):
@ -886,7 +886,7 @@ def getProfileDescription(profile):
profile = ImageCmsProfile(profile) profile = ImageCmsProfile(profile)
return (profile.profile.profile_description or "") + "\n" return (profile.profile.profile_description or "") + "\n"
except (AttributeError, OSError, TypeError, ValueError) as v: except (AttributeError, OSError, TypeError, ValueError) as v:
raise PyCMSError(v) raise PyCMSError(v) from v
def getDefaultIntent(profile): def getDefaultIntent(profile):
@ -925,7 +925,7 @@ def getDefaultIntent(profile):
profile = ImageCmsProfile(profile) profile = ImageCmsProfile(profile)
return profile.profile.rendering_intent return profile.profile.rendering_intent
except (AttributeError, OSError, TypeError, ValueError) as v: except (AttributeError, OSError, TypeError, ValueError) as v:
raise PyCMSError(v) raise PyCMSError(v) from v
def isIntentSupported(profile, intent, direction): def isIntentSupported(profile, intent, direction):
@ -976,7 +976,7 @@ def isIntentSupported(profile, intent, direction):
else: else:
return -1 return -1
except (AttributeError, OSError, TypeError, ValueError) as v: except (AttributeError, OSError, TypeError, ValueError) as v:
raise PyCMSError(v) raise PyCMSError(v) from v
def versions(): def versions():

View File

@ -16,21 +16,35 @@
# See the README file for information on usage and redistribution. # See the README file for information on usage and redistribution.
# #
"""
(Experimental) WCK-style drawing interface operations
.. seealso:: :py:mod:`PIL.ImageDraw`
"""
from . import Image, ImageColor, ImageDraw, ImageFont, ImagePath from . import Image, ImageColor, ImageDraw, ImageFont, ImagePath
class Pen: class Pen:
"""Stores an outline color and width."""
def __init__(self, color, width=1, opacity=255): def __init__(self, color, width=1, opacity=255):
self.color = ImageColor.getrgb(color) self.color = ImageColor.getrgb(color)
self.width = width self.width = width
class Brush: class Brush:
"""Stores a fill color"""
def __init__(self, color, opacity=255): def __init__(self, color, opacity=255):
self.color = ImageColor.getrgb(color) self.color = ImageColor.getrgb(color)
class Font: class Font:
"""Stores a TrueType font and color"""
def __init__(self, color, file, size=12): def __init__(self, color, file, size=12):
# FIXME: add support for bitmap fonts # FIXME: add support for bitmap fonts
self.color = ImageColor.getrgb(color) self.color = ImageColor.getrgb(color)
@ -38,6 +52,10 @@ class Font:
class Draw: class Draw:
"""
(Experimental) WCK-style drawing interface
"""
def __init__(self, image, size=None, color=None): def __init__(self, image, size=None, color=None):
if not hasattr(image, "im"): if not hasattr(image, "im"):
image = Image.new(image, size, color) image = Image.new(image, size, color)
@ -73,35 +91,89 @@ class Draw:
getattr(self.draw, op)(xy, fill=fill, outline=outline) getattr(self.draw, op)(xy, fill=fill, outline=outline)
def settransform(self, offset): def settransform(self, offset):
"""Sets a transformation offset."""
(xoffset, yoffset) = offset (xoffset, yoffset) = offset
self.transform = (1, 0, xoffset, 0, 1, yoffset) self.transform = (1, 0, xoffset, 0, 1, yoffset)
def arc(self, xy, start, end, *options): def arc(self, xy, start, end, *options):
"""
Draws an arc (a portion of a circle outline) between the start and end
angles, inside the given bounding box.
.. seealso:: :py:meth:`PIL.ImageDraw.ImageDraw.arc`
"""
self.render("arc", xy, start, end, *options) self.render("arc", xy, start, end, *options)
def chord(self, xy, start, end, *options): def chord(self, xy, start, end, *options):
"""
Same as :py:meth:`~PIL.ImageDraw2.Draw.arc`, but connects the end points
with a straight line.
.. seealso:: :py:meth:`PIL.ImageDraw.ImageDraw.chord`
"""
self.render("chord", xy, start, end, *options) self.render("chord", xy, start, end, *options)
def ellipse(self, xy, *options): def ellipse(self, xy, *options):
"""
Draws an ellipse inside the given bounding box.
.. seealso:: :py:meth:`PIL.ImageDraw.ImageDraw.ellipse`
"""
self.render("ellipse", xy, *options) self.render("ellipse", xy, *options)
def line(self, xy, *options): def line(self, xy, *options):
"""
Draws a line between the coordinates in the ``xy`` list.
.. seealso:: :py:meth:`PIL.ImageDraw.ImageDraw.line`
"""
self.render("line", xy, *options) self.render("line", xy, *options)
def pieslice(self, xy, start, end, *options): def pieslice(self, xy, start, end, *options):
"""
Same as arc, but also draws straight lines between the end points and the
center of the bounding box.
.. seealso:: :py:meth:`PIL.ImageDraw.ImageDraw.pieslice`
"""
self.render("pieslice", xy, start, end, *options) self.render("pieslice", xy, start, end, *options)
def polygon(self, xy, *options): def polygon(self, xy, *options):
"""
Draws a polygon.
The polygon outline consists of straight lines between the given
coordinates, plus a straight line between the last and the first
coordinate.
.. seealso:: :py:meth:`PIL.ImageDraw.ImageDraw.polygon`
"""
self.render("polygon", xy, *options) self.render("polygon", xy, *options)
def rectangle(self, xy, *options): def rectangle(self, xy, *options):
"""
Draws a rectangle.
.. seealso:: :py:meth:`PIL.ImageDraw.ImageDraw.rectangle`
"""
self.render("rectangle", xy, *options) self.render("rectangle", xy, *options)
def text(self, xy, text, font): def text(self, xy, text, font):
"""
Draws the string at the given position.
.. seealso:: :py:meth:`PIL.ImageDraw.ImageDraw.text`
"""
if self.transform: if self.transform:
xy = ImagePath.Path(xy) xy = ImagePath.Path(xy)
xy.transform(self.transform) xy.transform(self.transform)
self.draw.text(xy, text, font=font.font, fill=font.color) self.draw.text(xy, text, font=font.font, fill=font.color)
def textsize(self, text, font): def textsize(self, text, font):
"""
Return the size of the given string, in pixels.
.. seealso:: :py:meth:`PIL.ImageDraw.ImageDraw.textsize`
"""
return self.draw.textsize(text, font=font.font) return self.draw.textsize(text, font=font.font)

View File

@ -122,7 +122,7 @@ class ImageFile(Image.Image):
EOFError, # got header but not the first frame EOFError, # got header but not the first frame
struct.error, struct.error,
) as v: ) as v:
raise SyntaxError(v) raise SyntaxError(v) from v
if not self.mode or self.size[0] <= 0: if not self.mode or self.size[0] <= 0:
raise SyntaxError("not identified by this driver") raise SyntaxError("not identified by this driver")
@ -241,12 +241,12 @@ class ImageFile(Image.Image):
while True: while True:
try: try:
s = read(self.decodermaxblock) s = read(self.decodermaxblock)
except (IndexError, struct.error): except (IndexError, struct.error) as e:
# truncated png/gif # truncated png/gif
if LOAD_TRUNCATED_IMAGES: if LOAD_TRUNCATED_IMAGES:
break break
else: else:
raise OSError("image file is truncated") raise OSError("image file is truncated") from e
if not s: # truncated jpeg if not s: # truncated jpeg
if LOAD_TRUNCATED_IMAGES: if LOAD_TRUNCATED_IMAGES:
@ -505,7 +505,7 @@ def _save(im, fp, tile, bufsize=0):
try: try:
fh = fp.fileno() fh = fp.fileno()
fp.flush() fp.flush()
except (AttributeError, io.UnsupportedOperation): except (AttributeError, io.UnsupportedOperation) as e:
# compress to Python file-compatible object # compress to Python file-compatible object
for e, b, o, a in tile: for e, b, o, a in tile:
e = Image._getencoder(im.mode, e, a, im.encoderconfig) e = Image._getencoder(im.mode, e, a, im.encoderconfig)
@ -522,7 +522,7 @@ def _save(im, fp, tile, bufsize=0):
if s: if s:
break break
if s < 0: if s < 0:
raise OSError("encoder error %d when writing image file" % s) raise OSError("encoder error %d when writing image file" % s) from e
e.cleanup() e.cleanup()
else: else:
# slight speedup: compress to real file object # slight speedup: compress to real file object

View File

@ -411,10 +411,10 @@ class Color3DLUT(MultibandFilter):
def _check_size(size): def _check_size(size):
try: try:
_, _, _ = size _, _, _ = size
except ValueError: except ValueError as e:
raise ValueError( raise ValueError(
"Size should be either an integer or a tuple of three integers." "Size should be either an integer or a tuple of three integers."
) ) from e
except TypeError: except TypeError:
size = (size, size, size) size = (size, size, size)
size = [int(x) for x in size] size = [int(x) for x in size]

View File

@ -505,8 +505,8 @@ class FreeTypeFont:
""" """
try: try:
names = self.font.getvarnames() names = self.font.getvarnames()
except AttributeError: except AttributeError as e:
raise NotImplementedError("FreeType 2.9.1 or greater is required") raise NotImplementedError("FreeType 2.9.1 or greater is required") from e
return [name.replace(b"\x00", b"") for name in names] return [name.replace(b"\x00", b"") for name in names]
def set_variation_by_name(self, name): def set_variation_by_name(self, name):
@ -535,8 +535,8 @@ class FreeTypeFont:
""" """
try: try:
axes = self.font.getvaraxes() axes = self.font.getvaraxes()
except AttributeError: except AttributeError as e:
raise NotImplementedError("FreeType 2.9.1 or greater is required") raise NotImplementedError("FreeType 2.9.1 or greater is required") from e
for axis in axes: for axis in axes:
axis["name"] = axis["name"].replace(b"\x00", b"") axis["name"] = axis["name"].replace(b"\x00", b"")
return axes return axes
@ -548,8 +548,8 @@ class FreeTypeFont:
""" """
try: try:
self.font.setvaraxes(axes) self.font.setvaraxes(axes)
except AttributeError: except AttributeError as e:
raise NotImplementedError("FreeType 2.9.1 or greater is required") raise NotImplementedError("FreeType 2.9.1 or greater is required") from e
class TransposedFont: class TransposedFont:

View File

@ -57,8 +57,8 @@ class _Operand:
im1.load() im1.load()
try: try:
op = getattr(_imagingmath, op + "_" + im1.mode) op = getattr(_imagingmath, op + "_" + im1.mode)
except AttributeError: except AttributeError as e:
raise TypeError("bad operand type for '%s'" % op) raise TypeError("bad operand type for '%s'" % op) from e
_imagingmath.unop(op, out.im.id, im1.im.id) _imagingmath.unop(op, out.im.id, im1.im.id)
else: else:
# binary operation # binary operation
@ -85,8 +85,8 @@ class _Operand:
im2.load() im2.load()
try: try:
op = getattr(_imagingmath, op + "_" + im1.mode) op = getattr(_imagingmath, op + "_" + im1.mode)
except AttributeError: except AttributeError as e:
raise TypeError("bad operand type for '%s'" % op) raise TypeError("bad operand type for '%s'" % op) from e
_imagingmath.binop(op, out.im.id, im1.im.id, im2.im.id) _imagingmath.binop(op, out.im.id, im1.im.id, im2.im.id)
return _Operand(out) return _Operand(out)

View File

@ -97,13 +97,13 @@ class ImagePalette:
if isinstance(color, tuple): if isinstance(color, tuple):
try: try:
return self.colors[color] return self.colors[color]
except KeyError: except KeyError as e:
# allocate new color slot # allocate new color slot
if isinstance(self.palette, bytes): if isinstance(self.palette, bytes):
self.palette = bytearray(self.palette) self.palette = bytearray(self.palette)
index = len(self.colors) index = len(self.colors)
if index >= 256: if index >= 256:
raise ValueError("cannot allocate more than 256 colors") raise ValueError("cannot allocate more than 256 colors") from e
self.colors[color] = index self.colors[color] = index
self.palette[index] = color[0] self.palette[index] = color[0]
self.palette[index + 256] = color[1] self.palette[index + 256] = color[1]

View File

@ -38,8 +38,8 @@ class Iterator:
try: try:
self.im.seek(ix) self.im.seek(ix)
return self.im return self.im
except EOFError: except EOFError as e:
raise IndexError # end of sequence raise IndexError from e # end of sequence
def __iter__(self): def __iter__(self):
return self return self
@ -49,8 +49,8 @@ class Iterator:
self.im.seek(self.position) self.im.seek(self.position)
self.position += 1 self.position += 1
return self.im return self.im
except EOFError: except EOFError as e:
raise StopIteration raise StopIteration from e
def all_frames(im, func=None): def all_frames(im, func=None):

View File

@ -24,6 +24,14 @@ _viewers = []
def register(viewer, order=1): def register(viewer, order=1):
"""
The :py:func:`register` function is used to register additional viewers.
:param viewer: The viewer to be registered.
:param order:
Zero or a negative integer to prepend this viewer to the list,
a positive integer to append it.
"""
try: try:
if issubclass(viewer, Viewer): if issubclass(viewer, Viewer):
viewer = viewer() viewer = viewer()
@ -42,7 +50,7 @@ def show(image, title=None, **options):
:param image: An image object. :param image: An image object.
:param title: Optional title. Not all viewers can display the title. :param title: Optional title. Not all viewers can display the title.
:param \**options: Additional viewer options. :param \**options: Additional viewer options.
:returns: True if a suitable viewer was found, false otherwise. :returns: ``True`` if a suitable viewer was found, ``False`` otherwise.
""" """
for viewer in _viewers: for viewer in _viewers:
if viewer.show(image, title=title, **options): if viewer.show(image, title=title, **options):
@ -56,6 +64,10 @@ class Viewer:
# main api # main api
def show(self, image, **options): def show(self, image, **options):
"""
The main function for displaying an image.
Converts the given image to the target format and displays it.
"""
# save temporary image to disk # save temporary image to disk
if not ( if not (
@ -70,25 +82,31 @@ class Viewer:
# hook methods # hook methods
format = None format = None
"""The format to convert the image into."""
options = {} options = {}
"""Additional options used to convert the image."""
def get_format(self, image): def get_format(self, image):
"""Return format name, or None to save as PGM/PPM""" """Return format name, or ``None`` to save as PGM/PPM."""
return self.format return self.format
def get_command(self, file, **options): def get_command(self, file, **options):
"""
Returns the command used to display the file.
Not implemented in the base class.
"""
raise NotImplementedError raise NotImplementedError
def save_image(self, image): def save_image(self, image):
"""Save to temporary file, and return filename""" """Save to temporary file and return filename."""
return image._dump(format=self.get_format(image), **self.options) return image._dump(format=self.get_format(image), **self.options)
def show_image(self, image, **options): def show_image(self, image, **options):
"""Display given image""" """Display the given image."""
return self.show_file(self.save_image(image), **options) return self.show_file(self.save_image(image), **options)
def show_file(self, file, **options): def show_file(self, file, **options):
"""Display given file""" """Display the given file."""
os.system(self.get_command(file, **options)) os.system(self.get_command(file, **options))
return 1 return 1
@ -96,9 +114,9 @@ class Viewer:
# -------------------------------------------------------------------- # --------------------------------------------------------------------
if sys.platform == "win32": class WindowsViewer(Viewer):
"""The default viewer on Windows is the default system application for PNG files."""
class WindowsViewer(Viewer):
format = "PNG" format = "PNG"
options = {"compress_level": 1} options = {"compress_level": 1}
@ -109,11 +127,14 @@ if sys.platform == "win32":
'&& del /f "%s"' % (file, file) '&& del /f "%s"' % (file, file)
) )
if sys.platform == "win32":
register(WindowsViewer) register(WindowsViewer)
elif sys.platform == "darwin":
class MacViewer(Viewer): class MacViewer(Viewer):
"""The default viewer on MacOS using ``Preview.app``."""
format = "PNG" format = "PNG"
options = {"compress_level": 1} options = {"compress_level": 1}
@ -140,13 +161,12 @@ elif sys.platform == "darwin":
os.remove(path) os.remove(path)
return 1 return 1
if sys.platform == "darwin":
register(MacViewer) register(MacViewer)
else:
# unixoids class UnixViewer(Viewer):
class UnixViewer(Viewer):
format = "PNG" format = "PNG"
options = {"compress_level": 1} options = {"compress_level": 1}
@ -167,25 +187,29 @@ else:
os.remove(path) os.remove(path)
return 1 return 1
# implementations
class DisplayViewer(UnixViewer): class DisplayViewer(UnixViewer):
"""The ImageMagick ``display`` command."""
def get_command_ex(self, file, **options): def get_command_ex(self, file, **options):
command = executable = "display" command = executable = "display"
return command, executable return command, executable
if shutil.which("display"):
register(DisplayViewer)
class EogViewer(UnixViewer): class EogViewer(UnixViewer):
"""The GNOME Image Viewer ``eog`` command."""
def get_command_ex(self, file, **options): def get_command_ex(self, file, **options):
command = executable = "eog" command = executable = "eog"
return command, executable return command, executable
if shutil.which("eog"):
register(EogViewer)
class XVViewer(UnixViewer): class XVViewer(UnixViewer):
"""
The X Viewer ``xv`` command.
This viewer supports the ``title`` parameter.
"""
def get_command_ex(self, file, title=None, **options): def get_command_ex(self, file, title=None, **options):
# note: xv is pretty outdated. most modern systems have # note: xv is pretty outdated. most modern systems have
# imagemagick's display command instead. # imagemagick's display command instead.
@ -194,6 +218,12 @@ else:
command += " -name %s" % quote(title) command += " -name %s" % quote(title)
return command, executable return command, executable
if sys.platform not in ("win32", "darwin"): # unixoids
if shutil.which("display"):
register(DisplayViewer)
if shutil.which("eog"):
register(EogViewer)
if shutil.which("xv"): if shutil.which("xv"):
register(XVViewer) register(XVViewer)

View File

@ -118,8 +118,8 @@ class IptcImageFile(ImageFile.ImageFile):
# compression # compression
try: try:
compression = COMPRESSION[self.getint((3, 120))] compression = COMPRESSION[self.getint((3, 120))]
except KeyError: except KeyError as e:
raise OSError("Unknown IPTC image compression") raise OSError("Unknown IPTC image compression") from e
# tile # tile
if tag == (8, 10): if tag == (8, 10):

View File

@ -323,7 +323,8 @@ MARKER = {
def _accept(prefix): def _accept(prefix):
return prefix[0:1] == b"\377" # Magic number was taken from https://en.wikipedia.org/wiki/JPEG
return prefix[0:3] == b"\xFF\xD8\xFF"
## ##
@ -337,10 +338,11 @@ class JpegImageFile(ImageFile.ImageFile):
def _open(self): def _open(self):
s = self.fp.read(1) s = self.fp.read(3)
if i8(s) != 255: if not _accept(s):
raise SyntaxError("not a JPEG file") raise SyntaxError("not a JPEG file")
s = b"\xFF"
# Create attributes # Create attributes
self.bits = self.layers = 0 self.bits = self.layers = 0
@ -503,13 +505,13 @@ def _getmp(self):
file_contents.seek(info.next) file_contents.seek(info.next)
info.load(file_contents) info.load(file_contents)
mp = dict(info) mp = dict(info)
except Exception: except Exception as e:
raise SyntaxError("malformed MP Index (unreadable directory)") raise SyntaxError("malformed MP Index (unreadable directory)") from e
# it's an error not to have a number of images # it's an error not to have a number of images
try: try:
quant = mp[0xB001] quant = mp[0xB001]
except KeyError: except KeyError as e:
raise SyntaxError("malformed MP Index (no number of images)") raise SyntaxError("malformed MP Index (no number of images)") from e
# get MP entries # get MP entries
mpentries = [] mpentries = []
try: try:
@ -545,8 +547,8 @@ def _getmp(self):
mpentry["Attribute"] = mpentryattr mpentry["Attribute"] = mpentryattr
mpentries.append(mpentry) mpentries.append(mpentry)
mp[0xB002] = mpentries mp[0xB002] = mpentries
except KeyError: except KeyError as e:
raise SyntaxError("malformed MP Index (bad MP Entry)") raise SyntaxError("malformed MP Index (bad MP Entry)") from e
# Next we should try and parse the individual image unique ID list; # Next we should try and parse the individual image unique ID list;
# we don't because I've never seen this actually used in a real MPO # we don't because I've never seen this actually used in a real MPO
# file and so can't test it. # file and so can't test it.
@ -610,8 +612,8 @@ def _save(im, fp, filename):
try: try:
rawmode = RAWMODE[im.mode] rawmode = RAWMODE[im.mode]
except KeyError: except KeyError as e:
raise OSError("cannot write mode %s as JPEG" % im.mode) raise OSError("cannot write mode %s as JPEG" % im.mode) from e
info = im.encoderinfo info = im.encoderinfo
@ -663,8 +665,8 @@ def _save(im, fp, filename):
for line in qtables.splitlines() for line in qtables.splitlines()
for num in line.split("#", 1)[0].split() for num in line.split("#", 1)[0].split()
] ]
except ValueError: except ValueError as e:
raise ValueError("Invalid quantization table") raise ValueError("Invalid quantization table") from e
else: else:
qtables = [lines[s : s + 64] for s in range(0, len(lines), 64)] qtables = [lines[s : s + 64] for s in range(0, len(lines), 64)]
if isinstance(qtables, (tuple, list, dict)): if isinstance(qtables, (tuple, list, dict)):
@ -679,8 +681,8 @@ def _save(im, fp, filename):
if len(table) != 64: if len(table) != 64:
raise TypeError raise TypeError
table = array.array("B", table) table = array.array("B", table)
except TypeError: except TypeError as e:
raise ValueError("Invalid quantization table") raise ValueError("Invalid quantization table") from e
else: else:
qtables[idx] = list(table) qtables[idx] = list(table)
return qtables return qtables

View File

@ -1,9 +1,11 @@
""" """
JPEG quality settings equivalent to the Photoshop settings. JPEG quality settings equivalent to the Photoshop settings.
Can be used when saving JPEG files.
More presets can be added to the presets dict if needed. The following presets are available by default:
``web_low``, ``web_medium``, ``web_high``, ``web_very_high``, ``web_maximum``,
Can be use when saving JPEG file. ``low``, ``medium``, ``high``, ``maximum``.
More presets can be added to the :py:data:`presets` dict if needed.
To apply the preset, specify:: To apply the preset, specify::
@ -21,7 +23,6 @@ Example::
im.save("image_name.jpg", quality="web_high") im.save("image_name.jpg", quality="web_high")
Subsampling Subsampling
----------- -----------

View File

@ -46,8 +46,8 @@ class MicImageFile(TiffImagePlugin.TiffImageFile):
try: try:
self.ole = olefile.OleFileIO(self.fp) self.ole = olefile.OleFileIO(self.fp)
except OSError: except OSError as e:
raise SyntaxError("not an MIC file; invalid OLE file") raise SyntaxError("not an MIC file; invalid OLE file") from e
# find ACI subfiles with Image members (maybe not the # find ACI subfiles with Image members (maybe not the
# best way to identify MIC files, but what the... ;-) # best way to identify MIC files, but what the... ;-)
@ -77,8 +77,8 @@ class MicImageFile(TiffImagePlugin.TiffImageFile):
return return
try: try:
filename = self.images[frame] filename = self.images[frame]
except IndexError: except IndexError as e:
raise EOFError("no such frame") raise EOFError("no such frame") from e
self.fp = self.ole.openstream(filename) self.fp = self.ole.openstream(filename)

View File

@ -51,7 +51,7 @@ class MspImageFile(ImageFile.ImageFile):
# Header # Header
s = self.fp.read(32) s = self.fp.read(32)
if s[:4] not in [b"DanM", b"LinS"]: if not _accept(s):
raise SyntaxError("not an MSP file") raise SyntaxError("not an MSP file")
# Header checksum # Header checksum
@ -116,8 +116,8 @@ class MspDecoder(ImageFile.PyDecoder):
rowmap = struct.unpack_from( rowmap = struct.unpack_from(
"<%dH" % (self.state.ysize), self.fd.read(self.state.ysize * 2) "<%dH" % (self.state.ysize), self.fd.read(self.state.ysize * 2)
) )
except struct.error: except struct.error as e:
raise OSError("Truncated MSP file in row map") raise OSError("Truncated MSP file in row map") from e
for x, rowlen in enumerate(rowmap): for x, rowlen in enumerate(rowmap):
try: try:
@ -142,8 +142,8 @@ class MspDecoder(ImageFile.PyDecoder):
img.write(row[idx : idx + runcount]) img.write(row[idx : idx + runcount])
idx += runcount idx += runcount
except struct.error: except struct.error as e:
raise OSError("Corrupted MSP file in row %d" % x) raise OSError("Corrupted MSP file in row %d" % x) from e
self.set_as_raw(img.getvalue(), ("1", 0, 1)) self.set_as_raw(img.getvalue(), ("1", 0, 1))

View File

@ -15,11 +15,9 @@
from ._binary import o8 from ._binary import o8
##
# File handler for Teragon-style palette files.
class PaletteFile: class PaletteFile:
"""File handler for Teragon-style palette files."""
rawmode = "RGB" rawmode = "RGB"

View File

@ -48,11 +48,8 @@ def sz(s, o):
return s[o : s.index(b"\0", o)] return s[o : s.index(b"\0", o)]
##
# Font file plugin for the X11 PCF format.
class PcfFontFile(FontFile.FontFile): class PcfFontFile(FontFile.FontFile):
"""Font file plugin for the X11 PCF format."""
name = "name" name = "name"

View File

@ -131,8 +131,8 @@ def _save(im, fp, filename):
try: try:
version, bits, planes, rawmode = SAVE[im.mode] version, bits, planes, rawmode = SAVE[im.mode]
except KeyError: except KeyError as e:
raise ValueError("Cannot save %s images as PCX" % im.mode) raise ValueError("Cannot save %s images as PCX" % im.mode) from e
# bytes per plane # bytes per plane
stride = (im.size[0] * bits + 7) // 8 stride = (im.size[0] * bits + 7) // 8

View File

@ -43,7 +43,7 @@ class PixarImageFile(ImageFile.ImageFile):
# assuming a 4-byte magic label # assuming a 4-byte magic label
s = self.fp.read(4) s = self.fp.read(4)
if s != b"\200\350\000\000": if not _accept(s):
raise SyntaxError("not a PIXAR file") raise SyntaxError("not a PIXAR file")
# read rest of header # read rest of header

View File

@ -168,8 +168,10 @@ class ChunkStream:
crc2 = i32(self.fp.read(4)) crc2 = i32(self.fp.read(4))
if crc1 != crc2: if crc1 != crc2:
raise SyntaxError("broken PNG file (bad header checksum in %r)" % cid) raise SyntaxError("broken PNG file (bad header checksum in %r)" % cid)
except struct.error: except struct.error as e:
raise SyntaxError("broken PNG file (incomplete checksum in %r)" % cid) raise SyntaxError(
"broken PNG file (incomplete checksum in %r)" % cid
) from e
def crc_skip(self, cid, data): def crc_skip(self, cid, data):
"""Read checksum. Used if the C module is not present""" """Read checksum. Used if the C module is not present"""
@ -186,8 +188,8 @@ class ChunkStream:
while True: while True:
try: try:
cid, pos, length = self.read() cid, pos, length = self.read()
except struct.error: except struct.error as e:
raise OSError("truncated PNG file") raise OSError("truncated PNG file") from e
if cid == endchunk: if cid == endchunk:
break break
@ -633,7 +635,7 @@ class PngImageFile(ImageFile.ImageFile):
def _open(self): def _open(self):
if self.fp.read(8) != _MAGIC: if not _accept(self.fp.read(8)):
raise SyntaxError("not a PNG file") raise SyntaxError("not a PNG file")
self.__fp = self.fp self.__fp = self.fp
self.__frame = 0 self.__frame = 0
@ -737,9 +739,9 @@ class PngImageFile(ImageFile.ImageFile):
for f in range(self.__frame + 1, frame + 1): for f in range(self.__frame + 1, frame + 1):
try: try:
self._seek(f) self._seek(f)
except EOFError: except EOFError as e:
self.seek(last_frame) self.seek(last_frame)
raise EOFError("no more images in APNG file") raise EOFError("no more images in APNG file") from e
def _seek(self, frame, rewind=False): def _seek(self, frame, rewind=False):
if frame == 0: if frame == 0:
@ -1168,8 +1170,8 @@ def _save(im, fp, filename, chunk=putchunk, save_all=False):
# get the corresponding PNG mode # get the corresponding PNG mode
try: try:
rawmode, mode = _OUTMODES[mode] rawmode, mode = _OUTMODES[mode]
except KeyError: except KeyError as e:
raise OSError("cannot write mode %s as PNG" % mode) raise OSError("cannot write mode %s as PNG" % mode) from e
# #
# write minimal PNG file # write minimal PNG file

View File

@ -61,7 +61,7 @@ class PsdImageFile(ImageFile.ImageFile):
# header # header
s = read(26) s = read(26)
if s[:4] != b"8BPS" or i16(s[4:]) != 1: if not _accept(s) or i16(s[4:]) != 1:
raise SyntaxError("not a PSD file") raise SyntaxError("not a PSD file")
psd_bits = i16(s[22:]) psd_bits = i16(s[22:])
@ -144,8 +144,8 @@ class PsdImageFile(ImageFile.ImageFile):
self.frame = layer self.frame = layer
self.fp = self.__fp self.fp = self.__fp
return name, bbox return name, bbox
except IndexError: except IndexError as e:
raise EOFError("no such layer") raise EOFError("no such layer") from e
def tell(self): def tell(self):
# return layer number (0=image, 1..max=layers) # return layer number (0=image, 1..max=layers)

View File

@ -58,8 +58,7 @@ class SgiImageFile(ImageFile.ImageFile):
headlen = 512 headlen = 512
s = self.fp.read(headlen) s = self.fp.read(headlen)
# magic number : 474 if not _accept(s):
if i16(s) != 474:
raise ValueError("Not an SGI image file") raise ValueError("Not an SGI image file")
# compression : verbatim or RLE # compression : verbatim or RLE

View File

@ -111,8 +111,8 @@ class SpiderImageFile(ImageFile.ImageFile):
hdrlen = isSpiderHeader(t) hdrlen = isSpiderHeader(t)
if hdrlen == 0: if hdrlen == 0:
raise SyntaxError("not a valid Spider file") raise SyntaxError("not a valid Spider file")
except struct.error: except struct.error as e:
raise SyntaxError("not a valid Spider file") raise SyntaxError("not a valid Spider file") from e
h = (99,) + t # add 1 value : spider header index starts at 1 h = (99,) + t # add 1 value : spider header index starts at 1
iform = int(h[5]) iform = int(h[5])

View File

@ -53,7 +53,7 @@ class SunImageFile(ImageFile.ImageFile):
# HEAD # HEAD
s = self.fp.read(32) s = self.fp.read(32)
if i32(s) != 0x59A66A95: if not _accept(s):
raise SyntaxError("not an SUN raster file") raise SyntaxError("not an SUN raster file")
offset = 32 offset = 32

View File

@ -18,12 +18,10 @@ import io
from . import ContainerIO from . import ContainerIO
##
# A file object that provides read access to a given member of a TAR
# file.
class TarIO(ContainerIO.ContainerIO): class TarIO(ContainerIO.ContainerIO):
"""A file object that provides read access to a given member of a TAR file."""
def __init__(self, tarfile, file): def __init__(self, tarfile, file):
""" """
Create file object. Create file object.

View File

@ -167,8 +167,8 @@ def _save(im, fp, filename):
try: try:
rawmode, bits, colormaptype, imagetype = SAVE[im.mode] rawmode, bits, colormaptype, imagetype = SAVE[im.mode]
except KeyError: except KeyError as e:
raise OSError("cannot write mode %s as TGA" % im.mode) raise OSError("cannot write mode %s as TGA" % im.mode) from e
if "rle" in im.encoderinfo: if "rle" in im.encoderinfo:
rle = im.encoderinfo["rle"] rle = im.encoderinfo["rle"]

View File

@ -1117,8 +1117,8 @@ class TiffImageFile(ImageFile.ImageFile):
) )
try: try:
decoder.setimage(self.im, extents) decoder.setimage(self.im, extents)
except ValueError: except ValueError as e:
raise OSError("Couldn't set the image") raise OSError("Couldn't set the image") from e
close_self_fp = self._exclusive_fp and not self.is_animated close_self_fp = self._exclusive_fp and not self.is_animated
if hasattr(self.fp, "getvalue"): if hasattr(self.fp, "getvalue"):
@ -1231,9 +1231,9 @@ class TiffImageFile(ImageFile.ImageFile):
logger.debug("format key: {}".format(key)) logger.debug("format key: {}".format(key))
try: try:
self.mode, rawmode = OPEN_INFO[key] self.mode, rawmode = OPEN_INFO[key]
except KeyError: except KeyError as e:
logger.debug("- unsupported format") logger.debug("- unsupported format")
raise SyntaxError("unknown pixel mode") raise SyntaxError("unknown pixel mode") from e
logger.debug("- raw mode: {}".format(rawmode)) logger.debug("- raw mode: {}".format(rawmode))
logger.debug("- pil mode: {}".format(self.mode)) logger.debug("- pil mode: {}".format(self.mode))
@ -1400,8 +1400,8 @@ def _save(im, fp, filename):
try: try:
rawmode, prefix, photo, format, bits, extra = SAVE_INFO[im.mode] rawmode, prefix, photo, format, bits, extra = SAVE_INFO[im.mode]
except KeyError: except KeyError as e:
raise OSError("cannot write mode %s as TIFF" % im.mode) raise OSError("cannot write mode %s as TIFF" % im.mode) from e
ifd = ImageFileDirectory_v2(prefix=prefix) ifd = ImageFileDirectory_v2(prefix=prefix)
@ -1530,7 +1530,6 @@ def _save(im, fp, filename):
# BITSPERSAMPLE, etc), passing arrays with a different length will result in # BITSPERSAMPLE, etc), passing arrays with a different length will result in
# segfaults. Block these tags until we add extra validation. # segfaults. Block these tags until we add extra validation.
blocklist = [ blocklist = [
COLORMAP,
REFERENCEBLACKWHITE, REFERENCEBLACKWHITE,
SAMPLEFORMAT, SAMPLEFORMAT,
STRIPBYTECOUNTS, STRIPBYTECOUNTS,

View File

@ -483,7 +483,6 @@ LIBTIFF_CORE = {
65537, 65537,
} }
LIBTIFF_CORE.remove(320) # Array of short, crashes
LIBTIFF_CORE.remove(301) # Array of short, crashes LIBTIFF_CORE.remove(301) # Array of short, crashes
LIBTIFF_CORE.remove(532) # Array of long, crashes LIBTIFF_CORE.remove(532) # Array of long, crashes

View File

@ -12,13 +12,16 @@
# See the README file for information on usage and redistribution. # See the README file for information on usage and redistribution.
# #
# NOTE: This format cannot be automatically recognized, so the reader """
# is not registered for use with Image.open(). To open a WAL file, use This reader is based on the specification available from:
# the WalImageFile.open() function instead. https://www.flipcode.com/archives/Quake_2_BSP_File_Format.shtml
and has been tested with a few sample files found using google.
# This reader is based on the specification available from: .. note::
# https://www.flipcode.com/archives/Quake_2_BSP_File_Format.shtml This format cannot be automatically recognized, so the reader
# and has been tested with a few sample files found using google. is not registered for use with :py:func:`PIL.Image.open()`.
To open a WAL file, use the :py:func:`PIL.WalImageFile.open()` function instead.
"""
import builtins import builtins
@ -31,7 +34,7 @@ def open(filename):
Load texture from a Quake2 WAL texture file. Load texture from a Quake2 WAL texture file.
By default, a Quake2 standard palette is attached to the texture. By default, a Quake2 standard palette is attached to the texture.
To override the palette, use the <b>putpalette</b> method. To override the palette, use the :py:func:`PIL.Image.Image.putpalette()` method.
:param filename: WAL file name, or an opened file handle. :param filename: WAL file name, or an opened file handle.
:returns: An image instance. :returns: An image instance.

View File

@ -132,4 +132,8 @@ _plugins = [
class UnidentifiedImageError(OSError): class UnidentifiedImageError(OSError):
"""
Raised in :py:meth:`PIL.Image.open` if an image cannot be opened and identified.
"""
pass pass

View File

@ -11,6 +11,10 @@
# See the README file for information on usage and redistribution. # See the README file for information on usage and redistribution.
# #
"""Binary input/output support routines."""
from struct import pack, unpack_from from struct import pack, unpack_from

View File

@ -671,7 +671,7 @@ PyImaging_LibTiffEncoderNew(PyObject* self, PyObject* args)
// This list also exists in TiffTags.py // This list also exists in TiffTags.py
const int core_tags[] = { const int core_tags[] = {
256, 257, 258, 259, 262, 263, 266, 269, 274, 277, 278, 280, 281, 340, 256, 257, 258, 259, 262, 263, 266, 269, 274, 277, 278, 280, 281, 340,
341, 282, 283, 284, 286, 287, 296, 297, 321, 338, 32995, 32998, 32996, 341, 282, 283, 284, 286, 287, 296, 297, 320, 321, 338, 32995, 32998, 32996,
339, 32997, 330, 531, 530, 65537 339, 32997, 330, 531, 530, 65537
}; };
@ -801,7 +801,26 @@ PyImaging_LibTiffEncoderNew(PyObject* self, PyObject* args)
TRACE(("Setting from Tuple: %d \n", key_int)); TRACE(("Setting from Tuple: %d \n", key_int));
len = PyTuple_Size(value); len = PyTuple_Size(value);
if (type == TIFF_SHORT) { if (key_int == TIFFTAG_COLORMAP) {
int stride = 256;
if (len != 768) {
PyErr_SetString(PyExc_ValueError, "Requiring 768 items for for Colormap");
return NULL;
}
UINT16 *av;
/* malloc check ok, calloc checks for overflow */
av = calloc(len, sizeof(UINT16));
if (av) {
for (i=0;i<len;i++) {
av[i] = (UINT16)PyLong_AsLong(PyTuple_GetItem(value,i));
}
status = ImagingLibTiffSetField(&encoder->state, (ttag_t) key_int,
av,
av + stride,
av + stride * 2);
free(av);
}
} else if (type == TIFF_SHORT) {
UINT16 *av; UINT16 *av;
/* malloc check ok, calloc checks for overflow */ /* malloc check ok, calloc checks for overflow */
av = calloc(len, sizeof(UINT16)); av = calloc(len, sizeof(UINT16));

View File

@ -683,7 +683,7 @@ ImagingDrawWideLine(Imaging im, int x0, int y0, int x1, int y1,
return 0; return 0;
} }
big_hypotenuse = sqrt((double) (dx*dx + dy*dy)); big_hypotenuse = hypot(dx, dy);
small_hypotenuse = (width - 1) / 2.0; small_hypotenuse = (width - 1) / 2.0;
ratio_max = ROUND_UP(small_hypotenuse) / big_hypotenuse; ratio_max = ROUND_UP(small_hypotenuse) / big_hypotenuse;
ratio_min = ROUND_DOWN(small_hypotenuse) / big_hypotenuse; ratio_min = ROUND_DOWN(small_hypotenuse) / big_hypotenuse;

View File

@ -251,9 +251,9 @@ deps = {
"libs": [r"*.lib"], "libs": [r"*.lib"],
}, },
"harfbuzz": { "harfbuzz": {
"url": "https://github.com/harfbuzz/harfbuzz/archive/2.6.7.zip", "url": "https://github.com/harfbuzz/harfbuzz/archive/2.6.8.zip",
"filename": "harfbuzz-2.6.7.zip", "filename": "harfbuzz-2.6.8.zip",
"dir": "harfbuzz-2.6.7", "dir": "harfbuzz-2.6.8",
"build": [ "build": [
cmd_cmake("-DHB_HAVE_FREETYPE:BOOL=TRUE"), cmd_cmake("-DHB_HAVE_FREETYPE:BOOL=TRUE"),
cmd_nmake(target="clean"), cmd_nmake(target="clean"),