From e3dd4de1934c35144430de1a411d1df5bad84732 Mon Sep 17 00:00:00 2001 From: Yay295 Date: Sun, 15 Jan 2023 20:08:37 -0600 Subject: [PATCH 01/62] parametrize check_jpeg_leaks::test_qtables_leak() Co-authored-by: Andrew Murray <3112309+radarhere@users.noreply.github.com> --- Tests/check_jpeg_leaks.py | 71 +++++++++++++++++++-------------------- 1 file changed, 35 insertions(+), 36 deletions(-) diff --git a/Tests/check_jpeg_leaks.py b/Tests/check_jpeg_leaks.py index ab8d77719..940c0b00d 100644 --- a/Tests/check_jpeg_leaks.py +++ b/Tests/check_jpeg_leaks.py @@ -75,43 +75,42 @@ post-patch: """ -def test_qtables_leak(): +standard_l_qtable = ( + # fmt: off + 16, 11, 10, 16, 24, 40, 51, 61, + 12, 12, 14, 19, 26, 58, 60, 55, + 14, 13, 16, 24, 40, 57, 69, 56, + 14, 17, 22, 29, 51, 87, 80, 62, + 18, 22, 37, 56, 68, 109, 103, 77, + 24, 35, 55, 64, 81, 104, 113, 92, + 49, 64, 78, 87, 103, 121, 120, 101, + 72, 92, 95, 98, 112, 100, 103, 99, + # fmt: on +) + +standard_chrominance_qtable = ( + # fmt: off + 17, 18, 24, 47, 99, 99, 99, 99, + 18, 21, 26, 66, 99, 99, 99, 99, + 24, 26, 56, 99, 99, 99, 99, 99, + 47, 66, 99, 99, 99, 99, 99, 99, + 99, 99, 99, 99, 99, 99, 99, 99, + 99, 99, 99, 99, 99, 99, 99, 99, + 99, 99, 99, 99, 99, 99, 99, 99, + 99, 99, 99, 99, 99, 99, 99, 99, + # fmt: on +) + + +@pytest.mark.parametrize( + "qtables", + ( + (standard_l_qtable, standard_chrominance_qtable), + [standard_l_qtable, standard_chrominance_qtable], + ), +) +def test_qtables_leak(qtables): im = hopper("RGB") - - standard_l_qtable = [ - int(s) - for s in """ - 16 11 10 16 24 40 51 61 - 12 12 14 19 26 58 60 55 - 14 13 16 24 40 57 69 56 - 14 17 22 29 51 87 80 62 - 18 22 37 56 68 109 103 77 - 24 35 55 64 81 104 113 92 - 49 64 78 87 103 121 120 101 - 72 92 95 98 112 100 103 99 - """.split( - None - ) - ] - - standard_chrominance_qtable = [ - int(s) - for s in """ - 17 18 24 47 99 99 99 99 - 18 21 26 66 99 99 99 99 - 24 26 56 99 99 99 99 99 - 47 66 99 99 99 99 99 99 - 99 99 99 99 99 99 99 99 - 99 99 99 99 99 99 99 99 - 99 99 99 99 99 99 99 99 - 99 99 99 99 99 99 99 99 - """.split( - None - ) - ] - - qtables = [standard_l_qtable, standard_chrominance_qtable] - for _ in range(iterations): test_output = BytesIO() im.save(test_output, "JPEG", qtables=qtables) From e8307d74064d555524558a1dcb8828006ab3d65a Mon Sep 17 00:00:00 2001 From: Yay295 Date: Wed, 18 Jan 2023 23:03:13 -0600 Subject: [PATCH 02/62] more imagepath tests --- Tests/test_imagepath.py | 83 +++++++++++++++++++++++++++-------------- 1 file changed, 55 insertions(+), 28 deletions(-) diff --git a/Tests/test_imagepath.py b/Tests/test_imagepath.py index 861fb64f0..2b378d333 100644 --- a/Tests/test_imagepath.py +++ b/Tests/test_imagepath.py @@ -8,7 +8,6 @@ from PIL import Image, ImagePath def test_path(): - p = ImagePath.Path(list(range(10))) # sequence interface @@ -39,48 +38,76 @@ def test_path(): p.transform((1, 0, 1, 0, 1, 1)) assert list(p) == [(1.0, 2.0), (5.0, 6.0), (9.0, 10.0)] - # alternative constructors - p = ImagePath.Path([0, 1]) - assert list(p) == [(0.0, 1.0)] - p = ImagePath.Path([0.0, 1.0]) - assert list(p) == [(0.0, 1.0)] - p = ImagePath.Path([0, 1]) - assert list(p) == [(0.0, 1.0)] - p = ImagePath.Path([(0, 1)]) - assert list(p) == [(0.0, 1.0)] - p = ImagePath.Path(p) - assert list(p) == [(0.0, 1.0)] - p = ImagePath.Path(p.tolist(0)) - assert list(p) == [(0.0, 1.0)] - p = ImagePath.Path(p.tolist(1)) - assert list(p) == [(0.0, 1.0)] - p = ImagePath.Path(array.array("f", [0, 1])) - assert list(p) == [(0.0, 1.0)] - arr = array.array("f", [0, 1]) - p = ImagePath.Path(arr.tobytes()) +@pytest.mark.parametrize( + "coords", + ( + (0, 1), + [0, 1], + (0.0, 1.0), + [0.0, 1.0], + ((0, 1),), + [(0, 1)], + ((0.0, 1.0),), + [(0.0, 1.0)], + array.array("f", [0, 1]), + array.array("f", [0.0, 1.0]), + ImagePath.Path((0, 1)), + ), +) +def test_path_constructors(coords): + # Arrange / Act + p = ImagePath.Path(coords) + + # Assert assert list(p) == [(0.0, 1.0)] -def test_invalid_coords(): +def test_path_constructor_text(): # Arrange - coords = ["a", "b"] + arr = array.array("f", (0, 1)) - # Act / Assert + # Act + p = ImagePath.Path(arr.tobytes()) + + # Assert + assert list(p) == [(0.0, 1.0)] + + +@pytest.mark.parametrize( + "coords", + ( + ("a", "b"), + ([0, 1],), + [[0, 1]], + ([0.0, 1.0],), + [[0.0, 1.0]], + ), +) +def test_invalid_path_constructors(coords): + # Act with pytest.raises(ValueError) as e: ImagePath.Path(coords) + # Assert assert str(e.value) == "incorrect coordinate type" -def test_path_odd_number_of_coordinates(): - # Arrange - coords = [0] - - # Act / Assert +@pytest.mark.parametrize( + "coords", + ( + (0,), + [0], + (0, 1, 2), + [0, 1, 2], + ), +) +def test_path_odd_number_of_coordinates(coords): + # Act with pytest.raises(ValueError) as e: ImagePath.Path(coords) + # Assert assert str(e.value) == "wrong number of coordinates" From 4e8de9ac9a1e14593e17a2cd1ff82c45ed0900cd Mon Sep 17 00:00:00 2001 From: Yay295 Date: Wed, 25 Jan 2023 08:13:40 -0600 Subject: [PATCH 03/62] add path-from-bytes test Also `array.array("f", [0, 1]) == array.array("f", [0.0, 1.0])` so we didn't need both of them. --- Tests/test_imagepath.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Tests/test_imagepath.py b/Tests/test_imagepath.py index 2b378d333..7a517b6f6 100644 --- a/Tests/test_imagepath.py +++ b/Tests/test_imagepath.py @@ -51,7 +51,7 @@ def test_path(): ((0.0, 1.0),), [(0.0, 1.0)], array.array("f", [0, 1]), - array.array("f", [0.0, 1.0]), + array.array("f", [0, 1]).tobytes(), ImagePath.Path((0, 1)), ), ) From b05bc346045f4d243cdaecf85ff61567e5ca377c Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Sat, 31 Dec 2022 15:42:38 +1100 Subject: [PATCH 04/62] Test lists and tuples --- Tests/check_jpeg_leaks.py | 12 +-- Tests/test_imagedraw.py | 177 ++++++++++++++++++++++---------------- Tests/test_imagedraw2.py | 30 ++++--- 3 files changed, 128 insertions(+), 91 deletions(-) diff --git a/Tests/check_jpeg_leaks.py b/Tests/check_jpeg_leaks.py index ab8d77719..5d95ca29c 100644 --- a/Tests/check_jpeg_leaks.py +++ b/Tests/check_jpeg_leaks.py @@ -110,11 +110,13 @@ def test_qtables_leak(): ) ] - qtables = [standard_l_qtable, standard_chrominance_qtable] - - for _ in range(iterations): - test_output = BytesIO() - im.save(test_output, "JPEG", qtables=qtables) + for qtables in ( + (standard_l_qtable, standard_chrominance_qtable), + [standard_l_qtable, standard_chrominance_qtable], + ): + for _ in range(iterations): + test_output = BytesIO() + im.save(test_output, "JPEG", qtables=qtables) def test_exif_leak(): diff --git a/Tests/test_imagedraw.py b/Tests/test_imagedraw.py index d4723c924..f6f27d32d 100644 --- a/Tests/test_imagedraw.py +++ b/Tests/test_imagedraw.py @@ -27,15 +27,21 @@ X1 = int(X0 * 3) Y0 = int(H / 4) Y1 = int(X0 * 3) -# Two kinds of bounding box -BBOX1 = [(X0, Y0), (X1, Y1)] -BBOX2 = [X0, Y0, X1, Y1] +# Bounding boxes +BBOX = (((X0, Y0), (X1, Y1)), [(X0, Y0), (X1, Y1)], (X0, Y0, X1, Y1), [X0, Y0, X1, Y1]) -# Two kinds of coordinate sequences -POINTS1 = [(10, 10), (20, 40), (30, 30)] -POINTS2 = [10, 10, 20, 40, 30, 30] +# Coordinate sequences +POINTS = ( + ((10, 10), (20, 40), (30, 30)), + [(10, 10), (20, 40), (30, 30)], + (10, 10, 20, 40, 30, 30), + [10, 10, 20, 40, 30, 30], +) -KITE_POINTS = [(10, 50), (70, 10), (90, 50), (70, 90), (10, 50)] +KITE_POINTS = ( + ((10, 50), (70, 10), (90, 50), (70, 90), (10, 50)), + [(10, 50), (70, 10), (90, 50), (70, 90), (10, 50)], +) def test_sanity(): @@ -63,7 +69,7 @@ def test_mode_mismatch(): ImageDraw.ImageDraw(im, mode="L") -@pytest.mark.parametrize("bbox", (BBOX1, BBOX2)) +@pytest.mark.parametrize("bbox", BBOX) @pytest.mark.parametrize("start, end", ((0, 180), (0.5, 180.4))) def test_arc(bbox, start, end): # Arrange @@ -77,7 +83,8 @@ def test_arc(bbox, start, end): assert_image_similar_tofile(im, "Tests/images/imagedraw_arc.png", 1) -def test_arc_end_le_start(): +@pytest.mark.parametrize("bbox", BBOX) +def test_arc_end_le_start(bbox): # Arrange im = Image.new("RGB", (W, H)) draw = ImageDraw.Draw(im) @@ -85,13 +92,14 @@ def test_arc_end_le_start(): end = 0 # Act - draw.arc(BBOX1, start=start, end=end) + draw.arc(bbox, start=start, end=end) # Assert assert_image_equal_tofile(im, "Tests/images/imagedraw_arc_end_le_start.png") -def test_arc_no_loops(): +@pytest.mark.parametrize("bbox", BBOX) +def test_arc_no_loops(bbox): # No need to go in loops # Arrange im = Image.new("RGB", (W, H)) @@ -100,57 +108,61 @@ def test_arc_no_loops(): end = 370 # Act - draw.arc(BBOX1, start=start, end=end) + draw.arc(bbox, start=start, end=end) # Assert assert_image_similar_tofile(im, "Tests/images/imagedraw_arc_no_loops.png", 1) -def test_arc_width(): +@pytest.mark.parametrize("bbox", BBOX) +def test_arc_width(bbox): # Arrange im = Image.new("RGB", (W, H)) draw = ImageDraw.Draw(im) # Act - draw.arc(BBOX1, 10, 260, width=5) + draw.arc(bbox, 10, 260, width=5) # Assert assert_image_similar_tofile(im, "Tests/images/imagedraw_arc_width.png", 1) -def test_arc_width_pieslice_large(): +@pytest.mark.parametrize("bbox", BBOX) +def test_arc_width_pieslice_large(bbox): # Tests an arc with a large enough width that it is a pieslice # Arrange im = Image.new("RGB", (W, H)) draw = ImageDraw.Draw(im) # Act - draw.arc(BBOX1, 10, 260, fill="yellow", width=100) + draw.arc(bbox, 10, 260, fill="yellow", width=100) # Assert assert_image_similar_tofile(im, "Tests/images/imagedraw_arc_width_pieslice.png", 1) -def test_arc_width_fill(): +@pytest.mark.parametrize("bbox", BBOX) +def test_arc_width_fill(bbox): # Arrange im = Image.new("RGB", (W, H)) draw = ImageDraw.Draw(im) # Act - draw.arc(BBOX1, 10, 260, fill="yellow", width=5) + draw.arc(bbox, 10, 260, fill="yellow", width=5) # Assert assert_image_similar_tofile(im, "Tests/images/imagedraw_arc_width_fill.png", 1) -def test_arc_width_non_whole_angle(): +@pytest.mark.parametrize("bbox", BBOX) +def test_arc_width_non_whole_angle(bbox): # Arrange im = Image.new("RGB", (W, H)) draw = ImageDraw.Draw(im) expected = "Tests/images/imagedraw_arc_width_non_whole_angle.png" # Act - draw.arc(BBOX1, 10, 259.5, width=5) + draw.arc(bbox, 10, 259.5, width=5) # Assert assert_image_similar_tofile(im, expected, 1) @@ -184,7 +196,7 @@ def test_bitmap(): @pytest.mark.parametrize("mode", ("RGB", "L")) -@pytest.mark.parametrize("bbox", (BBOX1, BBOX2)) +@pytest.mark.parametrize("bbox", BBOX) def test_chord(mode, bbox): # Arrange im = Image.new(mode, (W, H)) @@ -198,37 +210,40 @@ def test_chord(mode, bbox): assert_image_similar_tofile(im, expected, 1) -def test_chord_width(): +@pytest.mark.parametrize("bbox", BBOX) +def test_chord_width(bbox): # Arrange im = Image.new("RGB", (W, H)) draw = ImageDraw.Draw(im) # Act - draw.chord(BBOX1, 10, 260, outline="yellow", width=5) + draw.chord(bbox, 10, 260, outline="yellow", width=5) # Assert assert_image_similar_tofile(im, "Tests/images/imagedraw_chord_width.png", 1) -def test_chord_width_fill(): +@pytest.mark.parametrize("bbox", BBOX) +def test_chord_width_fill(bbox): # Arrange im = Image.new("RGB", (W, H)) draw = ImageDraw.Draw(im) # Act - draw.chord(BBOX1, 10, 260, fill="red", outline="yellow", width=5) + draw.chord(bbox, 10, 260, fill="red", outline="yellow", width=5) # Assert assert_image_similar_tofile(im, "Tests/images/imagedraw_chord_width_fill.png", 1) -def test_chord_zero_width(): +@pytest.mark.parametrize("bbox", BBOX) +def test_chord_zero_width(bbox): # Arrange im = Image.new("RGB", (W, H)) draw = ImageDraw.Draw(im) # Act - draw.chord(BBOX1, 10, 260, fill="red", outline="yellow", width=0) + draw.chord(bbox, 10, 260, fill="red", outline="yellow", width=0) # Assert assert_image_equal_tofile(im, "Tests/images/imagedraw_chord_zero_width.png") @@ -247,7 +262,7 @@ def test_chord_too_fat(): @pytest.mark.parametrize("mode", ("RGB", "L")) -@pytest.mark.parametrize("bbox", (BBOX1, BBOX2)) +@pytest.mark.parametrize("bbox", BBOX) def test_ellipse(mode, bbox): # Arrange im = Image.new(mode, (W, H)) @@ -261,13 +276,14 @@ def test_ellipse(mode, bbox): assert_image_similar_tofile(im, expected, 1) -def test_ellipse_translucent(): +@pytest.mark.parametrize("bbox", BBOX) +def test_ellipse_translucent(bbox): # Arrange im = Image.new("RGB", (W, H)) draw = ImageDraw.Draw(im, "RGBA") # Act - draw.ellipse(BBOX1, fill=(0, 255, 0, 127)) + draw.ellipse(bbox, fill=(0, 255, 0, 127)) # Assert expected = "Tests/images/imagedraw_ellipse_translucent.png" @@ -297,13 +313,14 @@ def test_ellipse_symmetric(): assert_image_equal(im, im.transpose(Image.Transpose.FLIP_LEFT_RIGHT)) -def test_ellipse_width(): +@pytest.mark.parametrize("bbox", BBOX) +def test_ellipse_width(bbox): # Arrange im = Image.new("RGB", (W, H)) draw = ImageDraw.Draw(im) # Act - draw.ellipse(BBOX1, outline="blue", width=5) + draw.ellipse(bbox, outline="blue", width=5) # Assert assert_image_similar_tofile(im, "Tests/images/imagedraw_ellipse_width.png", 1) @@ -321,25 +338,27 @@ def test_ellipse_width_large(): assert_image_similar_tofile(im, "Tests/images/imagedraw_ellipse_width_large.png", 1) -def test_ellipse_width_fill(): +@pytest.mark.parametrize("bbox", BBOX) +def test_ellipse_width_fill(bbox): # Arrange im = Image.new("RGB", (W, H)) draw = ImageDraw.Draw(im) # Act - draw.ellipse(BBOX1, fill="green", outline="blue", width=5) + draw.ellipse(bbox, fill="green", outline="blue", width=5) # Assert assert_image_similar_tofile(im, "Tests/images/imagedraw_ellipse_width_fill.png", 1) -def test_ellipse_zero_width(): +@pytest.mark.parametrize("bbox", BBOX) +def test_ellipse_zero_width(bbox): # Arrange im = Image.new("RGB", (W, H)) draw = ImageDraw.Draw(im) # Act - draw.ellipse(BBOX1, fill="green", outline="blue", width=0) + draw.ellipse(bbox, fill="green", outline="blue", width=0) # Assert assert_image_equal_tofile(im, "Tests/images/imagedraw_ellipse_zero_width.png") @@ -380,7 +399,7 @@ def test_ellipse_various_sizes_filled(): ) -@pytest.mark.parametrize("points", (POINTS1, POINTS2)) +@pytest.mark.parametrize("points", POINTS) def test_line(points): # Arrange im = Image.new("RGB", (W, H)) @@ -452,7 +471,7 @@ def test_transform(): assert_image_equal(im, expected) -@pytest.mark.parametrize("bbox", (BBOX1, BBOX2)) +@pytest.mark.parametrize("bbox", BBOX) @pytest.mark.parametrize("start, end", ((-92, 46), (-92.2, 46.2))) def test_pieslice(bbox, start, end): # Arrange @@ -466,38 +485,41 @@ def test_pieslice(bbox, start, end): assert_image_similar_tofile(im, "Tests/images/imagedraw_pieslice.png", 1) -def test_pieslice_width(): +@pytest.mark.parametrize("bbox", BBOX) +def test_pieslice_width(bbox): # Arrange im = Image.new("RGB", (W, H)) draw = ImageDraw.Draw(im) # Act - draw.pieslice(BBOX1, 10, 260, outline="blue", width=5) + draw.pieslice(bbox, 10, 260, outline="blue", width=5) # Assert assert_image_similar_tofile(im, "Tests/images/imagedraw_pieslice_width.png", 1) -def test_pieslice_width_fill(): +@pytest.mark.parametrize("bbox", BBOX) +def test_pieslice_width_fill(bbox): # Arrange im = Image.new("RGB", (W, H)) draw = ImageDraw.Draw(im) expected = "Tests/images/imagedraw_pieslice_width_fill.png" # Act - draw.pieslice(BBOX1, 10, 260, fill="white", outline="blue", width=5) + draw.pieslice(bbox, 10, 260, fill="white", outline="blue", width=5) # Assert assert_image_similar_tofile(im, expected, 1) -def test_pieslice_zero_width(): +@pytest.mark.parametrize("bbox", BBOX) +def test_pieslice_zero_width(bbox): # Arrange im = Image.new("RGB", (W, H)) draw = ImageDraw.Draw(im) # Act - draw.pieslice(BBOX1, 10, 260, fill="white", outline="blue", width=0) + draw.pieslice(bbox, 10, 260, fill="white", outline="blue", width=0) # Assert assert_image_equal_tofile(im, "Tests/images/imagedraw_pieslice_zero_width.png") @@ -545,7 +567,7 @@ def test_pieslice_no_spikes(): assert_image_equal(im, im_pre_erase) -@pytest.mark.parametrize("points", (POINTS1, POINTS2)) +@pytest.mark.parametrize("points", POINTS) def test_point(points): # Arrange im = Image.new("RGB", (W, H)) @@ -558,7 +580,7 @@ def test_point(points): assert_image_equal_tofile(im, "Tests/images/imagedraw_point.png") -@pytest.mark.parametrize("points", (POINTS1, POINTS2)) +@pytest.mark.parametrize("points", POINTS) def test_polygon(points): # Arrange im = Image.new("RGB", (W, H)) @@ -572,7 +594,8 @@ def test_polygon(points): @pytest.mark.parametrize("mode", ("RGB", "L")) -def test_polygon_kite(mode): +@pytest.mark.parametrize("kite_points", KITE_POINTS) +def test_polygon_kite(mode, kite_points): # Test drawing lines of different gradients (dx>dy, dy>dx) and # vertical (dx==0) and horizontal (dy==0) lines # Arrange @@ -581,7 +604,7 @@ def test_polygon_kite(mode): expected = f"Tests/images/imagedraw_polygon_kite_{mode}.png" # Act - draw.polygon(KITE_POINTS, fill="blue", outline="yellow") + draw.polygon(kite_points, fill="blue", outline="yellow") # Assert assert_image_equal_tofile(im, expected) @@ -628,7 +651,7 @@ def test_polygon_translucent(): assert_image_equal_tofile(im, expected) -@pytest.mark.parametrize("bbox", (BBOX1, BBOX2)) +@pytest.mark.parametrize("bbox", BBOX) def test_rectangle(bbox): # Arrange im = Image.new("RGB", (W, H)) @@ -655,63 +678,68 @@ def test_big_rectangle(): assert_image_similar_tofile(im, "Tests/images/imagedraw_big_rectangle.png", 1) -def test_rectangle_width(): +@pytest.mark.parametrize("bbox", BBOX) +def test_rectangle_width(bbox): # Arrange im = Image.new("RGB", (W, H)) draw = ImageDraw.Draw(im) expected = "Tests/images/imagedraw_rectangle_width.png" # Act - draw.rectangle(BBOX1, outline="green", width=5) + draw.rectangle(bbox, outline="green", width=5) # Assert assert_image_equal_tofile(im, expected) -def test_rectangle_width_fill(): +@pytest.mark.parametrize("bbox", BBOX) +def test_rectangle_width_fill(bbox): # Arrange im = Image.new("RGB", (W, H)) draw = ImageDraw.Draw(im) expected = "Tests/images/imagedraw_rectangle_width_fill.png" # Act - draw.rectangle(BBOX1, fill="blue", outline="green", width=5) + draw.rectangle(bbox, fill="blue", outline="green", width=5) # Assert assert_image_equal_tofile(im, expected) -def test_rectangle_zero_width(): +@pytest.mark.parametrize("bbox", BBOX) +def test_rectangle_zero_width(bbox): # Arrange im = Image.new("RGB", (W, H)) draw = ImageDraw.Draw(im) # Act - draw.rectangle(BBOX1, fill="blue", outline="green", width=0) + draw.rectangle(bbox, fill="blue", outline="green", width=0) # Assert assert_image_equal_tofile(im, "Tests/images/imagedraw_rectangle_zero_width.png") -def test_rectangle_I16(): +@pytest.mark.parametrize("bbox", BBOX) +def test_rectangle_I16(bbox): # Arrange im = Image.new("I;16", (W, H)) draw = ImageDraw.Draw(im) # Act - draw.rectangle(BBOX1, fill="black", outline="green") + draw.rectangle(bbox, fill="black", outline="green") # Assert assert_image_equal_tofile(im.convert("I"), "Tests/images/imagedraw_rectangle_I.png") -def test_rectangle_translucent_outline(): +@pytest.mark.parametrize("bbox", BBOX) +def test_rectangle_translucent_outline(bbox): # Arrange im = Image.new("RGB", (W, H)) draw = ImageDraw.Draw(im, "RGBA") # Act - draw.rectangle(BBOX1, fill="black", outline=(0, 255, 0, 127), width=5) + draw.rectangle(bbox, fill="black", outline=(0, 255, 0, 127), width=5) # Assert assert_image_equal_tofile( @@ -758,13 +786,14 @@ def test_rounded_rectangle_non_integer_radius(xy, radius, type): ) -def test_rounded_rectangle_zero_radius(): +@pytest.mark.parametrize("bbox", BBOX) +def test_rounded_rectangle_zero_radius(bbox): # Arrange im = Image.new("RGB", (W, H)) draw = ImageDraw.Draw(im) # Act - draw.rounded_rectangle(BBOX1, 0, fill="blue", outline="green", width=5) + draw.rounded_rectangle(bbox, 0, fill="blue", outline="green", width=5) # Assert assert_image_equal_tofile(im, "Tests/images/imagedraw_rectangle_width_fill.png") @@ -794,14 +823,15 @@ def test_rounded_rectangle_translucent(xy, suffix): ) -def test_floodfill(): +@pytest.mark.parametrize("bbox", BBOX) +def test_floodfill(bbox): red = ImageColor.getrgb("red") for mode, value in [("L", 1), ("RGBA", (255, 0, 0, 0)), ("RGB", red)]: # Arrange im = Image.new(mode, (W, H)) draw = ImageDraw.Draw(im) - draw.rectangle(BBOX2, outline="yellow", fill="green") + draw.rectangle(bbox, outline="yellow", fill="green") centre_point = (int(W / 2), int(H / 2)) # Act @@ -826,13 +856,14 @@ def test_floodfill(): assert_image_equal(im, Image.new("RGB", (1, 1), red)) -def test_floodfill_border(): +@pytest.mark.parametrize("bbox", BBOX) +def test_floodfill_border(bbox): # floodfill() is experimental # Arrange im = Image.new("RGB", (W, H)) draw = ImageDraw.Draw(im) - draw.rectangle(BBOX2, outline="yellow", fill="green") + draw.rectangle(bbox, outline="yellow", fill="green") centre_point = (int(W / 2), int(H / 2)) # Act @@ -847,13 +878,14 @@ def test_floodfill_border(): assert_image_equal_tofile(im, "Tests/images/imagedraw_floodfill2.png") -def test_floodfill_thresh(): +@pytest.mark.parametrize("bbox", BBOX) +def test_floodfill_thresh(bbox): # floodfill() is experimental # Arrange im = Image.new("RGB", (W, H)) draw = ImageDraw.Draw(im) - draw.rectangle(BBOX2, outline="darkgreen", fill="green") + draw.rectangle(bbox, outline="darkgreen", fill="green") centre_point = (int(W / 2), int(H / 2)) # Act @@ -1291,7 +1323,8 @@ def test_setting_default_font(): assert isinstance(draw.getfont(), ImageFont.ImageFont) -def test_same_color_outline(): +@pytest.mark.parametrize("bbox", BBOX) +def test_same_color_outline(bbox): # Prepare shape x0, y0 = 5, 5 x1, y1 = 5, 50 @@ -1307,12 +1340,12 @@ def test_same_color_outline(): for mode in ["RGB", "L"]: for fill, outline in [["red", None], ["red", "red"], ["red", "#f00"]]: for operation, args in { - "chord": [BBOX1, 0, 180], - "ellipse": [BBOX1], + "chord": [bbox, 0, 180], + "ellipse": [bbox], "shape": [s], - "pieslice": [BBOX1, -90, 45], + "pieslice": [bbox, -90, 45], "polygon": [[(18, 30), (85, 30), (60, 72)]], - "rectangle": [BBOX1], + "rectangle": [bbox], }.items(): # Arrange im = Image.new(mode, (W, H)) diff --git a/Tests/test_imagedraw2.py b/Tests/test_imagedraw2.py index 6fc829f1a..143341b0a 100644 --- a/Tests/test_imagedraw2.py +++ b/Tests/test_imagedraw2.py @@ -27,15 +27,16 @@ X1 = int(X0 * 3) Y0 = int(H / 4) Y1 = int(X0 * 3) -# Two kinds of bounding box -BBOX1 = [(X0, Y0), (X1, Y1)] -BBOX2 = [X0, Y0, X1, Y1] +# Bounding boxes +BBOX = (((X0, Y0), (X1, Y1)), [(X0, Y0), (X1, Y1)], (X0, Y0, X1, Y1), [X0, Y0, X1, Y1]) -# Two kinds of coordinate sequences -POINTS1 = [(10, 10), (20, 40), (30, 30)] -POINTS2 = [10, 10, 20, 40, 30, 30] - -KITE_POINTS = [(10, 50), (70, 10), (90, 50), (70, 90), (10, 50)] +# Coordinate sequences +POINTS = ( + ((10, 10), (20, 40), (30, 30)), + [(10, 10), (20, 40), (30, 30)], + (10, 10, 20, 40, 30, 30), + [10, 10, 20, 40, 30, 30], +) FONT_PATH = "Tests/fonts/FreeMono.ttf" @@ -52,7 +53,7 @@ def test_sanity(): draw.line(list(range(10)), pen) -@pytest.mark.parametrize("bbox", (BBOX1, BBOX2)) +@pytest.mark.parametrize("bbox", BBOX) def test_ellipse(bbox): # Arrange im = Image.new("RGB", (W, H)) @@ -80,7 +81,7 @@ def test_ellipse_edge(): assert_image_similar_tofile(im, "Tests/images/imagedraw_ellipse_edge.png", 1) -@pytest.mark.parametrize("points", (POINTS1, POINTS2)) +@pytest.mark.parametrize("points", POINTS) def test_line(points): # Arrange im = Image.new("RGB", (W, H)) @@ -94,7 +95,8 @@ def test_line(points): assert_image_equal_tofile(im, "Tests/images/imagedraw_line.png") -def test_line_pen_as_brush(): +@pytest.mark.parametrize("points", POINTS) +def test_line_pen_as_brush(points): # Arrange im = Image.new("RGB", (W, H)) draw = ImageDraw2.Draw(im) @@ -103,13 +105,13 @@ def test_line_pen_as_brush(): # Act # Pass in the pen as the brush parameter - draw.line(POINTS1, pen, brush) + draw.line(points, pen, brush) # Assert assert_image_equal_tofile(im, "Tests/images/imagedraw_line.png") -@pytest.mark.parametrize("points", (POINTS1, POINTS2)) +@pytest.mark.parametrize("points", POINTS) def test_polygon(points): # Arrange im = Image.new("RGB", (W, H)) @@ -124,7 +126,7 @@ def test_polygon(points): assert_image_equal_tofile(im, "Tests/images/imagedraw_polygon.png") -@pytest.mark.parametrize("bbox", (BBOX1, BBOX2)) +@pytest.mark.parametrize("bbox", BBOX) def test_rectangle(bbox): # Arrange im = Image.new("RGB", (W, H)) From ca2bf046d35ec41251716cac152fc9209964a359 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Wed, 5 Apr 2023 09:57:16 +1000 Subject: [PATCH 05/62] Use "/sbin/ldconfig" if ldconfig is not found --- setup.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/setup.py b/setup.py index 07d6c66d6..f9670d4c0 100755 --- a/setup.py +++ b/setup.py @@ -10,6 +10,7 @@ import os import re +import shutil import struct import subprocess import sys @@ -150,6 +151,7 @@ def _dbg(s, tp=None): def _find_library_dirs_ldconfig(): # Based on ctypes.util from Python 2 + ldconfig = "ldconfig" if shutil.which("ldconfig") else "/sbin/ldconfig" if sys.platform.startswith("linux") or sys.platform.startswith("gnu"): if struct.calcsize("l") == 4: machine = os.uname()[4] + "-32" @@ -166,14 +168,14 @@ def _find_library_dirs_ldconfig(): # Assuming GLIBC's ldconfig (with option -p) # Alpine Linux uses musl that can't print cache - args = ["ldconfig", "-p"] + args = [ldconfig, "-p"] expr = rf".*\({abi_type}.*\) => (.*)" env = dict(os.environ) env["LC_ALL"] = "C" env["LANG"] = "C" elif sys.platform.startswith("freebsd"): - args = ["ldconfig", "-r"] + args = [ldconfig, "-r"] expr = r".* => (.*)" env = {} From 4e6f1f1ac60cb9e88c0605739468693e0e17e4e9 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Mon, 17 Apr 2023 19:08:59 +1000 Subject: [PATCH 06/62] Removed Fedora 36 --- .github/workflows/test-docker.yml | 1 - docs/installation.rst | 2 -- 2 files changed, 3 deletions(-) diff --git a/.github/workflows/test-docker.yml b/.github/workflows/test-docker.yml index 14592ea1d..cbe6c2ca3 100644 --- a/.github/workflows/test-docker.yml +++ b/.github/workflows/test-docker.yml @@ -39,7 +39,6 @@ jobs: centos-stream-8-amd64, centos-stream-9-amd64, debian-11-bullseye-x86, - fedora-36-amd64, fedora-37-amd64, gentoo, ubuntu-18.04-bionic-amd64, diff --git a/docs/installation.rst b/docs/installation.rst index 7088657f9..0ac5914da 100644 --- a/docs/installation.rst +++ b/docs/installation.rst @@ -448,8 +448,6 @@ These platforms are built and tested for every change. +----------------------------------+----------------------------+---------------------+ | Debian 11 Bullseye | 3.9 | x86 | +----------------------------------+----------------------------+---------------------+ -| Fedora 36 | 3.10 | x86-64 | -+----------------------------------+----------------------------+---------------------+ | Fedora 37 | 3.11 | x86-64 | +----------------------------------+----------------------------+---------------------+ | Gentoo | 3.9 | x86-64 | From 6ffa189d0156f52df422cd99fa3d28ebb0432107 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Mon, 17 Apr 2023 09:16:01 +0000 Subject: [PATCH 07/62] Update cygwin/cygwin-install-action action to v4 --- .github/workflows/test-cygwin.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/test-cygwin.yml b/.github/workflows/test-cygwin.yml index 2b597a945..9a1e46705 100644 --- a/.github/workflows/test-cygwin.yml +++ b/.github/workflows/test-cygwin.yml @@ -39,7 +39,7 @@ jobs: uses: actions/checkout@v3 - name: Install Cygwin - uses: cygwin/cygwin-install-action@v3 + uses: cygwin/cygwin-install-action@v4 with: platform: x86_64 packages: > From 75c4acf02c6128f1fe03a14c202a6bcf57eaa31f Mon Sep 17 00:00:00 2001 From: Hugo van Kemenade Date: Wed, 19 Apr 2023 06:46:58 -0600 Subject: [PATCH 08/62] Fix typo --- docs/reference/Image.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/reference/Image.rst b/docs/reference/Image.rst index 35a4c2110..41d3b8fce 100644 --- a/docs/reference/Image.rst +++ b/docs/reference/Image.rst @@ -439,7 +439,7 @@ Used to specify the dithering method to use for the Palettes ^^^^^^^^ -Used to specify the pallete to use for the :meth:`~Image.convert` method. +Used to specify the palette to use for the :meth:`~Image.convert` method. .. autoclass:: Palette :members: From d2256338b82730fef647a3cd8b65bc9040a7d73e Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Thu, 20 Apr 2023 23:15:20 +1000 Subject: [PATCH 09/62] Use later value for duplicate xref entries --- Tests/images/duplicate_xref_entry.pdf | Bin 0 -> 3326 bytes Tests/test_pdfparser.py | 6 ++++++ src/PIL/PdfParser.py | 9 +++------ 3 files changed, 9 insertions(+), 6 deletions(-) create mode 100644 Tests/images/duplicate_xref_entry.pdf diff --git a/Tests/images/duplicate_xref_entry.pdf b/Tests/images/duplicate_xref_entry.pdf new file mode 100644 index 0000000000000000000000000000000000000000..f57a57d61c6ad0649448af316f689db4e7f1322e GIT binary patch literal 3326 zcmeH}S#Z-v7{?{cr{WAa2{dFvvpL)(9cpQ1`C?K!#KsU>hQvt=rPBu=n?xDMN@V3? zrVr4YH+r{EK<~cMmR?V#rRN(xfxeX9?GyB-54hh-K4N>ADRh|WbXZv$>)UU4zfY_G z_mg)x7QYVrWZsL?8cFITgHlUqSjlG91%yQ(Ju+loMBs-qnleu`UPdBPQ&R&2yfC&j zLy!dCA+!3)F536e(v=uhw)HjrEf+<1Cht5G`bra+-sVeSG25c>$rMtTYEd|@%5sv zb=~dleWCt!B9>*jqc?JWSQ_y8WrcSs#sUif`UBv~_gs z=392`ymePPL&hg2m8rZwH@~pBwENDx?!M>V`|jWOz=IDx{K%t^J^sX@C!c!ynP;DS z{)HD`dilsJufF#B8*jaR?45Vtd;fzEKl=ESPe1$o#K}{qzxw){Z@>HghaZ1B`|~fq z{`UJHf1dk`=EX9cnHF5l#A@>LKwcKBm9si%UaVySPR?Vsbz*zd#t}zywz*5%<7^q+ zfAH{8SGPZLW>rc%&adu~PkYbO)QrsjSz>!HDYJ57mApuq>@K|!@nqas$i;ijjE_d?$oguT~S4{zapYpI=RtG zD^+gN(?~Fbi>YXYuTMN!nR-movjp{>FEBbom^`ERL#(ubDQYeTWeoMj)=Q$~7iCGr zQb3eLyTD_cnz>+Sxn3=5WSkdKh&SV;R}>5c{6RF1N;VvTSd5-r*%pz*wKj~!dOtA$ z8(1?|j6`Z}7)O?k)wQIOh1yHEIiadLqD43Xl~CDDAXH%}H?AW3e2hKK>q$*F^1xCg z!h5}&w#U7fQ0ei1>{Y|1x3;JROyAZjw$z}gZa^JVs$e9QVPs^UK(M3LObXR5c!fz9 zM%5K2RkQYg%p@w5f6XK+u8Uz3)J$3f&5zJ3Ce`YmdR`_d;bp+P{8Yjv0*Rtjp^^mO zz6Mh01;_vmQ(DPLs@Q?kB|H6Nwu&tXpTnjN$OAh;8!FQchh?a>)ix>UuOpchia?32 z_QN4*8Oe-J5r;x%Jj>Sz1nc~yaCq4obrduJP{JUV=_e4GM+WpvBpSO@!$c_(4wy{* E8-kd> ]" assert pdf_repr(PdfBinary(b"\x90\x1F\xA0")) == b"<901FA0>" + + +def test_duplicate_xref_entry(): + pdf = PdfParser("Tests/images/duplicate_xref_entry.pdf") + assert pdf.xref_table.existing_entries[6][0] == 1197 + pdf.close() diff --git a/src/PIL/PdfParser.py b/src/PIL/PdfParser.py index 1b3cb52a2..dc1012f54 100644 --- a/src/PIL/PdfParser.py +++ b/src/PIL/PdfParser.py @@ -957,14 +957,11 @@ class PdfParser: check_format_condition(m, "xref entry not found") offset = m.end() is_free = m.group(3) == b"f" - generation = int(m.group(2)) if not is_free: + generation = int(m.group(2)) new_entry = (int(m.group(1)), generation) - check_format_condition( - i not in self.xref_table or self.xref_table[i] == new_entry, - "xref entry duplicated (and not identical)", - ) - self.xref_table[i] = new_entry + if i not in self.xref_table: + self.xref_table[i] = new_entry return offset def read_indirect(self, ref, max_nesting=-1): From 895f5a4ffc70fafaae2d16e6618c373e1689186d Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Fri, 21 Apr 2023 09:04:46 +1000 Subject: [PATCH 10/62] Updated macOS tested Pillow versions [ci skip] --- docs/installation.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/installation.rst b/docs/installation.rst index 0ac5914da..8798c0791 100644 --- a/docs/installation.rst +++ b/docs/installation.rst @@ -490,7 +490,7 @@ These platforms have been reported to work at the versions mentioned. | Operating system | | Tested Python | | Latest tested | | Tested | | | | versions | | Pillow version | | processors | +==================================+===========================+==================+==============+ -| macOS 13 Ventura | 3.7, 3.8, 3.9, 3.10, 3.11 | 9.4.0 |arm | +| macOS 13 Ventura | 3.7, 3.8, 3.9, 3.10, 3.11 | 9.5.0 |arm | +----------------------------------+---------------------------+------------------+--------------+ | macOS 12 Big Sur | 3.7, 3.8, 3.9, 3.10, 3.11 | 9.3.0 |arm | +----------------------------------+---------------------------+------------------+--------------+ From b10379b3c14eba9d32a5e81d5242c7c5857a32cc Mon Sep 17 00:00:00 2001 From: Alexander Piskun Date: Fri, 21 Apr 2023 17:42:45 +0300 Subject: [PATCH 11/62] Load image before deepcopy(__getstate__) Signed-off-by: bigcat88 --- Tests/test_numpy.py | 9 +++++++++ src/PIL/Image.py | 3 ++- 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/Tests/test_numpy.py b/Tests/test_numpy.py index 147f94a71..6dc53d1e8 100644 --- a/Tests/test_numpy.py +++ b/Tests/test_numpy.py @@ -1,4 +1,5 @@ import warnings +from copy import deepcopy import pytest @@ -226,6 +227,14 @@ def test_load_first(): assert a.shape == (88, 590) +@skip_unless_feature("libtiff") +def test_load_first_deepcopy(): + with Image.open("Tests/images/g4_orientation_5.tif") as im: + im_deepcopy = deepcopy(im) + a = numpy.array(im_deepcopy) + assert a.shape == (88, 590) + + def test_bool(): # https://github.com/python-pillow/Pillow/issues/2044 a = numpy.zeros((10, 2), dtype=bool) diff --git a/src/PIL/Image.py b/src/PIL/Image.py index 5a43f6c4a..bee9e23d0 100644 --- a/src/PIL/Image.py +++ b/src/PIL/Image.py @@ -672,7 +672,8 @@ class Image: return new def __getstate__(self): - return [self.info, self.mode, self.size, self.getpalette(), self.tobytes()] + im_data = self.tobytes() # load image first + return [self.info, self.mode, self.size, self.getpalette(), im_data] def __setstate__(self, state): Image.__init__(self) From 91b69857c78cb75bb096306a6460c27e70a55c38 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Sat, 22 Apr 2023 10:13:56 +1000 Subject: [PATCH 12/62] Removed duplicate code --- src/PIL/ImageCms.py | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/src/PIL/ImageCms.py b/src/PIL/ImageCms.py index 31b0e5a5e..38cbab19c 100644 --- a/src/PIL/ImageCms.py +++ b/src/PIL/ImageCms.py @@ -185,12 +185,8 @@ class ImageCmsProfile: def _set(self, profile, filename=None): self.profile = profile self.filename = filename - if profile: - self.product_name = None # profile.product_name - self.product_info = None # profile.product_info - else: - self.product_name = None - self.product_info = None + self.product_name = None # profile.product_name + self.product_info = None # profile.product_info def tobytes(self): """ From b62287da3a529bc8e47ab330a81dfd1d8959cef8 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Sat, 22 Apr 2023 11:18:56 +1000 Subject: [PATCH 13/62] Moved test to test_image_copy --- Tests/test_image_copy.py | 9 ++++++++- Tests/test_numpy.py | 9 --------- 2 files changed, 8 insertions(+), 10 deletions(-) diff --git a/Tests/test_image_copy.py b/Tests/test_image_copy.py index 591832147..cd602fc76 100644 --- a/Tests/test_image_copy.py +++ b/Tests/test_image_copy.py @@ -4,7 +4,7 @@ import pytest from PIL import Image -from .helper import hopper +from .helper import hopper, skip_unless_feature @pytest.mark.parametrize("mode", ("1", "P", "L", "RGB", "I", "F")) @@ -42,3 +42,10 @@ def test_copy_zero(): out = im.copy() assert out.mode == im.mode assert out.size == im.size + + +@skip_unless_feature("libtiff") +def test_deepcopy(): + with Image.open("Tests/images/g4_orientation_5.tif") as im: + out = copy.deepcopy(im) + assert out.size == (590, 88) diff --git a/Tests/test_numpy.py b/Tests/test_numpy.py index 6dc53d1e8..147f94a71 100644 --- a/Tests/test_numpy.py +++ b/Tests/test_numpy.py @@ -1,5 +1,4 @@ import warnings -from copy import deepcopy import pytest @@ -227,14 +226,6 @@ def test_load_first(): assert a.shape == (88, 590) -@skip_unless_feature("libtiff") -def test_load_first_deepcopy(): - with Image.open("Tests/images/g4_orientation_5.tif") as im: - im_deepcopy = deepcopy(im) - a = numpy.array(im_deepcopy) - assert a.shape == (88, 590) - - def test_bool(): # https://github.com/python-pillow/Pillow/issues/2044 a = numpy.zeros((10, 2), dtype=bool) From 81a756e93b102b20a8c4599d4b5b3235109ef5ab Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Sat, 22 Apr 2023 13:45:18 +1000 Subject: [PATCH 14/62] Support float font sizes --- Tests/test_imagefont.py | 10 ++++++++++ src/_imagingft.c | 14 +++++++++++--- 2 files changed, 21 insertions(+), 3 deletions(-) diff --git a/Tests/test_imagefont.py b/Tests/test_imagefont.py index 623365d53..7ea485a55 100644 --- a/Tests/test_imagefont.py +++ b/Tests/test_imagefont.py @@ -191,6 +191,16 @@ def test_getlength( assert length == length_raqm +def test_float_size(): + lengths = [] + for size in (48, 48.5, 49): + f = ImageFont.truetype( + "Tests/fonts/NotoSans-Regular.ttf", size, layout_engine=layout_engine + ) + lengths.append(f.getlength("text")) + assert lengths[0] != lengths[1] != lengths[2] + + def test_render_multiline(font): im = Image.new(mode="RGB", size=(300, 100)) draw = ImageDraw.Draw(im) diff --git a/src/_imagingft.c b/src/_imagingft.c index 19785a47f..78e3f7f10 100644 --- a/src/_imagingft.c +++ b/src/_imagingft.c @@ -116,7 +116,9 @@ getfont(PyObject *self_, PyObject *args, PyObject *kw) { int error = 0; char *filename = NULL; - Py_ssize_t size; + float size; + FT_Size_RequestRec req; + FT_Long width; Py_ssize_t index = 0; Py_ssize_t layout_engine = 0; unsigned char *encoding; @@ -133,7 +135,7 @@ getfont(PyObject *self_, PyObject *args, PyObject *kw) { if (!PyArg_ParseTupleAndKeywords( args, kw, - "etn|nsy#n", + "etf|nsy#n", kwlist, Py_FileSystemDefaultEncoding, &filename, @@ -179,7 +181,13 @@ getfont(PyObject *self_, PyObject *args, PyObject *kw) { } if (!error) { - error = FT_Set_Pixel_Sizes(self->face, 0, size); + width = size * 64; + req.type = FT_SIZE_REQUEST_TYPE_NOMINAL; + req.width = width; + req.height = width; + req.horiResolution = 0; + req.vertResolution = 0; + error = FT_Request_Size(self->face, &req); } if (!error && encoding && strlen((char *)encoding) == 4) { From d0b41da094c48077c3511988e98c168bd45f84dc Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Sat, 22 Apr 2023 21:22:01 +1000 Subject: [PATCH 15/62] Support I mode for BuiltinFilter --- Tests/images/hopper_emboss_I.png | Bin 0 -> 13273 bytes Tests/images/hopper_emboss_more_I.png | Bin 0 -> 14624 bytes Tests/test_image_filter.py | 43 +++++---- src/libImaging/Filter.c | 133 ++++++++++++++++++-------- 4 files changed, 120 insertions(+), 56 deletions(-) create mode 100644 Tests/images/hopper_emboss_I.png create mode 100644 Tests/images/hopper_emboss_more_I.png diff --git a/Tests/images/hopper_emboss_I.png b/Tests/images/hopper_emboss_I.png new file mode 100644 index 0000000000000000000000000000000000000000..f4dab388fe5c2dce3771133525df2c09b0fada45 GIT binary patch literal 13273 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 WC}ig2^aR-e0000= pow(2, 31) - 1) { + return pow(2, 31) - 1; + } + return (INT32)in; +} + Imaging ImagingExpand(Imaging imIn, int xmargin, int ymargin, int mode) { Imaging imOut; @@ -96,8 +107,8 @@ ImagingExpand(Imaging imIn, int xmargin, int ymargin, int mode) { void ImagingFilter3x3(Imaging imOut, Imaging im, const float *kernel, float offset) { #define KERNEL1x3(in0, x, kernel, d) \ - (_i2f((UINT8)in0[x - d]) * (kernel)[0] + _i2f((UINT8)in0[x]) * (kernel)[1] + \ - _i2f((UINT8)in0[x + d]) * (kernel)[2]) + (_i2f(in0[x - d]) * (kernel)[0] + _i2f(in0[x]) * (kernel)[1] + \ + _i2f(in0[x + d]) * (kernel)[2]) int x = 0, y = 0; @@ -105,21 +116,40 @@ ImagingFilter3x3(Imaging imOut, Imaging im, const float *kernel, float offset) { if (im->bands == 1) { // Add one time for rounding offset += 0.5; - for (y = 1; y < im->ysize - 1; y++) { - UINT8 *in_1 = (UINT8 *)im->image[y - 1]; - UINT8 *in0 = (UINT8 *)im->image[y]; - UINT8 *in1 = (UINT8 *)im->image[y + 1]; - UINT8 *out = (UINT8 *)imOut->image[y]; + if (im->type == IMAGING_TYPE_INT32) { + for (y = 1; y < im->ysize - 1; y++) { + INT32 *in_1 = (INT32 *)im->image[y - 1]; + INT32 *in0 = (INT32 *)im->image[y]; + INT32 *in1 = (INT32 *)im->image[y + 1]; + INT32 *out = (INT32 *)imOut->image[y]; - out[0] = in0[0]; - for (x = 1; x < im->xsize - 1; x++) { - float ss = offset; - ss += KERNEL1x3(in1, x, &kernel[0], 1); - ss += KERNEL1x3(in0, x, &kernel[3], 1); - ss += KERNEL1x3(in_1, x, &kernel[6], 1); - out[x] = clip8(ss); + out[0] = in0[0]; + for (x = 1; x < im->xsize - 1; x++) { + float ss = offset; + ss += KERNEL1x3(in1, x, &kernel[0], 1); + ss += KERNEL1x3(in0, x, &kernel[3], 1); + ss += KERNEL1x3(in_1, x, &kernel[6], 1); + out[x] = clip32(ss); + } + out[x] = in0[x]; + } + } else { + for (y = 1; y < im->ysize - 1; y++) { + UINT8 *in_1 = (UINT8 *)im->image[y - 1]; + UINT8 *in0 = (UINT8 *)im->image[y]; + UINT8 *in1 = (UINT8 *)im->image[y + 1]; + UINT8 *out = (UINT8 *)imOut->image[y]; + + out[0] = in0[0]; + for (x = 1; x < im->xsize - 1; x++) { + float ss = offset; + ss += KERNEL1x3(in1, x, &kernel[0], 1); + ss += KERNEL1x3(in0, x, &kernel[3], 1); + ss += KERNEL1x3(in_1, x, &kernel[6], 1); + out[x] = clip8(ss); + } + out[x] = in0[x]; } - out[x] = in0[x]; } } else { // Add one time for rounding @@ -195,10 +225,10 @@ ImagingFilter3x3(Imaging imOut, Imaging im, const float *kernel, float offset) { void ImagingFilter5x5(Imaging imOut, Imaging im, const float *kernel, float offset) { #define KERNEL1x5(in0, x, kernel, d) \ - (_i2f((UINT8)in0[x - d - d]) * (kernel)[0] + \ - _i2f((UINT8)in0[x - d]) * (kernel)[1] + _i2f((UINT8)in0[x]) * (kernel)[2] + \ - _i2f((UINT8)in0[x + d]) * (kernel)[3] + \ - _i2f((UINT8)in0[x + d + d]) * (kernel)[4]) + (_i2f(in0[x - d - d]) * (kernel)[0] + \ + _i2f(in0[x - d]) * (kernel)[1] + _i2f(in0[x]) * (kernel)[2] + \ + _i2f(in0[x + d]) * (kernel)[3] + \ + _i2f(in0[x + d + d]) * (kernel)[4]) int x = 0, y = 0; @@ -207,27 +237,52 @@ ImagingFilter5x5(Imaging imOut, Imaging im, const float *kernel, float offset) { if (im->bands == 1) { // Add one time for rounding offset += 0.5; - for (y = 2; y < im->ysize - 2; y++) { - UINT8 *in_2 = (UINT8 *)im->image[y - 2]; - UINT8 *in_1 = (UINT8 *)im->image[y - 1]; - UINT8 *in0 = (UINT8 *)im->image[y]; - UINT8 *in1 = (UINT8 *)im->image[y + 1]; - UINT8 *in2 = (UINT8 *)im->image[y + 2]; - UINT8 *out = (UINT8 *)imOut->image[y]; + if (im->type == IMAGING_TYPE_INT32) { + for (y = 2; y < im->ysize - 2; y++) { + INT32 *in_2 = (INT32 *)im->image[y - 2]; + INT32 *in_1 = (INT32 *)im->image[y - 1]; + INT32 *in0 = (INT32 *)im->image[y]; + INT32 *in1 = (INT32 *)im->image[y + 1]; + INT32 *in2 = (INT32 *)im->image[y + 2]; + INT32 *out = (INT32 *)imOut->image[y]; - out[0] = in0[0]; - out[1] = in0[1]; - for (x = 2; x < im->xsize - 2; x++) { - float ss = offset; - ss += KERNEL1x5(in2, x, &kernel[0], 1); - ss += KERNEL1x5(in1, x, &kernel[5], 1); - ss += KERNEL1x5(in0, x, &kernel[10], 1); - ss += KERNEL1x5(in_1, x, &kernel[15], 1); - ss += KERNEL1x5(in_2, x, &kernel[20], 1); - out[x] = clip8(ss); + out[0] = in0[0]; + out[1] = in0[1]; + for (x = 2; x < im->xsize - 2; x++) { + float ss = offset; + ss += KERNEL1x5(in2, x, &kernel[0], 1); + ss += KERNEL1x5(in1, x, &kernel[5], 1); + ss += KERNEL1x5(in0, x, &kernel[10], 1); + ss += KERNEL1x5(in_1, x, &kernel[15], 1); + ss += KERNEL1x5(in_2, x, &kernel[20], 1); + out[x] = clip32(ss); + } + out[x + 0] = in0[x + 0]; + out[x + 1] = in0[x + 1]; + } + } else { + for (y = 2; y < im->ysize - 2; y++) { + UINT8 *in_2 = (UINT8 *)im->image[y - 2]; + UINT8 *in_1 = (UINT8 *)im->image[y - 1]; + UINT8 *in0 = (UINT8 *)im->image[y]; + UINT8 *in1 = (UINT8 *)im->image[y + 1]; + UINT8 *in2 = (UINT8 *)im->image[y + 2]; + UINT8 *out = (UINT8 *)imOut->image[y]; + + out[0] = in0[0]; + out[1] = in0[1]; + for (x = 2; x < im->xsize - 2; x++) { + float ss = offset; + ss += KERNEL1x5(in2, x, &kernel[0], 1); + ss += KERNEL1x5(in1, x, &kernel[5], 1); + ss += KERNEL1x5(in0, x, &kernel[10], 1); + ss += KERNEL1x5(in_1, x, &kernel[15], 1); + ss += KERNEL1x5(in_2, x, &kernel[20], 1); + out[x] = clip8(ss); + } + out[x + 0] = in0[x + 0]; + out[x + 1] = in0[x + 1]; } - out[x + 0] = in0[x + 0]; - out[x + 1] = in0[x + 1]; } } else { // Add one time for rounding @@ -327,7 +382,7 @@ ImagingFilter(Imaging im, int xsize, int ysize, const FLOAT32 *kernel, FLOAT32 o Imaging imOut; ImagingSectionCookie cookie; - if (!im || im->type != IMAGING_TYPE_UINT8) { + if (im->type != IMAGING_TYPE_UINT8 && im->type != IMAGING_TYPE_INT32) { return (Imaging)ImagingError_ModeError(); } From f5c1f7a2c27c810d3d95dc659c5c24c455ca6f74 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Sat, 22 Apr 2023 21:47:36 +1000 Subject: [PATCH 16/62] Added Fedora 38 --- .github/workflows/test-docker.yml | 1 + docs/installation.rst | 2 ++ 2 files changed, 3 insertions(+) diff --git a/.github/workflows/test-docker.yml b/.github/workflows/test-docker.yml index cbe6c2ca3..84c46b1d4 100644 --- a/.github/workflows/test-docker.yml +++ b/.github/workflows/test-docker.yml @@ -40,6 +40,7 @@ jobs: centos-stream-9-amd64, debian-11-bullseye-x86, fedora-37-amd64, + fedora-38-amd64, gentoo, ubuntu-18.04-bionic-amd64, ubuntu-20.04-focal-amd64, diff --git a/docs/installation.rst b/docs/installation.rst index 8798c0791..21dcd0227 100644 --- a/docs/installation.rst +++ b/docs/installation.rst @@ -450,6 +450,8 @@ These platforms are built and tested for every change. +----------------------------------+----------------------------+---------------------+ | Fedora 37 | 3.11 | x86-64 | +----------------------------------+----------------------------+---------------------+ +| Fedora 38 | 3.11 | x86-64 | ++----------------------------------+----------------------------+---------------------+ | Gentoo | 3.9 | x86-64 | +----------------------------------+----------------------------+---------------------+ | macOS 12 Monterey | 3.8, 3.9, 3.10, 3.11, | x86-64 | From e3cb4bb8e00fcaf4c3e0783f7c02e51372595659 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Mon, 24 Apr 2023 07:48:53 +1000 Subject: [PATCH 17/62] Update CHANGES.rst [ci skip] --- CHANGES.rst | 3 +++ 1 file changed, 3 insertions(+) diff --git a/CHANGES.rst b/CHANGES.rst index cd0b95085..b82333af7 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -5,6 +5,9 @@ Changelog (Pillow) 10.0.0 (unreleased) ------------------- +- Load before getting size in __getstate__ #7105 + [bigcat88, radarhere] + - Fixed type handling for include and lib directories #7069 [adisbladis, radarhere] From ab3d0c071e60da904062d22f6b9a73e8fc6cdcb9 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Mon, 24 Apr 2023 21:03:27 +1000 Subject: [PATCH 18/62] Raise error from stderr of Linux grabclipboard command --- src/PIL/ImageGrab.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/PIL/ImageGrab.py b/src/PIL/ImageGrab.py index 982f77f20..2592ba2df 100644 --- a/src/PIL/ImageGrab.py +++ b/src/PIL/ImageGrab.py @@ -141,8 +141,11 @@ def grabclipboard(): msg = "wl-paste or xclip is required for ImageGrab.grabclipboard() on Linux" raise NotImplementedError(msg) fh, filepath = tempfile.mkstemp() - subprocess.call(args, stdout=fh) + err = subprocess.run(args, stdout=fh, stderr=subprocess.PIPE).stderr os.close(fh) + if err: + msg = f"{args[0]} error: {err.strip().decode()}" + raise ChildProcessError(msg) im = Image.open(filepath) im.load() os.unlink(filepath) From 99a474a9e63d5bf2ea2654bb8102731db90ff8c2 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Tue, 25 Apr 2023 23:55:29 +1000 Subject: [PATCH 19/62] Removed Ubuntu 18.04 --- .github/workflows/test-docker.yml | 1 - docs/installation.rst | 2 -- 2 files changed, 3 deletions(-) diff --git a/.github/workflows/test-docker.yml b/.github/workflows/test-docker.yml index 84c46b1d4..4f01abe44 100644 --- a/.github/workflows/test-docker.yml +++ b/.github/workflows/test-docker.yml @@ -42,7 +42,6 @@ jobs: fedora-37-amd64, fedora-38-amd64, gentoo, - ubuntu-18.04-bionic-amd64, ubuntu-20.04-focal-amd64, ubuntu-22.04-jammy-amd64, ] diff --git a/docs/installation.rst b/docs/installation.rst index 21dcd0227..a254ec8c2 100644 --- a/docs/installation.rst +++ b/docs/installation.rst @@ -457,8 +457,6 @@ These platforms are built and tested for every change. | macOS 12 Monterey | 3.8, 3.9, 3.10, 3.11, | x86-64 | | | 3.12, PyPy3 | | +----------------------------------+----------------------------+---------------------+ -| Ubuntu Linux 18.04 LTS (Bionic) | 3.9 | x86-64 | -+----------------------------------+----------------------------+---------------------+ | Ubuntu Linux 20.04 LTS (Focal) | 3.8 | x86-64 | +----------------------------------+----------------------------+---------------------+ | Ubuntu Linux 22.04 LTS (Jammy) | 3.8, 3.9, 3.10, 3.11, | x86-64 | From d0e9a13c0c8ee0c77559e604c8758720991d930a Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Wed, 26 Apr 2023 17:08:06 +1000 Subject: [PATCH 20/62] Build all readthedocs formats --- .readthedocs.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.readthedocs.yml b/.readthedocs.yml index 98d9e4425..ec3300dd1 100644 --- a/.readthedocs.yml +++ b/.readthedocs.yml @@ -1,5 +1,7 @@ version: 2 +formats: all + build: os: ubuntu-22.04 tools: From 0c8db130afe5343358b30fadffa6e59d2c877083 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Thu, 27 Apr 2023 13:31:14 +1000 Subject: [PATCH 21/62] Updated harfbuzz to 7.2.0 --- winbuild/build_prepare.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/winbuild/build_prepare.py b/winbuild/build_prepare.py index 3f639454b..9b5fc5d18 100644 --- a/winbuild/build_prepare.py +++ b/winbuild/build_prepare.py @@ -337,9 +337,9 @@ deps = { "libs": [r"imagequant.lib"], }, "harfbuzz": { - "url": "https://github.com/harfbuzz/harfbuzz/archive/7.1.0.zip", - "filename": "harfbuzz-7.1.0.zip", - "dir": "harfbuzz-7.1.0", + "url": "https://github.com/harfbuzz/harfbuzz/archive/7.2.0.zip", + "filename": "harfbuzz-7.2.0.zip", + "dir": "harfbuzz-7.2.0", "license": "COPYING", "build": [ *cmds_cmake( From b62c3baeeedb642b8eaf4dc16245fb54bcb92dfd Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Sun, 30 Apr 2023 06:32:27 +1000 Subject: [PATCH 22/62] Update CHANGES.rst [ci skip] --- CHANGES.rst | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/CHANGES.rst b/CHANGES.rst index b82333af7..7b13900a8 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -5,6 +5,12 @@ Changelog (Pillow) 10.0.0 (unreleased) ------------------- +- Support float font sizes #7107 + [radarhere] + +- Use later value for duplicate xref entries in PdfParser #7102 + [radarhere] + - Load before getting size in __getstate__ #7105 [bigcat88, radarhere] From ff003bfbcc9cfd7d281030f836616c0cdd59cfa6 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Sun, 30 Apr 2023 14:49:40 +1000 Subject: [PATCH 23/62] Added unpacker from I;16B to I;16 --- Tests/test_lib_pack.py | 1 + src/libImaging/Unpack.c | 11 +++++++++++ 2 files changed, 12 insertions(+) diff --git a/Tests/test_lib_pack.py b/Tests/test_lib_pack.py index de3e7d156..f7812f62b 100644 --- a/Tests/test_lib_pack.py +++ b/Tests/test_lib_pack.py @@ -757,6 +757,7 @@ class TestLibUnpack: def test_I16(self): self.assert_unpack("I;16", "I;16", 2, 0x0201, 0x0403, 0x0605) + self.assert_unpack("I;16", "I;16B", 2, 0x0102, 0x0304, 0x0506) self.assert_unpack("I;16B", "I;16B", 2, 0x0102, 0x0304, 0x0506) self.assert_unpack("I;16L", "I;16L", 2, 0x0201, 0x0403, 0x0605) self.assert_unpack("I;16", "I;12", 2, 0x0010, 0x0203, 0x0040) diff --git a/src/libImaging/Unpack.c b/src/libImaging/Unpack.c index 7eeadf944..a0fa22c7d 100644 --- a/src/libImaging/Unpack.c +++ b/src/libImaging/Unpack.c @@ -1149,6 +1149,16 @@ unpackI16N_I16(UINT8 *out, const UINT8 *in, int pixels) { } } static void +unpackI16B_I16(UINT8 *out, const UINT8 *in, int pixels) { + int i; + for (i = 0; i < pixels; i++) { + out[0] = in[1]; + out[1] = in[0]; + in += 2; + out += 2; + } +} +static void unpackI16R_I16(UINT8 *out, const UINT8 *in, int pixels) { int i; for (i = 0; i < pixels; i++) { @@ -1764,6 +1774,7 @@ static struct { {"I;16L", "I;16L", 16, copy2}, {"I;16N", "I;16N", 16, copy2}, + {"I;16", "I;16B", 16, unpackI16B_I16}, {"I;16", "I;16N", 16, unpackI16N_I16}, // LibTiff native->image endian. {"I;16L", "I;16N", 16, unpackI16N_I16}, // LibTiff native->image endian. {"I;16B", "I;16N", 16, unpackI16N_I16B}, From b7d19e83d00b08c643c69bca6ea3f6e603c72a98 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 1 May 2023 19:15:29 +0000 Subject: [PATCH 24/62] [pre-commit.ci] pre-commit autoupdate MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit updates: - [github.com/tox-dev/tox-ini-fmt: 1.0.0 → 1.3.0](https://github.com/tox-dev/tox-ini-fmt/compare/1.0.0...1.3.0) --- .pre-commit-config.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index c3b6dc0a6..4882a317f 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -57,7 +57,7 @@ repos: - id: sphinx-lint - repo: https://github.com/tox-dev/tox-ini-fmt - rev: 1.0.0 + rev: 1.3.0 hooks: - id: tox-ini-fmt From a766fa4cd1c749ca27375079ef92a57dc538c905 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 1 May 2023 19:16:00 +0000 Subject: [PATCH 25/62] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- tox.ini | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/tox.ini b/tox.ini index d7948ef6d..458a00107 100644 --- a/tox.ini +++ b/tox.ini @@ -1,6 +1,7 @@ [tox] -minversion = 1.9 -envlist = +requires = + tox>=4.2 +env_list = lint py{py3, 311, 310, 39, 38} @@ -23,7 +24,7 @@ skip_install = true deps = check-manifest pre-commit -passenv = +pass_env = PRE_COMMIT_COLOR commands = pre-commit run --all-files --show-diff-on-failure From d9921f697aed24bd21b7c090bb6edd55acc8997f Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Wed, 3 May 2023 08:29:20 +1000 Subject: [PATCH 26/62] Use stdlib for setuptools on MinGW --- .github/workflows/test-mingw.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/test-mingw.yml b/.github/workflows/test-mingw.yml index ddfafc9d7..a109ec0d8 100644 --- a/.github/workflows/test-mingw.yml +++ b/.github/workflows/test-mingw.yml @@ -80,7 +80,7 @@ jobs: pushd depends && ./install_extra_test_images.sh && popd - name: Build Pillow - run: CFLAGS="-coverage" python3 -m pip install --global-option="build_ext" . + run: SETUPTOOLS_USE_DISTUTILS="stdlib" CFLAGS="-coverage" python3 -m pip install --global-option="build_ext" . - name: Test Pillow run: | From db7326674e5261e19fa35f3a32efaf6dea48a5cc Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Mon, 1 May 2023 13:03:31 +1000 Subject: [PATCH 27/62] Updated libimagequant to 4.2.0 --- depends/install_imagequant.sh | 2 +- docs/installation.rst | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/depends/install_imagequant.sh b/depends/install_imagequant.sh index 362ad95a2..fd6000ee1 100755 --- a/depends/install_imagequant.sh +++ b/depends/install_imagequant.sh @@ -1,7 +1,7 @@ #!/bin/bash # install libimagequant -archive=libimagequant-4.1.1 +archive=libimagequant-4.2.0 ./download-and-extract.sh $archive https://raw.githubusercontent.com/python-pillow/pillow-depends/main/$archive.tar.gz diff --git a/docs/installation.rst b/docs/installation.rst index a254ec8c2..ad27b67ee 100644 --- a/docs/installation.rst +++ b/docs/installation.rst @@ -181,7 +181,7 @@ Many of Pillow's features require external libraries: * **libimagequant** provides improved color quantization - * Pillow has been tested with libimagequant **2.6-4.1.1** + * Pillow has been tested with libimagequant **2.6-4.2** * Libimagequant is licensed GPLv3, which is more restrictive than the Pillow license, therefore we will not be distributing binaries with libimagequant support enabled. From 3fc446c2770b091b07d3b6cff63e87385c19e7fc Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Wed, 3 May 2023 22:54:18 +1000 Subject: [PATCH 28/62] Added width argument to regular_polygon --- Tests/images/imagedraw_triangle_width.png | Bin 0 -> 499 bytes Tests/test_imagedraw.py | 20 ++++++++++---------- docs/reference/ImageDraw.rst | 3 ++- src/PIL/ImageDraw.py | 4 ++-- 4 files changed, 14 insertions(+), 13 deletions(-) create mode 100644 Tests/images/imagedraw_triangle_width.png diff --git a/Tests/images/imagedraw_triangle_width.png b/Tests/images/imagedraw_triangle_width.png new file mode 100644 index 0000000000000000000000000000000000000000..3d35326e73b92ffb24b9d64cc771662eee87000c GIT binary patch literal 499 zcmeAS@N?(olHy`uVBq!ia0vp^DImD;uumf=k2YHeTNK0SOcs7 z|DV6msp$lZm}+J5+nH&zb(QAFEMItk*2G_{>lmti1tOXb2(e64*uc`L%Aw$qU_*5&+_>uupeX5C~oixu{%*F`cV5M`^+^t6UwtW;ym>{J}i7cmHX4mfDJwq z^7*X=RE1MQ*4H&8pJJBW`?dD-_sjpAPcv~^--$oPB)K)#^@mrH!?$&-I!`hF^gpP5 zlEE@dmQkZ-LOj3W7puq3J-@zfU|z3hc0x)1)AJh&?jqZS^iDLr&gkVSN^#UqY|`1) zcdbJqeeX_1?T3v$R>iUgB4Q_QhKC;5dYoye(vPc086$r<|C3>U^2%Ifj}Y656|C1U zN--{ZwW?{|61kQzLoZ#AukA7wy5BZkXDqONb1i$vo=pi?8!lYG7o7YVvOvzStWc37?}*7u6{1-oD!M<@Fc=% literal 0 HcmV?d00001 diff --git a/Tests/test_imagedraw.py b/Tests/test_imagedraw.py index 7ffd7969d..406f44c06 100644 --- a/Tests/test_imagedraw.py +++ b/Tests/test_imagedraw.py @@ -1347,20 +1347,20 @@ def test_same_color_outline(): @pytest.mark.parametrize( - "n_sides, rotation, polygon_name", - [(4, 0, "square"), (8, 0, "regular_octagon"), (4, 45, "square")], + "n_sides, polygon_name, args", + [ + (4, "square", {}), + (8, "regular_octagon", {}), + (4, "square_rotate_45", {"rotation": 45}), + (3, "triangle_width", {"width": 5, "outline": "yellow"}), + ], ) -def test_draw_regular_polygon(n_sides, rotation, polygon_name): +def test_draw_regular_polygon(n_sides, polygon_name, args): im = Image.new("RGBA", size=(W, H), color=(255, 0, 0, 0)) - filename_base = f"Tests/images/imagedraw_{polygon_name}" - filename = ( - f"{filename_base}.png" - if rotation == 0 - else f"{filename_base}_rotate_{rotation}.png" - ) + filename = f"Tests/images/imagedraw_{polygon_name}.png" draw = ImageDraw.Draw(im) bounding_circle = ((W // 2, H // 2), 25) - draw.regular_polygon(bounding_circle, n_sides, rotation=rotation, fill="red") + draw.regular_polygon(bounding_circle, n_sides, fill="red", **args) assert_image_equal_tofile(im, filename) diff --git a/docs/reference/ImageDraw.rst b/docs/reference/ImageDraw.rst index aec7a3ef8..29115120c 100644 --- a/docs/reference/ImageDraw.rst +++ b/docs/reference/ImageDraw.rst @@ -296,7 +296,7 @@ Methods :param width: The line width, in pixels. -.. py:method:: ImageDraw.regular_polygon(bounding_circle, n_sides, rotation=0, fill=None, outline=None) +.. py:method:: ImageDraw.regular_polygon(bounding_circle, n_sides, rotation=0, fill=None, outline=None, width=1) Draws a regular polygon inscribed in ``bounding_circle``, with ``n_sides``, and rotation of ``rotation`` degrees. @@ -311,6 +311,7 @@ Methods (e.g. ``rotation=90``, applies a 90 degree rotation). :param fill: Color to use for the fill. :param outline: Color to use for the outline. + :param width: The line width, in pixels. .. py:method:: ImageDraw.rectangle(xy, fill=None, outline=None, width=1) diff --git a/src/PIL/ImageDraw.py b/src/PIL/ImageDraw.py index e9ccf8041..1e4eeab25 100644 --- a/src/PIL/ImageDraw.py +++ b/src/PIL/ImageDraw.py @@ -279,11 +279,11 @@ class ImageDraw: self.im.paste(im.im, (0, 0) + im.size, mask.im) def regular_polygon( - self, bounding_circle, n_sides, rotation=0, fill=None, outline=None + self, bounding_circle, n_sides, rotation=0, fill=None, outline=None, width=1 ): """Draw a regular polygon.""" xy = _compute_regular_polygon_vertices(bounding_circle, n_sides, rotation) - self.polygon(xy, fill, outline) + self.polygon(xy, fill, outline, width) def rectangle(self, xy, fill=None, outline=None, width=1): """Draw a rectangle.""" From a4986ba9866797648b602ce62fee393f43b522df Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Thu, 4 May 2023 07:54:30 +1000 Subject: [PATCH 29/62] Support reading signed 8-bit TIFF images --- Tests/images/8bit.s.tif | Bin 0 -> 16518 bytes Tests/test_file_tiff.py | 6 ++++++ src/PIL/TiffImagePlugin.py | 2 ++ 3 files changed, 8 insertions(+) create mode 100644 Tests/images/8bit.s.tif diff --git a/Tests/images/8bit.s.tif b/Tests/images/8bit.s.tif new file mode 100644 index 0000000000000000000000000000000000000000..043cba6af8bbfe6d7386ac7ca247c5235c0dc387 GIT binary patch literal 16518 zcmYj&2VfKD`F_$7u(j?^cc;^qEE^k)S!NRmnFPWNdk^nD@ZQ^&W!bXjz4tcQ?7b5R zfwW1Qj-)NkB2BY51n~d=d?(Xizr?4LZ1lYE^UiPFnl(>Md*X>FCO_f+}ThXe%$1_lHK95@gV5a=4X z2?`Dl3h6D!%heyXJBqb#!9E^`Ys1azLUQ4kiv%u*MTC>R$_$oo~X;A4c z$pOy{K5sCp&3^pLhZZw>i`skM8()6=^@lct*R(16x!%*}&UkiHNKpKNNSCs~5y3$& z^svxSKoMYqA4G0NLhxxY2uMs!N&*83iGqsJ>op42>@k7wWfD|^TIa3f4f+++HXMCg ztugAn_=RK3R4Sv%!*t=7%AAo!8XtpZeRkTad9!BDoVI>{#GV5%L1=X7ZfG7F78V*D z3Vc9;1tLUnFtNhKEeMEBh(Ab1LL#qH0-xeMENUM5n~Yv&g0B@!dO_vIZ|R$*7d*X; z%58%lTD{t0p7GYrnOeqV@~OA2w0;wBP9In|YufyMF+ri>VZp&+k-!ZL4+HkZAR=IZ zf7b|rEWx3XF;UU67;*8OiZueCciCTKGJn{4@1*tF4z~je_v!w@Bv5>79JiR3jYJoA)%q<0wRK3 z)*vJR9~6a0L`FqL$Ha01Z_=w3oYAaS8I2mF(aXds;Q*c{PSB_|oB9`MG*hPNm752A zSZ<2RXXfdi8l8}N?@sIIKaSu3;o&duymj}Azi-Kvro<2+M??TUJUlE!gdxTtcEAV= zf$NKo;PQ{qaNt7#Uad0g)pAy66nKJf@-`EEwV=~z7`<-I;M1JROJmSSw;BYa$q@P1 z2jeB`=hC*M?pqby`{AAL;O+V8D}ycmUq1TfrXwmOA~GTz{KEl=nivp;=nM4`n1uYG zz@QK~XBhCKqhn$O?Li+kPt$r&6*JAE(rbORYNd+d^*TMLR2$Y0&(oSM7K7$Mr_rMJ zyZZB^U&q6ZdPB*DA3m@6{Q946RR8$5KW)^^oO1gA-GB7(?d*t{$e@G!V(- z@Nj@25nl|1pb+rl3JdakI6(w$`STq=HRY)`s5QFjf>x_Hs}zEfHX{w{)y7qW3$m!r)!|)Q1Fte?O;LMTJTx+fQR(ys6(^Wp z9$Kn3d%;7KYnJ@<{ez!A8X5WH5}8Wn9rd?2fBE&-2R}c!|HtlWf?kvGB?uUQ_~6ms z6T-t|gTS|I1P2BN1K9-%zmFichkJyPfgsQtu0Htc?$uRBEwA-Dxz{XMG=gB5tm4!f zJ+IL$AAU|_@I+#cZu!g4kG^ZO#y@RV^Cr!d(|>RL^Zkd99{%!8)8E21oJV0Uq8H#ux24Nv0GCF$r?spG=xSK$GSQvKdpWfTD z;ldv`@M=~$+vd%Ih-U^?2wG3ALA(F2-+viCU%|*_dtyx*#wY8Wf4|fD@vrwU{ppLZ zwoUUfS;9Pgv>O^P-TUVAxAz<*EW3utJsiM=kJO9gNIZ`S5D^sN_th7lfA!bJ9vXui_tM=;?V{w_i-*<;f~Q$+*nHZqR%vvHZ(Ta&ymIG@KmR2) zz{@YQd+CaiW9OgM2>VJG zdZ-zVL{RKTIgSVe(`3Nu#Igk5N!cUV5F-ZlH_qVkbhXK(SF(bJ^}hB^LjLQw7Hej0 zslEA+503Z=nkmhDlv<-wE@Na0l~P9ko~PtmN+ZZvM#FmwM!{$h^i$O8DF^cY@$PyL zo|4fDcS`;gvkGYsSq4;NM2bTc1=!I9n*79J$c@vVMy+QIdZR^q%uhWRZ=Jtd`BEzPkk!wD>}mrznSW-{geq+BMI zQNNqaQp+NN9T_F|Ie>-~fW-}(9^3$3?0ymwxTzY0&r~D6P2gwrp!@YS={QcoWZl`| zU74*DDv~seRK`gaZc3?=QYd8d$+YsfjJr%CnJkgJ%M?;K>7+@tRK`5zE}bN}DSl76 zGqgm$JO=m>AUY;4jsX78h>nbe0QevleYnb`w|Gs}ayqrr=(pHQ=V|dUswf5H>{BcB z{yHu{MWv8a6eXwRQiV(+n=F${rE=*c2;eT4xs%T%QmMq_~?Y_1Y+cPGSPq> zapcvdID~SEB$y4Jp57{o*J|`?LG5KSdzjP=#Vi)QWbT5BOD?1-nL;k-C^zsw=_#q3 zgr=p?6hj7MEATrR^jAO}+{)!{Qd%yN$!5pKCB`PkB*vr1P9U8&2Id=aqBIZ&a)^`= z{XM7fa*aj_0rXxV0QxH!+N@LZf{r#E7xz@zW(_+ zCWeSfoPd^@pm~3iv0a^6mD(` zN+OX<+1P{0Kut+b#{Z%W0LP>c0wiGanMj0yh)IG;XEuAOXr+!9%)F<`;^ER?P@?_e zCEAR1C0rUzfPCeY?T*&&!J(ny(WA!(M-H_$G}pJaw)c#TjU5{3>FDfl&)x4uPXYr9 znvOpxLZ@Qjh6n)~{%?>8a6&>-ipH!n5qw6=avo}LqseSGkoeQ+)H($$Tacmxr4os| zpfRn=2Y7ea;qzBtd;RK}t2Zy6yL|rQ`SVw<-?(;R^w^Q1p^@&|oD~ABuTXNyKuAhX zNlQyhO-V^fO-+FS7`RV0WM1&Fcxj%S#tLeML9K&B8GuhIl!65l z0ES^x>@ulx((k->?Am;=dFaUK@e^k*Tzliz?d#WXy!FPF*RS4s^Ty5BuAIMc>E@Av zk)gik7JKR5B^2XsOiqkXNk~acPfJZD9Q;=fNHa0g($dq@|5In2Z5{KFpbqFY9(n^- z9bEV(y;k4^WqOfRDN}f?*tj;Rw7Kud*!fFWuU$HO^2Ft9*Kfam;p*kn7tb9T>!>QN zs%&kmYia9ja#rT;G)dTL>1in$$?4e{>1pX185!w#b;*)W-ezRr9r-x@^N)gVezsqy zSdlCkbb7sB&>9UY@UO)rOU-GNS;cbdH}uryYqw=oR5bVXHP=^_78X}Fow)Sc^(&We zUORO9M0;~_UU6zkd*{i{#^&ZaYjMg7UN$Qu9kvF;fX>X!%*ezm0mMCd3*-#)dFH>q z{pRDdQ
_vd+OX$L`f=Gnw@MkZHHd1+60wzayuzrVl5VYQYwboHIS zeCgcPTbD14bvHLB?LDw3BByq==SX!`YePcHn(6Arxw*-y*;#3T%E-(TAv3bFvNE%? zaRIvcd3H|jH=lm=&wWNu$-a-8y-Y!kv$R-*;3x&9RC637XZe(DhNTQE!@}Y+9c?WQ z&CR7rC>#e9>xPeyjhw!5`P`8%XF<@`-5Zv@xO~yG&#YRrXK!Hqrm4nNh52b21-S$` zli)&xtZabil$2a)(NS^*%M~WF z%p{NC*!^Ki)h(4ZHI+G0fq@Yzg=y6TV~0+>ar5d}O;JkFa<3&DgLm&*?>jwix8I6w zd!|p@S(KfcmYJFXFu-SJ65Q+@f{y_a@DAGN<$d-Nnp|U7HKRYcSkig2y%+Jduz*#xDu92PNx;y&iTkn4{pj-0# z-H-nDm%USXtxjvAR{PJc=rXD#48^9WnpjJ4QRJ5A40D(I8KT-*{mHaUfdP6w=kz^NdaB-SJ5cFCTzcM=iE5J}(cS+0J@;a~^tK#`~Xt_P2OXoz6q6VP{9hMOzq!oaUJ9BE!6d zg2M=zMy{B4sPJY zwmTxLzO*_gHY#<+vdYrp%v@V$(Znbwqo@#wg&5=o@)YFft$k)HCwM3+-3_O2=+OiL zE1*2RJRvi9vK-#0RK}Hfyjr$xb9hutcuvH|^{ZZbcI9f!>~l+N>zV_{-ulDYjIsWv zlC0{I+~nA#VtZkEO>bdJytQ&!T5euxeqoV_i%SW{1imN{`3(`kM=h8=%yQY(H`nVt z%?orowZWiXx+AIfRRzs*44smz-W|0eGCn@s*>Es=^~=jvgj5Z6RMb};x-@WL==$3? z`v*@nlpXFrartDot!JRAwWT#bskY6&DXk#KR*+{WxMWzZR#5fG=CP8q7u)S4XU2M)N?Qlpdpo=?~1eMvSQc+ThGir2b1C-H)I{%R6FzT-SYFm*tIk>o>*_oeQ`r7%C z)YfZny>;zY#n7=^=Nsz_>^X&HBmK4Ybya1_o!xuO3aojyl423ObYfUbh#d&=g}8{K z@PYsWXoaA{+N&DtUuK;K6Dz4g$*Sc_hKViWjA^elS0(JT+G|T|G6FzcS>@PUw=VUk zpSt?a?LWSAq~`LC8}HsYH_+I6_-KD~U9}@_RbfYFPDyEDVQHBSnwQ#uZ^gZ}xCA%E z4<@WoTr8;7I+I!<<2eDda2~<{U%@D(Dw@`yu}vwL%uWgJuG+sZzp$>MJ}ten|J;qY z|NMvR@6>i)y8708@7#R5edOw!H*Va#c=Y)3{+9NJjP~LUG40NZa$7+`N%3R+veHtU z4Wfwn&|Ne>$N;_;4G)&o)CRp;1!1%T{2rc9QCbPBim@$jzEsfMxHr|2RZ>@19F%_Q zy|@4LxA*>ZE&KHAw{O4q&aK<0az?LSyYpxbK+tZz% zR9aEv$nL%Q$G2}@I+m7t_}s1AH{O2t-K!~qwFATb14qxF>2sDASM{E5%d}V8tfe5k z2nGPK%|`SuBmWZtKm)-hz5#rVo-`bs8kJA0(-{QRU0Oy_Y9*tUSqgevj=$bF)YN`v zC?~nFu&S%T+HvN5b5U48=dtrQ-@f^WH($SbA}2O6D5S7&^hl4@R$ktDqAI1a%xbd( z&MM-UL4eXyJK=!P;WCA2j8d}5uH|HM5nr#bAjJ2QZ6@twj^~ zWp-PcsJ|$J3m;dT4g3RNPBFY%qtU1ZPR()zp9nxLZ9hCR=D2jdq`K$yU{7gwfx}VK z*fwzNRLkDAp&PcRWIJkV9kGE6Hu!I;97OJEZ|JTst4{1XR@G%KvX+&VSJ*^+ds$h9 zy^P$tC=ij!rL2rH2&@ky#hQ`UjFo?l+Nh>!j&r9J9JjLe(2+yK)tAp&Yldp84`de? zR<<~*+dKN}g4Q-?E_?d9H9K}}TDy1EwweIHU|VfPTWxc1QLhvj5lx&wZTN0 zy`o%91trA$0PONfdwIE@S9ULv>1GFfBa9hx7&G**Am@imXiqvWL}=*S*VZBOKL11YZ^OpczCG(!l}-x_TwXG zFCMX#wf5B1HRWs#Z0T<6Z|(1>%r0tnHr3c#QfEIO9G_p+*45m8>`eRdVFE_H4*HW9 za&f_v;CEF6Q3Ct(Q?l1-RT8O5uF|M99L=dXiesgeN8RC}(cZD4;l758CwsDsPhLH9 zeYC8my{Exp+qb-`t-EieXW-D`?$P$frmm*0y3KxD!tJ${^)TnU08v-m|C9 zrxv$3M{1hlU)W#W*?;`-iP1CHFJC@!yrXxpx20tQD!0Ig6{lc2HYuD@vUcYwzn&(%q3QQ_*={w(B*HrW34o9QC zrsQ$_kq{vGa0akYHW36aDTw}eZWQY{+HK`L%=dM2idU+5IpsNLMoQh-kz<1=jt%xV zojzF-yYcy@3!e7#Np!XkojjhO6}bNSok8JAvAeeh#IqOazIn>%xuwj2` zZ=bCkUkx8$G+8|puDq-XfZa7S%jN9pjf!>#=d z$*)F~AL^2(W}9aSBp-FYn?PV{y;sRttiLKD-hoqg@iWgRVT<(1jn zg3`*W^2#eJAOjiVEA%I5KqW7t00f`#!D<T8`P72zRC@x^83 zm9`2pAj4xpDv%5avg-;0zyP_ZXcZ%2l**|dYFdqsUkL)#)BUH;T2weVJaVLOptJf& ze@beG9dn7iz=hjVYf8(rTRR-3&MJG|z@fg*`UXc~v7@!K!`WSEvu}*bOSM_6%F8M$ z9F>)higE`L0HYHA2U8HgfD|M>ksM?#)k-;oO7a`|B&ABuQb_zb3gA6!w(qPtTbp~4 zf%|*v$GVdb5r*%+G5Sib<{O?^!K$j4PCi)aMq zY^|`C+bSIsL!<#0M}=q(T!g?<}bM5Numyeygbm`3KSe>Id-da+bnP_#C zRaBN&i2_tsRS|p)hrJvXn3Q1%07n#a53#=z6_3I?Hm8IUC^TN0Y5x8*r%#{Zzi4+` zM^8=jIcHN{T3JR`er`#2;ZpD7s?_|ls=~UKj@HJ3!PDo451qPt`AAn=QEF6nR%&TZ zeks9rRyZqQ`U;2hF%O~cX&YL^S&)3g?@v@@!L2Lch zYHNOyE!7^MR<+bRFIHqGhUMl} zB$t#&t1Ne&ZH|5Orp|vcAUQKBe8--EsL0Tu!2R3SF7%$4THTQ;;XNZNN-Ho=a1j6h zzfn~QU{L@UKFNt>usqsrxq_z9_%NJh;oQ0N=L3KGbpJX2bF%6k4Hs%N5`**eN)yU5 zIQPWYk{cX(+bljamacqmk>Aw0&pbDO+SEBuzqo4I;)Blm&RCgJ&hDzIt}KBQR647j zm5&GBi!R_YfLMw|d=B{7)S_9?!22v%G{=A5{JFDd_)ecS*LPlUOGf+EuDI}k;)6LE z!5W!+*Wv9g73C=_UU=r|MJ6?;Li4O*RMTJHy7iTaDqCB7B7??NxvA3G?5wVKI-Ql} z|HX#@6BZzu5p}^eI3Cj-Df(|(WAd22aQaNb|E%fLeCN!axnNCeOwVh>A^Ue1>`s{j z)#MkBJ=0TLoEf}(_m*|DeGFQqT%q=u@p4eazCB5W&dOffWX@eq{eEtKmEBoa6D7488wxTn)=#Gk_h0ARW%ikDtH1ilLIaQ8B|zfbeEZWM#s9!29sAT zojGS7{Lk0hJZpiU7S#x;NjljLTW{E3q}?xn`OK=O#=QKbP6BJ>iK5 z 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 TR=N?=Kr4|!DMe+Lt#1AQlp!)* literal 0 HcmV?d00001 diff --git a/Tests/test_file_tiff.py b/Tests/test_file_tiff.py index 97a02ac96..30c6303a2 100644 --- a/Tests/test_file_tiff.py +++ b/Tests/test_file_tiff.py @@ -198,6 +198,12 @@ class TestFileTiff: with pytest.raises(OSError): im.save(outfile) + def test_8bit_s(self): + with Image.open("Tests/images/8bit.s.tif") as im: + im.load() + assert im.mode == "L" + assert im.getpixel((50, 50)) == 184 + def test_little_endian(self): with Image.open("Tests/images/16bit.cropped.tif") as im: assert im.getpixel((0, 0)) == 480 diff --git a/src/PIL/TiffImagePlugin.py b/src/PIL/TiffImagePlugin.py index 3d4d0910a..1ca1b6ea9 100644 --- a/src/PIL/TiffImagePlugin.py +++ b/src/PIL/TiffImagePlugin.py @@ -170,6 +170,8 @@ OPEN_INFO = { (MM, 0, (1,), 2, (8,), ()): ("L", "L;IR"), (II, 1, (1,), 1, (8,), ()): ("L", "L"), (MM, 1, (1,), 1, (8,), ()): ("L", "L"), + (II, 1, (2,), 1, (8,), ()): ("L", "L"), + (MM, 1, (2,), 1, (8,), ()): ("L", "L"), (II, 1, (1,), 2, (8,), ()): ("L", "L;R"), (MM, 1, (1,), 2, (8,), ()): ("L", "L;R"), (II, 1, (1,), 1, (12,), ()): ("I;16", "I;12"), From 9154a6b22d2915d4ca689054a239ceeed078ef2d Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Thu, 4 May 2023 08:01:48 +1000 Subject: [PATCH 30/62] Added release notes --- docs/releasenotes/10.0.0.rst | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/docs/releasenotes/10.0.0.rst b/docs/releasenotes/10.0.0.rst index 3ee1a9973..1004ba57d 100644 --- a/docs/releasenotes/10.0.0.rst +++ b/docs/releasenotes/10.0.0.rst @@ -159,7 +159,8 @@ TODO Other Changes ============= -TODO -^^^^ +Support reading signed 8-bit TIFF images +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -TODO +TIFF images with signed integer data, 8 bits per sample and a photometric +interpretaton of BlackIsZero can now be read. From 2467db492e7e50efaf39d445c92190c713e40f6f Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Thu, 4 May 2023 08:15:48 +1000 Subject: [PATCH 31/62] Update CHANGES.rst [ci skip] --- CHANGES.rst | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/CHANGES.rst b/CHANGES.rst index 7b13900a8..93517e1cd 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -5,6 +5,18 @@ Changelog (Pillow) 10.0.0 (unreleased) ------------------- +- Added width argument to ImageDraw regular_polygon #7132 + [radarhere] + +- Support I mode for ImageFilter.BuiltinFilter #7108 + [radarhere] + +- Raise error from stderr of Linux ImageGrab.grabclipboard() command #7112 + [radarhere] + +- Added unpacker from I;16B to I;16 #7125 + [radarhere] + - Support float font sizes #7107 [radarhere] From 9f0c4164694ce99e14e7ec7fbfa8938acfb74823 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Thu, 4 May 2023 15:30:17 +1000 Subject: [PATCH 32/62] Update CHANGES.rst [ci skip] --- CHANGES.rst | 3 +++ 1 file changed, 3 insertions(+) diff --git a/CHANGES.rst b/CHANGES.rst index 93517e1cd..f8844daca 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -5,6 +5,9 @@ Changelog (Pillow) 10.0.0 (unreleased) ------------------- +- Support reading signed 8-bit TIFF images #7111 + [radarhere] + - Added width argument to ImageDraw regular_polygon #7132 [radarhere] From 3ae321832a52cf185ae2c10a5b17f7ea1c6f2dbe Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Thu, 4 May 2023 15:37:35 +1000 Subject: [PATCH 33/62] Added release notes for #7132 --- docs/releasenotes/10.0.0.rst | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/docs/releasenotes/10.0.0.rst b/docs/releasenotes/10.0.0.rst index 1004ba57d..e2005b710 100644 --- a/docs/releasenotes/10.0.0.rst +++ b/docs/releasenotes/10.0.0.rst @@ -135,10 +135,11 @@ TODO API Changes =========== -TODO -^^^^ +Added line width parameter to ImageDraw regular_polygon +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -TODO +An optional line ``width`` parameter has been added to +``ImageDraw.Draw.regular_polygon``. API Additions ============= From 5377b0735f44ad78184a1b0092a327e22203a02a Mon Sep 17 00:00:00 2001 From: Ishant Mrinal Haloi Date: Thu, 4 May 2023 21:43:57 +0530 Subject: [PATCH 34/62] add _repr_jpg_ for ipython display Signed-off-by: Ishant Mrinal Haloi --- Tests/test_file_jpeg.py | 13 +++++++++++++ src/PIL/Image.py | 23 ++++++++++++++++++++--- 2 files changed, 33 insertions(+), 3 deletions(-) diff --git a/Tests/test_file_jpeg.py b/Tests/test_file_jpeg.py index 73a00386f..3676c8f07 100644 --- a/Tests/test_file_jpeg.py +++ b/Tests/test_file_jpeg.py @@ -922,6 +922,19 @@ class TestFileJpeg: im.load() ImageFile.LOAD_TRUNCATED_IMAGES = False + def test_repr_jpg(self): + im = hopper() + + with Image.open(BytesIO(im._repr_jpg_())) as repr_jpg: + assert repr_jpg.format == "JPEG" + assert_image_equal(im, repr_jpg) + + def test_repr_jpg_error(self): + im = hopper("F") + + with pytest.raises(ValueError): + im._repr_jpg_() + @pytest.mark.skipif(not is_win32(), reason="Windows only") @skip_unless_feature("jpg") diff --git a/src/PIL/Image.py b/src/PIL/Image.py index bee9e23d0..557810f6c 100644 --- a/src/PIL/Image.py +++ b/src/PIL/Image.py @@ -633,19 +633,36 @@ class Image: ) ) - def _repr_png_(self): + def _repr_image(self, format): """iPython display hook support + :param format: Image format. :returns: png version of the image as bytes """ b = io.BytesIO() try: - self.save(b, "PNG") + self.save(b, format) except Exception as e: - msg = "Could not save to PNG for display" + msg = f"Could not save to {format} for display" raise ValueError(msg) from e return b.getvalue() + def _repr_png_(self): + """iPython display hook support for PNG format. + + :returns: png version of the image as bytes + """ + return self._repr_image("PNG") + + def _repr_jpg_(self): + """iPython display hook support for JPEG format. + + :returns: jpg version of the image as bytes + """ + return self._repr_image("JPEG") + + _repr_jpeg_ = _repr_jpg_ + @property def __array_interface__(self): # numpy array interface support From 93e507294bb5f54ea8ac43a596fd0d2677e2cdad Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Fri, 5 May 2023 08:19:43 +1000 Subject: [PATCH 35/62] Only assert image is similar --- Tests/test_file_jpeg.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/Tests/test_file_jpeg.py b/Tests/test_file_jpeg.py index 3676c8f07..0247527f5 100644 --- a/Tests/test_file_jpeg.py +++ b/Tests/test_file_jpeg.py @@ -922,18 +922,18 @@ class TestFileJpeg: im.load() ImageFile.LOAD_TRUNCATED_IMAGES = False - def test_repr_jpg(self): + def test_repr_jpeg(self): im = hopper() - with Image.open(BytesIO(im._repr_jpg_())) as repr_jpg: - assert repr_jpg.format == "JPEG" - assert_image_equal(im, repr_jpg) + with Image.open(BytesIO(im._repr_jpeg_())) as repr_jpeg: + assert repr_jpeg.format == "JPEG" + assert_image_similar(im, repr_jpeg, 17) - def test_repr_jpg_error(self): + def test_repr_jpeg_error(self): im = hopper("F") with pytest.raises(ValueError): - im._repr_jpg_() + im._repr_jpeg_() @pytest.mark.skipif(not is_win32(), reason="Windows only") From 04191d15f6fee33c50536991e734454195c2da8a Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Fri, 5 May 2023 17:54:42 +1000 Subject: [PATCH 36/62] Removed separate test for array tobytes() --- Tests/test_imagepath.py | 11 ----------- 1 file changed, 11 deletions(-) diff --git a/Tests/test_imagepath.py b/Tests/test_imagepath.py index 7a517b6f6..5082f9a79 100644 --- a/Tests/test_imagepath.py +++ b/Tests/test_imagepath.py @@ -63,17 +63,6 @@ def test_path_constructors(coords): assert list(p) == [(0.0, 1.0)] -def test_path_constructor_text(): - # Arrange - arr = array.array("f", (0, 1)) - - # Act - p = ImagePath.Path(arr.tobytes()) - - # Assert - assert list(p) == [(0.0, 1.0)] - - @pytest.mark.parametrize( "coords", ( From 17fbafb10b6fbb7d364ff4e6474149c12bc03a42 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Fri, 5 May 2023 18:12:10 +1000 Subject: [PATCH 37/62] Updated ImagePath tolist() default --- docs/reference/ImagePath.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/reference/ImagePath.rst b/docs/reference/ImagePath.rst index 7c1a3ad70..500096ef7 100644 --- a/docs/reference/ImagePath.rst +++ b/docs/reference/ImagePath.rst @@ -48,7 +48,7 @@ vector data. Path objects can be passed to the methods on the Maps the path through a function. -.. py:method:: PIL.ImagePath.Path.tolist(flat=0) +.. py:method:: PIL.ImagePath.Path.tolist(flat=False) Converts the path to a Python list [(x, y), …]. From 38c40d81d2d0a97e208c7f3bcf468a81176f6288 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Fri, 5 May 2023 18:25:05 +1000 Subject: [PATCH 38/62] Use boolean instead of integer --- Tests/test_imagepath.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Tests/test_imagepath.py b/Tests/test_imagepath.py index 8f8a9f449..5c40d4756 100644 --- a/Tests/test_imagepath.py +++ b/Tests/test_imagepath.py @@ -28,7 +28,7 @@ def test_path(): (6.0, 7.0), (8.0, 9.0), ] - assert p.tolist(1) == [0.0, 1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0] + assert p.tolist(True) == [0.0, 1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0] assert p.getbbox() == (0.0, 1.0, 8.0, 9.0) From 2d841e16c2d7b16f6fe0b156c79a13affd9ac630 Mon Sep 17 00:00:00 2001 From: Ishant Mrinal Haloi Date: Sat, 6 May 2023 10:31:58 +0530 Subject: [PATCH 39/62] Apply suggestions from code review Co-authored-by: Andrew Murray <3112309+radarhere@users.noreply.github.com> --- src/PIL/Image.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/PIL/Image.py b/src/PIL/Image.py index 557810f6c..3c0094817 100644 --- a/src/PIL/Image.py +++ b/src/PIL/Image.py @@ -637,7 +637,7 @@ class Image: """iPython display hook support :param format: Image format. - :returns: png version of the image as bytes + :returns: image as bytes, saved into the given format. """ b = io.BytesIO() try: From ccdce1791dab1e754df17d21a771c8ac073b7c58 Mon Sep 17 00:00:00 2001 From: Ishant Mrinal Haloi Date: Sat, 6 May 2023 10:35:28 +0530 Subject: [PATCH 40/62] rename format to image_format --- src/PIL/Image.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/PIL/Image.py b/src/PIL/Image.py index 3c0094817..33984e594 100644 --- a/src/PIL/Image.py +++ b/src/PIL/Image.py @@ -633,17 +633,17 @@ class Image: ) ) - def _repr_image(self, format): + def _repr_image(self, image_format): """iPython display hook support - :param format: Image format. + :param image_format: Image format. :returns: image as bytes, saved into the given format. """ b = io.BytesIO() try: - self.save(b, format) + self.save(b, image_format) except Exception as e: - msg = f"Could not save to {format} for display" + msg = f"Could not save to {image_format} for display" raise ValueError(msg) from e return b.getvalue() From 2f896ee4ac1f86cd05659c6a3053d38ed0b15aff Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Mon, 8 May 2023 21:16:34 +1000 Subject: [PATCH 41/62] Clarify that line() and polygon() include xy pixels --- docs/reference/ImageDraw.rst | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/docs/reference/ImageDraw.rst b/docs/reference/ImageDraw.rst index 29115120c..524f821fb 100644 --- a/docs/reference/ImageDraw.rst +++ b/docs/reference/ImageDraw.rst @@ -243,6 +243,7 @@ Methods .. py:method:: ImageDraw.line(xy, fill=None, width=0, joint=None) Draws a line between the coordinates in the ``xy`` list. + The coordinate pixels are included in the drawn line. :param xy: Sequence of either 2-tuples like ``[(x, y), (x, y), ...]`` or numeric values like ``[x, y, x, y, ...]``. @@ -287,7 +288,7 @@ Methods The polygon outline consists of straight lines between the given coordinates, plus a straight line between the last and the first - coordinate. + coordinate. The coordinate pixels are included in the drawn polygon. :param xy: Sequence of either 2-tuples like ``[(x, y), (x, y), ...]`` or numeric values like ``[x, y, x, y, ...]``. From bb18abc603a285b9c1c3705f03d7ee3955f935d7 Mon Sep 17 00:00:00 2001 From: nulano Date: Mon, 8 May 2023 22:30:11 +0100 Subject: [PATCH 42/62] prefer screenshots using XCB over gnome-screenshot --- docs/reference/ImageGrab.rst | 5 +++-- src/PIL/ImageGrab.py | 23 +++++++++++++---------- 2 files changed, 16 insertions(+), 12 deletions(-) diff --git a/docs/reference/ImageGrab.rst b/docs/reference/ImageGrab.rst index 3086ba8c3..6437307c0 100644 --- a/docs/reference/ImageGrab.rst +++ b/docs/reference/ImageGrab.rst @@ -15,8 +15,9 @@ or the clipboard to a PIL image memory. returned as an "RGBA" on macOS, or an "RGB" image otherwise. If the bounding box is omitted, the entire screen is copied. - On Linux, if ``xdisplay`` is ``None`` then ``gnome-screenshot`` will be used if it - is installed. To capture the default X11 display instead, pass ``xdisplay=""``. + On Linux, if ``xdisplay`` is ``None`` and the default X11 display does not return + a snapshot of the screen, ``gnome-screenshot`` will be used as fallback if it is + installed. To disable this behaviour, pass ``xdisplay=""`` instead. .. versionadded:: 1.1.3 (Windows), 3.0.0 (macOS), 7.1.0 (Linux) diff --git a/src/PIL/ImageGrab.py b/src/PIL/ImageGrab.py index 2592ba2df..db993836d 100644 --- a/src/PIL/ImageGrab.py +++ b/src/PIL/ImageGrab.py @@ -61,7 +61,17 @@ def grab(bbox=None, include_layered_windows=False, all_screens=False, xdisplay=N left, top, right, bottom = bbox im = im.crop((left - x0, top - y0, right - x0, bottom - y0)) return im - elif shutil.which("gnome-screenshot"): + try: + if not Image.core.HAVE_XCB: + msg = "Pillow was built without XCB support" + raise OSError(msg) + size, data = Image.core.grabscreen_x11(xdisplay) + im = Image.frombytes("RGB", size, data, "raw", "BGRX", size[0] * 4, 1) + if bbox: + im = im.crop(bbox) + return im + except OSError: + if xdisplay is None and shutil.which("gnome-screenshot"): fh, filepath = tempfile.mkstemp(".png") os.close(fh) subprocess.call(["gnome-screenshot", "-f", filepath]) @@ -73,15 +83,8 @@ def grab(bbox=None, include_layered_windows=False, all_screens=False, xdisplay=N im.close() return im_cropped return im - # use xdisplay=None for default display on non-win32/macOS systems - if not Image.core.HAVE_XCB: - msg = "Pillow was built without XCB support" - raise OSError(msg) - size, data = Image.core.grabscreen_x11(xdisplay) - im = Image.frombytes("RGB", size, data, "raw", "BGRX", size[0] * 4, 1) - if bbox: - im = im.crop(bbox) - return im + else: + raise def grabclipboard(): From b9b685fc5699cbeb18f18096e53a1130d1cd6789 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Wed, 10 May 2023 12:35:59 +1000 Subject: [PATCH 43/62] Updated harfbuzz to 7.3.0 --- winbuild/build_prepare.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/winbuild/build_prepare.py b/winbuild/build_prepare.py index 9b5fc5d18..1e5a54f64 100644 --- a/winbuild/build_prepare.py +++ b/winbuild/build_prepare.py @@ -337,9 +337,9 @@ deps = { "libs": [r"imagequant.lib"], }, "harfbuzz": { - "url": "https://github.com/harfbuzz/harfbuzz/archive/7.2.0.zip", - "filename": "harfbuzz-7.2.0.zip", - "dir": "harfbuzz-7.2.0", + "url": "https://github.com/harfbuzz/harfbuzz/archive/7.3.0.zip", + "filename": "harfbuzz-7.3.0.zip", + "dir": "harfbuzz-7.3.0", "license": "COPYING", "build": [ *cmds_cmake( From c68c508e27935f31ffa357e7e3bc7763cbe461e5 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Wed, 10 May 2023 13:25:35 +1000 Subject: [PATCH 44/62] Fixed joined corners for odd dimensions --- .../images/imagedraw_rounded_rectangle_x_odd.png | Bin 0 -> 565 bytes .../images/imagedraw_rounded_rectangle_y_odd.png | Bin 0 -> 527 bytes Tests/test_imagedraw.py | 2 ++ src/PIL/ImageDraw.py | 4 ++-- 4 files changed, 4 insertions(+), 2 deletions(-) create mode 100644 Tests/images/imagedraw_rounded_rectangle_x_odd.png create mode 100644 Tests/images/imagedraw_rounded_rectangle_y_odd.png diff --git a/Tests/images/imagedraw_rounded_rectangle_x_odd.png b/Tests/images/imagedraw_rounded_rectangle_x_odd.png new file mode 100644 index 0000000000000000000000000000000000000000..f23f1945e1fec32362830cc6e872c08956fe7758 GIT binary patch literal 565 zcmeAS@N?(olHy`uVBq!ia0vp^DImiE{w+sGn_n7?5SDmN3P}2njzRbCo#{c2^^YHh}*S}w5dCT|M zE9R}USk2jA&%3xWWowR(cyO##q+A$Fa4b`#&^6JO{qORB7e5qTwD-`)Up}=9!fJO| zY>HUZcg1$uF22YK;R<2iS#wsda}|3i+FGjhGgMXbv*XuKnWT5Ms_^&yc>Az@ zA4^o_6StUTA+6Q{U6F?LqSp{H{qj(<0ZTvZx)z3Ao!v58r+w;qX>O8ZUel0MKZ z`qzKc(vOPHX4$`@a&I0tNU%*>yN%8LYyLH*?5$5_)&3M;tIrb2-hRV-?bT~}OD0-R zJS)WH&7reUam`oxi0F04#b&FrOcRK3O5kiHi@}@tc3)ZP)6>g>Z}FD>zMYf)GjX6^Gd-AAG-p+a`)s@1J==Xy$i$t|mNo$vAZ{+h@*&h+msciMi&SuU3; zteYmFzL~S|*?fWJo?L}~D+Saaa20ObB(S`UyHL(X5C|?#{>56pvSWg^N}?GsIWTy- L`njxgN@xNA*hcbx literal 0 HcmV?d00001 diff --git a/Tests/images/imagedraw_rounded_rectangle_y_odd.png b/Tests/images/imagedraw_rounded_rectangle_y_odd.png new file mode 100644 index 0000000000000000000000000000000000000000..96441bc7289eb3e70f23df0f9569a72ca407046d GIT binary patch literal 527 zcmeAS@N?(olHy`uVBq!ia0vp^DIm66s@eeHX4ax2z2 zHumxGnrmG<+MA`k>ge%ZwwI%FHl2v*Kea_{s*cXIrB|NMkUq8kp?mumu8z`qnE?xw zpFgyk{PmcXW@+KFt&>x1_wGuPox02L@~uFla{c->cZ2v(ZrL{b%=OoYw>Rl5oz@)s zv#zi@NmVP<;DC1H)>?8~Y_h7tw1x<#B+d>5Bjd=enzOk-pU;aiv$pylHUFfL zfVYE=qxH2T+jU>IZ8eA#@?T@wc6uXs@S4--s_&fG#Vopy=IX&tG`Z(a= x1 - x0 + full_x = d >= x1 - x0 - 1 if full_x: # The two left and two right corners are joined d = x1 - x0 - full_y = d >= y1 - y0 + full_y = d >= y1 - y0 - 1 if full_y: # The two top and two bottom corners are joined d = y1 - y0 From 3ec03c6720e4a4a0d4bd5a893ccac6b1df14418a Mon Sep 17 00:00:00 2001 From: Andrew Murray <3112309+radarhere@users.noreply.github.com> Date: Wed, 10 May 2023 13:53:55 +1000 Subject: [PATCH 45/62] Only check for gnome-screenshot on Linux MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Ondrej Baranovič <3819630+nulano@users.noreply.github.com> --- src/PIL/ImageGrab.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/PIL/ImageGrab.py b/src/PIL/ImageGrab.py index db993836d..361077110 100644 --- a/src/PIL/ImageGrab.py +++ b/src/PIL/ImageGrab.py @@ -71,7 +71,11 @@ def grab(bbox=None, include_layered_windows=False, all_screens=False, xdisplay=N im = im.crop(bbox) return im except OSError: - if xdisplay is None and shutil.which("gnome-screenshot"): + if ( + xdisplay is None + and sys.platform not in ("darwin", "win32") + and shutil.which("gnome-screenshot") + ): fh, filepath = tempfile.mkstemp(".png") os.close(fh) subprocess.call(["gnome-screenshot", "-f", filepath]) From 8bbccba8250d4ec05a88fce3381f85c7dbb882e6 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Thu, 11 May 2023 08:13:33 +1000 Subject: [PATCH 46/62] Updated redirected URL --- docs/deprecations.rst | 2 +- docs/releasenotes/10.0.0.rst | 2 +- docs/releasenotes/9.2.0.rst | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/deprecations.rst b/docs/deprecations.rst index 45b2f4200..62687d869 100644 --- a/docs/deprecations.rst +++ b/docs/deprecations.rst @@ -210,7 +210,7 @@ open-source users (and will reach EOL on 2023-12-08 for commercial licence holde Support for PyQt5 and PySide2 has been removed from ``ImageQt``. Upgrade to `PyQt6 `_ or -`PySide6 `_ instead. +`PySide6 `_ instead. Image.coerce_e ~~~~~~~~~~~~~~ diff --git a/docs/releasenotes/10.0.0.rst b/docs/releasenotes/10.0.0.rst index e2005b710..d71ca0fa6 100644 --- a/docs/releasenotes/10.0.0.rst +++ b/docs/releasenotes/10.0.0.rst @@ -117,7 +117,7 @@ open-source users (and will reach EOL on 2023-12-08 for commercial licence holde Support for PyQt5 and PySide2 has been removed from ``ImageQt``. Upgrade to `PyQt6 `_ or -`PySide6 `_ instead. +`PySide6 `_ instead. Image.coerce_e ^^^^^^^^^^^^^^ diff --git a/docs/releasenotes/9.2.0.rst b/docs/releasenotes/9.2.0.rst index 8d8bfc9f8..b875edf8e 100644 --- a/docs/releasenotes/9.2.0.rst +++ b/docs/releasenotes/9.2.0.rst @@ -15,7 +15,7 @@ open-source users (and will reach EOL on 2023-12-08 for commercial licence holde Support for PyQt5 and PySide2 has been deprecated from ``ImageQt`` and will be removed in Pillow 10 (2023-07-01). Upgrade to `PyQt6 `_ or -`PySide6 `_ instead. +`PySide6 `_ instead. FreeTypeFont.getmask2 fill parameter ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ From 848fd7c2dbaf07bc8e0a24d8efe1400cc8140f99 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Thu, 11 May 2023 16:49:08 +1000 Subject: [PATCH 47/62] Added linkcheck_allowed_redirects --- docs/conf.py | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/docs/conf.py b/docs/conf.py index 2ebcd6b2e..a2c825292 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -317,6 +317,17 @@ def setup(app): app.add_css_file("css/dark.css") +linkcheck_allowed_redirects = { + r"https://bestpractices.coreinfrastructure.org/projects/6331": r"https://bestpractices.coreinfrastructure.org/en/.*", # noqa: E501 + r"https://badges.gitter.im/python-pillow/Pillow.svg": r"https://badges.gitter.im/repo.svg", # noqa: E501 + r"https://gitter.im/python-pillow/Pillow?.*": r"https://app.gitter.im/#/room/#python-pillow_Pillow:gitter.im?.*", # noqa: E501 + r"https://pillow.readthedocs.io/?badge=latest": r"https://pillow.readthedocs.io/en/stable/?badge=latest", # noqa: E501 + r"https://pillow.readthedocs.io": r"https://pillow.readthedocs.io/en/stable/", + r"https://tidelift.com/badges/package/pypi/Pillow?.*": r"https://img.shields.io/badge/.*", # noqa: E501 + r"https://zenodo.org/badge/17549/python-pillow/Pillow.svg": r"https://zenodo.org/badge/doi/[\.0-9]+/zenodo.[0-9]+.svg", # noqa: E501 + r"https://zenodo.org/badge/latestdoi/17549/python-pillow/Pillow": r"https://zenodo.org/record/[0-9]+", # noqa: E501 +} + # sphinx.ext.extlinks # This config is a dictionary of external sites, # mapping unique short aliases to a base URL and a prefix. From 7e29efd518b0b5b2d3c5ed25446f12cf90f63338 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Thu, 11 May 2023 20:08:10 +1000 Subject: [PATCH 48/62] Do not catch OSError raised when loading image --- src/PIL/ImageGrab.py | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/src/PIL/ImageGrab.py b/src/PIL/ImageGrab.py index 361077110..a51294cb5 100644 --- a/src/PIL/ImageGrab.py +++ b/src/PIL/ImageGrab.py @@ -66,10 +66,6 @@ def grab(bbox=None, include_layered_windows=False, all_screens=False, xdisplay=N msg = "Pillow was built without XCB support" raise OSError(msg) size, data = Image.core.grabscreen_x11(xdisplay) - im = Image.frombytes("RGB", size, data, "raw", "BGRX", size[0] * 4, 1) - if bbox: - im = im.crop(bbox) - return im except OSError: if ( xdisplay is None @@ -89,6 +85,11 @@ def grab(bbox=None, include_layered_windows=False, all_screens=False, xdisplay=N return im else: raise + else: + im = Image.frombytes("RGB", size, data, "raw", "BGRX", size[0] * 4, 1) + if bbox: + im = im.crop(bbox) + return im def grabclipboard(): From 46708099b10a99b3d35d7eae624e83daa6d67bf9 Mon Sep 17 00:00:00 2001 From: Ishant Mrinal Haloi Date: Fri, 12 May 2023 21:56:40 +0530 Subject: [PATCH 49/62] Apply suggestions from code review Co-authored-by: Andrew Murray <3112309+radarhere@users.noreply.github.com> --- src/PIL/Image.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/PIL/Image.py b/src/PIL/Image.py index 33984e594..21305d52a 100644 --- a/src/PIL/Image.py +++ b/src/PIL/Image.py @@ -654,15 +654,13 @@ class Image: """ return self._repr_image("PNG") - def _repr_jpg_(self): + def _repr_jpeg_(self): """iPython display hook support for JPEG format. :returns: jpg version of the image as bytes """ return self._repr_image("JPEG") - _repr_jpeg_ = _repr_jpg_ - @property def __array_interface__(self): # numpy array interface support From e063ed772c0be2b878e97d55b69d59c573cd40fa Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Sat, 13 May 2023 11:02:53 +1000 Subject: [PATCH 50/62] Update CHANGES.rst [ci skip] --- CHANGES.rst | 3 +++ 1 file changed, 3 insertions(+) diff --git a/CHANGES.rst b/CHANGES.rst index f8844daca..7dd99af99 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -5,6 +5,9 @@ Changelog (Pillow) 10.0.0 (unreleased) ------------------- +- Fixed joined corners for ImageDraw rounded_rectangle() odd dimensions #7151 + [radarhere] + - Support reading signed 8-bit TIFF images #7111 [radarhere] From 2db9c68571f3be1d29888b3497f4e2af518a2d36 Mon Sep 17 00:00:00 2001 From: Ishant Mrinal Haloi Date: Sat, 13 May 2023 07:32:02 +0530 Subject: [PATCH 51/62] Apply suggestions from code review MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Ondrej Baranovič <3819630+nulano@users.noreply.github.com> Co-authored-by: Andrew Murray <3112309+radarhere@users.noreply.github.com> --- src/PIL/Image.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/PIL/Image.py b/src/PIL/Image.py index 21305d52a..3522ff6d0 100644 --- a/src/PIL/Image.py +++ b/src/PIL/Image.py @@ -634,8 +634,7 @@ class Image: ) def _repr_image(self, image_format): - """iPython display hook support - + """Helper function for iPython display hook :param image_format: Image format. :returns: image as bytes, saved into the given format. """ @@ -657,7 +656,7 @@ class Image: def _repr_jpeg_(self): """iPython display hook support for JPEG format. - :returns: jpg version of the image as bytes + :returns: jpeg version of the image as bytes """ return self._repr_image("JPEG") From 59b7a48570cd9c7a2fae1e0876c5072bdd780338 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Sat, 13 May 2023 12:24:50 +1000 Subject: [PATCH 52/62] Updated docstrings --- src/PIL/Image.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/PIL/Image.py b/src/PIL/Image.py index 3522ff6d0..105c83a8b 100644 --- a/src/PIL/Image.py +++ b/src/PIL/Image.py @@ -634,7 +634,8 @@ class Image: ) def _repr_image(self, image_format): - """Helper function for iPython display hook + """Helper function for iPython display hook. + :param image_format: Image format. :returns: image as bytes, saved into the given format. """ @@ -1122,7 +1123,6 @@ class Image: Available methods are :data:`Dither.NONE` or :data:`Dither.FLOYDSTEINBERG` (default). :returns: A new image - """ self.load() From 6df8716025686fab55b7565df489e01f3a23e5b4 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Sat, 13 May 2023 21:38:01 +1000 Subject: [PATCH 53/62] Update grabclipboard() documentation after #6783 --- docs/reference/ImageGrab.rst | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/docs/reference/ImageGrab.rst b/docs/reference/ImageGrab.rst index 3086ba8c3..10c580a74 100644 --- a/docs/reference/ImageGrab.rst +++ b/docs/reference/ImageGrab.rst @@ -39,9 +39,11 @@ or the clipboard to a PIL image memory. .. py:function:: grabclipboard() - Take a snapshot of the clipboard image, if any. Only macOS and Windows are currently supported. + Take a snapshot of the clipboard image, if any. - .. versionadded:: 1.1.4 (Windows), 3.3.0 (macOS) + On Linux, ``wl-paste`` or ``xclip`` is required. + + .. versionadded:: 1.1.4 (Windows), 3.3.0 (macOS), 9.4.0 (Linux) :return: On Windows, an image, a list of filenames, or None if the clipboard does not contain image data or filenames. @@ -49,3 +51,5 @@ or the clipboard to a PIL image memory. On Mac, an image, or None if the clipboard does not contain image data. + + On Linux, an image. From f3283837630e25f88f1d8f73961c898d88ab1aee Mon Sep 17 00:00:00 2001 From: Ishant Mrinal Haloi Date: Sun, 14 May 2023 11:11:56 +0530 Subject: [PATCH 54/62] Apply suggestions from code review Co-authored-by: Hugo van Kemenade --- src/PIL/Image.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/PIL/Image.py b/src/PIL/Image.py index 105c83a8b..e0fb6a885 100644 --- a/src/PIL/Image.py +++ b/src/PIL/Image.py @@ -650,14 +650,14 @@ class Image: def _repr_png_(self): """iPython display hook support for PNG format. - :returns: png version of the image as bytes + :returns: PNG version of the image as bytes """ return self._repr_image("PNG") def _repr_jpeg_(self): """iPython display hook support for JPEG format. - :returns: jpeg version of the image as bytes + :returns: JPEG version of the image as bytes """ return self._repr_image("JPEG") From 9754c8d18dc8f013193d18810b9de4f2b68694ff Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Sun, 14 May 2023 22:42:39 +1000 Subject: [PATCH 55/62] Added release notes --- docs/releasenotes/10.0.0.rst | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/docs/releasenotes/10.0.0.rst b/docs/releasenotes/10.0.0.rst index e2005b710..ececfa20d 100644 --- a/docs/releasenotes/10.0.0.rst +++ b/docs/releasenotes/10.0.0.rst @@ -160,6 +160,18 @@ TODO Other Changes ============= +Support display_jpeg() in IPython +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +In addition to ``display()`` and ``display_png``, ``display_jpeg()`` can now +also be used to display images in IPython:: + + from PIL import Image + from IPython.display import display_jpeg + + im = Image.new("RGB", (100, 100), (255, 0, 0)) + display_jpeg(im) + Support reading signed 8-bit TIFF images ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ From ce22ad96b71dd3a64ed65d00f36640b81beafd31 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Mon, 15 May 2023 09:47:09 +1000 Subject: [PATCH 56/62] Update CHANGES.rst [ci skip] --- CHANGES.rst | 3 +++ 1 file changed, 3 insertions(+) diff --git a/CHANGES.rst b/CHANGES.rst index 7dd99af99..b3a6c45a4 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -5,6 +5,9 @@ Changelog (Pillow) 10.0.0 (unreleased) ------------------- +- Prefer screenshots using XCB over gnome-screenshot #7143 + [nulano, radarhere] + - Fixed joined corners for ImageDraw rounded_rectangle() odd dimensions #7151 [radarhere] From 82e57b8a90bf0c062f6fb87b4bc3ed465e2b2c88 Mon Sep 17 00:00:00 2001 From: Hugo van Kemenade Date: Fri, 28 Apr 2023 12:53:17 +0300 Subject: [PATCH 57/62] Build only PDF in addition to default html --- .readthedocs.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.readthedocs.yml b/.readthedocs.yml index ec3300dd1..bda03d944 100644 --- a/.readthedocs.yml +++ b/.readthedocs.yml @@ -1,6 +1,6 @@ version: 2 -formats: all +formats: [pdf] build: os: ubuntu-22.04 From ac2d283065e38672a844730e0b11dd9835d588b8 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Tue, 16 May 2023 07:08:02 +1000 Subject: [PATCH 58/62] Update CHANGES.rst [ci skip] --- CHANGES.rst | 3 +++ 1 file changed, 3 insertions(+) diff --git a/CHANGES.rst b/CHANGES.rst index b3a6c45a4..c67b9b432 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -5,6 +5,9 @@ Changelog (Pillow) 10.0.0 (unreleased) ------------------- +- Use "/sbin/ldconfig" if ldconfig is not found #7068 + [radarhere] + - Prefer screenshots using XCB over gnome-screenshot #7143 [nulano, radarhere] From 53e73fd0941a8148e293245dfe9edf8412dacbaa Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Wed, 17 May 2023 08:21:59 +1000 Subject: [PATCH 59/62] Updated fribidi to 1.0.13 --- winbuild/build_prepare.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/winbuild/build_prepare.py b/winbuild/build_prepare.py index 1e5a54f64..19552f3c7 100644 --- a/winbuild/build_prepare.py +++ b/winbuild/build_prepare.py @@ -352,12 +352,12 @@ deps = { "libs": [r"*.lib"], }, "fribidi": { - "url": "https://github.com/fribidi/fribidi/archive/v1.0.12.zip", - "filename": "fribidi-1.0.12.zip", - "dir": "fribidi-1.0.12", + "url": "https://github.com/fribidi/fribidi/archive/v1.0.13.zip", + "filename": "fribidi-1.0.13.zip", + "dir": "fribidi-1.0.13", "license": "COPYING", "build": [ - cmd_copy(r"COPYING", r"{bin_dir}\fribidi-1.0.12-COPYING"), + cmd_copy(r"COPYING", r"{bin_dir}\fribidi-1.0.13-COPYING"), cmd_copy(r"{winbuild_dir}\fribidi.cmake", r"CMakeLists.txt"), *cmds_cmake("fribidi"), ], From 0e21e47768315f6af1afcbd88ee24a4ab68b5d36 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Wed, 17 May 2023 08:25:25 +1000 Subject: [PATCH 60/62] Update CHANGES.rst [ci skip] --- CHANGES.rst | 3 +++ 1 file changed, 3 insertions(+) diff --git a/CHANGES.rst b/CHANGES.rst index c67b9b432..c79274d64 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -5,6 +5,9 @@ Changelog (Pillow) 10.0.0 (unreleased) ------------------- +- Added `_repr_jpeg_` for IPython display_jpeg #7135 + [n3011, radarhere, nulano] + - Use "/sbin/ldconfig" if ldconfig is not found #7068 [radarhere] From 599979caae111c22bd15f0103320bf74eb53d963 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Wed, 17 May 2023 16:53:42 +1000 Subject: [PATCH 61/62] Update CHANGES.rst [ci skip] --- CHANGES.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGES.rst b/CHANGES.rst index c79274d64..626b8b231 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -5,7 +5,7 @@ Changelog (Pillow) 10.0.0 (unreleased) ------------------- -- Added `_repr_jpeg_` for IPython display_jpeg #7135 +- Added _repr_jpeg_() for IPython display_jpeg #7135 [n3011, radarhere, nulano] - Use "/sbin/ldconfig" if ldconfig is not found #7068 From dc6d0641b3c5e93b26b214a251754e549b84260a Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Mon, 22 May 2023 19:39:25 +1000 Subject: [PATCH 62/62] Updated redirected URLs --- codecov.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/codecov.yml b/codecov.yml index f3afccc1c..b794632fa 100644 --- a/codecov.yml +++ b/codecov.yml @@ -1,9 +1,9 @@ -# Documentation: https://docs.codecov.io/docs/codecov-yaml +# Documentation: https://docs.codecov.com/docs/codecov-yaml codecov: # Avoid "Missing base report" due to committing CHANGES.rst with "[CI skip]" # https://github.com/codecov/support/issues/363 - # https://docs.codecov.io/docs/comparing-commits + # https://docs.codecov.com/docs/comparing-commits allow_coverage_offsets: true comment: false