{
    "file_diffs": [
        {
            "old_file_content": "from __future__ import annotations\n\nimport contextlib\nimport os.path\n\nimport pytest\n\nfrom PIL import Image, ImageColor, ImageDraw, ImageFont, features\nfrom PIL._typing import Coords\n\nfrom .helper import (\n    assert_image_equal,\n    assert_image_equal_tofile,\n    assert_image_similar_tofile,\n    hopper,\n    skip_unless_feature,\n)\n\nBLACK = (0, 0, 0)\nWHITE = (255, 255, 255)\nGRAY = (190, 190, 190)\nDEFAULT_MODE = \"RGB\"\nIMAGES_PATH = os.path.join(\"Tests\", \"images\", \"imagedraw\")\n\n# Image size\nW, H = 100, 100\n\n# Bounding box points\nX0 = int(W / 4)\nX1 = int(X0 * 3)\nY0 = int(H / 4)\nY1 = int(X0 * 3)\n\n# Bounding boxes\nBBOX = (((X0, Y0), (X1, Y1)), [(X0, Y0), (X1, Y1)], (X0, Y0, X1, Y1), [X0, Y0, X1, Y1])\n\n# Coordinate sequences\nPOINTS = (\n    ((10, 10), (20, 40), (30, 30)),\n    [(10, 10), (20, 40), (30, 30)],\n    (10, 10, 20, 40, 30, 30),\n    [10, 10, 20, 40, 30, 30],\n)\n\nKITE_POINTS = (\n    ((10, 50), (70, 10), (90, 50), (70, 90), (10, 50)),\n    [(10, 50), (70, 10), (90, 50), (70, 90), (10, 50)],\n)\n\n\ndef test_sanity() -> None:\n    im = hopper(\"RGB\").copy()\n\n    draw = ImageDraw.ImageDraw(im)\n    draw = ImageDraw.Draw(im)\n\n    draw.ellipse(list(range(4)))\n    draw.line(list(range(10)))\n    draw.polygon(list(range(100)))\n    draw.rectangle(list(range(4)))\n\n\ndef test_valueerror() -> None:\n    with Image.open(\"Tests/images/chi.gif\") as im:\n        draw = ImageDraw.Draw(im)\n        draw.line((0, 0), fill=(0, 0, 0))\n\n\ndef test_mode_mismatch() -> None:\n    im = hopper(\"RGB\").copy()\n\n    with pytest.raises(ValueError):\n        ImageDraw.ImageDraw(im, mode=\"L\")\n\n\n@pytest.mark.parametrize(\"bbox\", BBOX)\n@pytest.mark.parametrize(\"start, end\", ((0, 180), (0.5, 180.4)))\ndef test_arc(bbox: Coords, start: float, end: float) -> None:\n    # Arrange\n    im = Image.new(\"RGB\", (W, H))\n    draw = ImageDraw.Draw(im)\n\n    # Act\n    draw.arc(bbox, start, end)\n\n    # Assert\n    assert_image_similar_tofile(im, \"Tests/images/imagedraw_arc.png\", 1)\n\n\n@pytest.mark.parametrize(\"bbox\", BBOX)\ndef test_arc_end_le_start(bbox: Coords) -> None:\n    # Arrange\n    im = Image.new(\"RGB\", (W, H))\n    draw = ImageDraw.Draw(im)\n    start = 270.5\n    end = 0\n\n    # Act\n    draw.arc(bbox, start=start, end=end)\n\n    # Assert\n    assert_image_equal_tofile(im, \"Tests/images/imagedraw_arc_end_le_start.png\")\n\n\n@pytest.mark.parametrize(\"bbox\", BBOX)\ndef test_arc_no_loops(bbox: Coords) -> None:\n    # No need to go in loops\n    # Arrange\n    im = Image.new(\"RGB\", (W, H))\n    draw = ImageDraw.Draw(im)\n    start = 5\n    end = 370\n\n    # Act\n    draw.arc(bbox, start=start, end=end)\n\n    # Assert\n    assert_image_similar_tofile(im, \"Tests/images/imagedraw_arc_no_loops.png\", 1)\n\n\n@pytest.mark.parametrize(\"bbox\", BBOX)\ndef test_arc_width(bbox: Coords) -> None:\n    # Arrange\n    im = Image.new(\"RGB\", (W, H))\n    draw = ImageDraw.Draw(im)\n\n    # Act\n    draw.arc(bbox, 10, 260, width=5)\n\n    # Assert\n    assert_image_similar_tofile(im, \"Tests/images/imagedraw_arc_width.png\", 1)\n\n\n@pytest.mark.parametrize(\"bbox\", BBOX)\ndef test_arc_width_pieslice_large(bbox: Coords) -> None:\n    # Tests an arc with a large enough width that it is a pieslice\n    # Arrange\n    im = Image.new(\"RGB\", (W, H))\n    draw = ImageDraw.Draw(im)\n\n    # Act\n    draw.arc(bbox, 10, 260, fill=\"yellow\", width=100)\n\n    # Assert\n    assert_image_similar_tofile(im, \"Tests/images/imagedraw_arc_width_pieslice.png\", 1)\n\n\n@pytest.mark.parametrize(\"bbox\", BBOX)\ndef test_arc_width_fill(bbox: Coords) -> None:\n    # Arrange\n    im = Image.new(\"RGB\", (W, H))\n    draw = ImageDraw.Draw(im)\n\n    # Act\n    draw.arc(bbox, 10, 260, fill=\"yellow\", width=5)\n\n    # Assert\n    assert_image_similar_tofile(im, \"Tests/images/imagedraw_arc_width_fill.png\", 1)\n\n\n@pytest.mark.parametrize(\"bbox\", BBOX)\ndef test_arc_width_non_whole_angle(bbox: Coords) -> None:\n    # Arrange\n    im = Image.new(\"RGB\", (W, H))\n    draw = ImageDraw.Draw(im)\n    expected = \"Tests/images/imagedraw_arc_width_non_whole_angle.png\"\n\n    # Act\n    draw.arc(bbox, 10, 259.5, width=5)\n\n    # Assert\n    assert_image_similar_tofile(im, expected, 1)\n\n\ndef test_arc_high() -> None:\n    # Arrange\n    im = Image.new(\"RGB\", (200, 200))\n    draw = ImageDraw.Draw(im)\n\n    # Act\n    draw.arc([10, 10, 89, 189], 20, 330, width=20, fill=\"white\")\n    draw.arc([110, 10, 189, 189], 20, 150, width=20, fill=\"white\")\n\n    # Assert\n    assert_image_equal_tofile(im, \"Tests/images/imagedraw_arc_high.png\")\n\n\ndef test_bitmap() -> None:\n    # Arrange\n    im = Image.new(\"RGB\", (W, H))\n    draw = ImageDraw.Draw(im)\n    with Image.open(\"Tests/images/pil123rgba.png\") as small:\n        small = small.resize((50, 50), Image.Resampling.NEAREST)\n\n        # Act\n        draw.bitmap((10, 10), small)\n\n    # Assert\n    assert_image_equal_tofile(im, \"Tests/images/imagedraw_bitmap.png\")\n\n\n@pytest.mark.parametrize(\"mode\", (\"RGB\", \"L\"))\n@pytest.mark.parametrize(\"bbox\", BBOX)\ndef test_chord(mode: str, bbox: Coords) -> None:\n    # Arrange\n    im = Image.new(mode, (W, H))\n    draw = ImageDraw.Draw(im)\n    expected = f\"Tests/images/imagedraw_chord_{mode}.png\"\n\n    # Act\n    draw.chord(bbox, 0, 180, fill=\"red\", outline=\"yellow\")\n\n    # Assert\n    assert_image_similar_tofile(im, expected, 1)\n\n\n@pytest.mark.parametrize(\"bbox\", BBOX)\ndef test_chord_width(bbox: Coords) -> None:\n    # Arrange\n    im = Image.new(\"RGB\", (W, H))\n    draw = ImageDraw.Draw(im)\n\n    # Act\n    draw.chord(bbox, 10, 260, outline=\"yellow\", width=5)\n\n    # Assert\n    assert_image_similar_tofile(im, \"Tests/images/imagedraw_chord_width.png\", 1)\n\n\n@pytest.mark.parametrize(\"bbox\", BBOX)\ndef test_chord_width_fill(bbox: Coords) -> None:\n    # Arrange\n    im = Image.new(\"RGB\", (W, H))\n    draw = ImageDraw.Draw(im)\n\n    # Act\n    draw.chord(bbox, 10, 260, fill=\"red\", outline=\"yellow\", width=5)\n\n    # Assert\n    assert_image_similar_tofile(im, \"Tests/images/imagedraw_chord_width_fill.png\", 1)\n\n\n@pytest.mark.parametrize(\"bbox\", BBOX)\ndef test_chord_zero_width(bbox: Coords) -> None:\n    # Arrange\n    im = Image.new(\"RGB\", (W, H))\n    draw = ImageDraw.Draw(im)\n\n    # Act\n    draw.chord(bbox, 10, 260, fill=\"red\", outline=\"yellow\", width=0)\n\n    # Assert\n    assert_image_equal_tofile(im, \"Tests/images/imagedraw_chord_zero_width.png\")\n\n\ndef test_chord_too_fat() -> None:\n    # Arrange\n    im = Image.new(\"RGB\", (100, 100))\n    draw = ImageDraw.Draw(im)\n\n    # Act\n    draw.chord([-150, -150, 99, 99], 15, 60, width=10, fill=\"white\", outline=\"red\")\n\n    # Assert\n    assert_image_equal_tofile(im, \"Tests/images/imagedraw_chord_too_fat.png\")\n\n\n@pytest.mark.parametrize(\"mode\", (\"RGB\", \"L\"))\n@pytest.mark.parametrize(\"bbox\", BBOX)\ndef test_ellipse(mode: str, bbox: Coords) -> None:\n    # Arrange\n    im = Image.new(mode, (W, H))\n    draw = ImageDraw.Draw(im)\n    expected = f\"Tests/images/imagedraw_ellipse_{mode}.png\"\n\n    # Act\n    draw.ellipse(bbox, fill=\"green\", outline=\"blue\")\n\n    # Assert\n    assert_image_similar_tofile(im, expected, 1)\n\n\n@pytest.mark.parametrize(\"bbox\", BBOX)\ndef test_ellipse_translucent(bbox: Coords) -> None:\n    # Arrange\n    im = Image.new(\"RGB\", (W, H))\n    draw = ImageDraw.Draw(im, \"RGBA\")\n\n    # Act\n    draw.ellipse(bbox, fill=(0, 255, 0, 127))\n\n    # Assert\n    expected = \"Tests/images/imagedraw_ellipse_translucent.png\"\n    assert_image_similar_tofile(im, expected, 1)\n\n\ndef test_ellipse_edge() -> None:\n    # Arrange\n    im = Image.new(\"RGB\", (W, H))\n    draw = ImageDraw.Draw(im)\n\n    # Act\n    draw.ellipse(((0, 0), (W - 1, H - 1)), fill=\"white\")\n\n    # Assert\n    assert_image_similar_tofile(im, \"Tests/images/imagedraw_ellipse_edge.png\", 1)\n\n\ndef test_ellipse_symmetric() -> None:\n    for width, bbox in (\n        (100, (24, 24, 75, 75)),\n        (101, (25, 25, 75, 75)),\n    ):\n        im = Image.new(\"RGB\", (width, 100))\n        draw = ImageDraw.Draw(im)\n        draw.ellipse(bbox, fill=\"green\", outline=\"blue\")\n        assert_image_equal(im, im.transpose(Image.Transpose.FLIP_LEFT_RIGHT))\n\n\n@pytest.mark.parametrize(\"bbox\", BBOX)\ndef test_ellipse_width(bbox: Coords) -> None:\n    # Arrange\n    im = Image.new(\"RGB\", (W, H))\n    draw = ImageDraw.Draw(im)\n\n    # Act\n    draw.ellipse(bbox, outline=\"blue\", width=5)\n\n    # Assert\n    assert_image_similar_tofile(im, \"Tests/images/imagedraw_ellipse_width.png\", 1)\n\n\ndef test_ellipse_width_large() -> None:\n    # Arrange\n    im = Image.new(\"RGB\", (500, 500))\n    draw = ImageDraw.Draw(im)\n\n    # Act\n    draw.ellipse((25, 25, 475, 475), outline=\"blue\", width=75)\n\n    # Assert\n    assert_image_similar_tofile(im, \"Tests/images/imagedraw_ellipse_width_large.png\", 1)\n\n\n@pytest.mark.parametrize(\"bbox\", BBOX)\ndef test_ellipse_width_fill(bbox: Coords) -> None:\n    # Arrange\n    im = Image.new(\"RGB\", (W, H))\n    draw = ImageDraw.Draw(im)\n\n    # Act\n    draw.ellipse(bbox, fill=\"green\", outline=\"blue\", width=5)\n\n    # Assert\n    assert_image_similar_tofile(im, \"Tests/images/imagedraw_ellipse_width_fill.png\", 1)\n\n\n@pytest.mark.parametrize(\"bbox\", BBOX)\ndef test_ellipse_zero_width(bbox: Coords) -> None:\n    # Arrange\n    im = Image.new(\"RGB\", (W, H))\n    draw = ImageDraw.Draw(im)\n\n    # Act\n    draw.ellipse(bbox, fill=\"green\", outline=\"blue\", width=0)\n\n    # Assert\n    assert_image_equal_tofile(im, \"Tests/images/imagedraw_ellipse_zero_width.png\")\n\n\ndef ellipse_various_sizes_helper(filled: bool) -> Image.Image:\n    ellipse_sizes = range(32)\n    image_size = sum(ellipse_sizes) + len(ellipse_sizes) + 1\n    im = Image.new(\"RGB\", (image_size, image_size))\n    draw = ImageDraw.Draw(im)\n\n    x = 1\n    for w in ellipse_sizes:\n        y = 1\n        for h in ellipse_sizes:\n            x1 = x + w\n            if w:\n                x1 -= 1\n            y1 = y + h\n            if h:\n                y1 -= 1\n            border = [x, y, x1, y1]\n            if filled:\n                draw.ellipse(border, fill=\"white\")\n            else:\n                draw.ellipse(border, outline=\"white\")\n            y += h + 1\n        x += w + 1\n\n    return im\n\n\ndef test_ellipse_various_sizes() -> None:\n    im = ellipse_various_sizes_helper(False)\n\n    assert_image_equal_tofile(im, \"Tests/images/imagedraw_ellipse_various_sizes.png\")\n\n\ndef test_ellipse_various_sizes_filled() -> None:\n    im = ellipse_various_sizes_helper(True)\n\n    assert_image_equal_tofile(\n        im, \"Tests/images/imagedraw_ellipse_various_sizes_filled.png\"\n    )\n\n\n@pytest.mark.parametrize(\"points\", POINTS)\ndef test_line(points: Coords) -> None:\n    # Arrange\n    im = Image.new(\"RGB\", (W, H))\n    draw = ImageDraw.Draw(im)\n\n    # Act\n    draw.line(points, fill=\"yellow\", width=2)\n\n    # Assert\n    assert_image_equal_tofile(im, \"Tests/images/imagedraw_line.png\")\n\n\ndef test_shape1() -> None:\n    # Arrange\n    im = Image.new(\"RGB\", (100, 100), \"white\")\n    draw = ImageDraw.Draw(im)\n    x0, y0 = 5, 5\n    x1, y1 = 5, 50\n    x2, y2 = 95, 50\n    x3, y3 = 95, 5\n\n    # Act\n    s = ImageDraw.Outline()\n    s.move(x0, y0)\n    s.curve(x1, y1, x2, y2, x3, y3)\n    s.line(x0, y0)\n\n    draw.shape(s, fill=1)\n\n    # Assert\n    assert_image_equal_tofile(im, \"Tests/images/imagedraw_shape1.png\")\n\n\ndef test_shape2() -> None:\n    # Arrange\n    im = Image.new(\"RGB\", (100, 100), \"white\")\n    draw = ImageDraw.Draw(im)\n    x0, y0 = 95, 95\n    x1, y1 = 95, 50\n    x2, y2 = 5, 50\n    x3, y3 = 5, 95\n\n    # Act\n    s = ImageDraw.Outline()\n    s.move(x0, y0)\n    s.curve(x1, y1, x2, y2, x3, y3)\n    s.line(x0, y0)\n\n    draw.shape(s, outline=\"blue\")\n\n    # Assert\n    assert_image_equal_tofile(im, \"Tests/images/imagedraw_shape2.png\")\n\n\ndef test_transform() -> None:\n    # Arrange\n    im = Image.new(\"RGB\", (100, 100), \"white\")\n    expected = im.copy()\n    draw = ImageDraw.Draw(im)\n\n    # Act\n    s = ImageDraw.Outline()\n    s.line(0, 0)\n    s.transform((0, 0, 0, 0, 0, 0))\n\n    draw.shape(s, fill=1)\n\n    # Assert\n    assert_image_equal(im, expected)\n\n\n@pytest.mark.parametrize(\"bbox\", BBOX)\n@pytest.mark.parametrize(\"start, end\", ((-92, 46), (-92.2, 46.2)))\ndef test_pieslice(bbox: Coords, start: float, end: float) -> None:\n    # Arrange\n    im = Image.new(\"RGB\", (W, H))\n    draw = ImageDraw.Draw(im)\n\n    # Act\n    draw.pieslice(bbox, start, end, fill=\"white\", outline=\"blue\")\n\n    # Assert\n    assert_image_similar_tofile(im, \"Tests/images/imagedraw_pieslice.png\", 1)\n\n\n@pytest.mark.parametrize(\"bbox\", BBOX)\ndef test_pieslice_width(bbox: Coords) -> None:\n    # Arrange\n    im = Image.new(\"RGB\", (W, H))\n    draw = ImageDraw.Draw(im)\n\n    # Act\n    draw.pieslice(bbox, 10, 260, outline=\"blue\", width=5)\n\n    # Assert\n    assert_image_similar_tofile(im, \"Tests/images/imagedraw_pieslice_width.png\", 1)\n\n\n@pytest.mark.parametrize(\"bbox\", BBOX)\ndef test_pieslice_width_fill(bbox: Coords) -> None:\n    # Arrange\n    im = Image.new(\"RGB\", (W, H))\n    draw = ImageDraw.Draw(im)\n    expected = \"Tests/images/imagedraw_pieslice_width_fill.png\"\n\n    # Act\n    draw.pieslice(bbox, 10, 260, fill=\"white\", outline=\"blue\", width=5)\n\n    # Assert\n    assert_image_similar_tofile(im, expected, 1)\n\n\n@pytest.mark.parametrize(\"bbox\", BBOX)\ndef test_pieslice_zero_width(bbox: Coords) -> None:\n    # Arrange\n    im = Image.new(\"RGB\", (W, H))\n    draw = ImageDraw.Draw(im)\n\n    # Act\n    draw.pieslice(bbox, 10, 260, fill=\"white\", outline=\"blue\", width=0)\n\n    # Assert\n    assert_image_equal_tofile(im, \"Tests/images/imagedraw_pieslice_zero_width.png\")\n\n\ndef test_pieslice_wide() -> None:\n    # Arrange\n    im = Image.new(\"RGB\", (200, 100))\n    draw = ImageDraw.Draw(im)\n\n    # Act\n    draw.pieslice([0, 0, 199, 99], 190, 170, width=10, fill=\"white\", outline=\"red\")\n\n    # Assert\n    assert_image_equal_tofile(im, \"Tests/images/imagedraw_pieslice_wide.png\")\n\n\ndef test_pieslice_no_spikes() -> None:\n    im = Image.new(\"RGB\", (161, 161), \"white\")\n    draw = ImageDraw.Draw(im)\n    cxs = (\n        [140] * 3\n        + list(range(140, 19, -20))\n        + [20] * 5\n        + list(range(20, 141, 20))\n        + [140] * 2\n    )\n    cys = (\n        list(range(80, 141, 20))\n        + [140] * 5\n        + list(range(140, 19, -20))\n        + [20] * 5\n        + list(range(20, 80, 20))\n    )\n\n    for cx, cy, angle in zip(cxs, cys, range(0, 360, 15)):\n        draw.pieslice(\n            [cx - 100, cy - 100, cx + 100, cy + 100], angle, angle + 1, fill=\"black\"\n        )\n        draw.point([cx, cy], fill=\"red\")\n\n    im_pre_erase = im.copy()\n    draw.rectangle([21, 21, 139, 139], fill=\"white\")\n\n    assert_image_equal(im, im_pre_erase)\n\n\n@pytest.mark.parametrize(\"points\", POINTS)\ndef test_point(points: Coords) -> None:\n    # Arrange\n    im = Image.new(\"RGB\", (W, H))\n    draw = ImageDraw.Draw(im)\n\n    # Act\n    draw.point(points, fill=\"yellow\")\n\n    # Assert\n    assert_image_equal_tofile(im, \"Tests/images/imagedraw_point.png\")\n\n\ndef test_point_I16() -> None:\n    # Arrange\n    im = Image.new(\"I;16\", (1, 1))\n    draw = ImageDraw.Draw(im)\n\n    # Act\n    draw.point((0, 0), fill=0x1234)\n\n    # Assert\n    assert im.getpixel((0, 0)) == 0x1234\n\n\n@pytest.mark.parametrize(\"points\", POINTS)\ndef test_polygon(points: Coords) -> None:\n    # Arrange\n    im = Image.new(\"RGB\", (W, H))\n    draw = ImageDraw.Draw(im)\n\n    # Act\n    draw.polygon(points, fill=\"red\", outline=\"blue\")\n\n    # Assert\n    assert_image_equal_tofile(im, \"Tests/images/imagedraw_polygon.png\")\n\n\n@pytest.mark.parametrize(\"mode\", (\"RGB\", \"L\"))\n@pytest.mark.parametrize(\"kite_points\", KITE_POINTS)\ndef test_polygon_kite(\n    mode: str, kite_points: tuple[tuple[int, int], ...] | list[tuple[int, int]]\n) -> None:\n    # Test drawing lines of different gradients (dx>dy, dy>dx) and\n    # vertical (dx==0) and horizontal (dy==0) lines\n    # Arrange\n    im = Image.new(mode, (W, H))\n    draw = ImageDraw.Draw(im)\n    expected = f\"Tests/images/imagedraw_polygon_kite_{mode}.png\"\n\n    # Act\n    draw.polygon(kite_points, fill=\"blue\", outline=\"yellow\")\n\n    # Assert\n    assert_image_equal_tofile(im, expected)\n\n\ndef test_polygon_1px_high() -> None:\n    # Test drawing a 1px high polygon\n    # Arrange\n    im = Image.new(\"RGB\", (3, 3))\n    draw = ImageDraw.Draw(im)\n    expected = \"Tests/images/imagedraw_polygon_1px_high.png\"\n\n    # Act\n    draw.polygon([(0, 1), (0, 1), (2, 1), (2, 1)], \"#f00\")\n\n    # Assert\n    assert_image_equal_tofile(im, expected)\n\n\ndef test_polygon_1px_high_translucent() -> None:\n    # Test drawing a translucent 1px high polygon\n    # Arrange\n    im = Image.new(\"RGB\", (4, 3))\n    draw = ImageDraw.Draw(im, \"RGBA\")\n    expected = \"Tests/images/imagedraw_polygon_1px_high_translucent.png\"\n\n    # Act\n    draw.polygon([(1, 1), (1, 1), (3, 1), (3, 1)], (255, 0, 0, 127))\n\n    # Assert\n    assert_image_equal_tofile(im, expected)\n\n\ndef test_polygon_translucent() -> None:\n    # Arrange\n    im = Image.new(\"RGB\", (W, H))\n    draw = ImageDraw.Draw(im, \"RGBA\")\n\n    # Act\n    draw.polygon([(20, 80), (80, 80), (80, 20)], fill=(0, 255, 0, 127))\n\n    # Assert\n    expected = \"Tests/images/imagedraw_polygon_translucent.png\"\n    assert_image_equal_tofile(im, expected)\n\n\n@pytest.mark.parametrize(\"bbox\", BBOX)\ndef test_rectangle(bbox: Coords) -> None:\n    # Arrange\n    im = Image.new(\"RGB\", (W, H))\n    draw = ImageDraw.Draw(im)\n\n    # Act\n    draw.rectangle(bbox, fill=\"black\", outline=\"green\")\n\n    # Assert\n    assert_image_equal_tofile(im, \"Tests/images/imagedraw_rectangle.png\")\n\n\ndef test_big_rectangle() -> None:\n    # Test drawing a rectangle bigger than the image\n    # Arrange\n    im = Image.new(\"RGB\", (W, H))\n    bbox = [(-1, -1), (W + 1, H + 1)]\n    draw = ImageDraw.Draw(im)\n\n    # Act\n    draw.rectangle(bbox, fill=\"orange\")\n\n    # Assert\n    assert_image_similar_tofile(im, \"Tests/images/imagedraw_big_rectangle.png\", 1)\n\n\n@pytest.mark.parametrize(\"bbox\", BBOX)\ndef test_rectangle_width(bbox: Coords) -> None:\n    # Arrange\n    im = Image.new(\"RGB\", (W, H))\n    draw = ImageDraw.Draw(im)\n    expected = \"Tests/images/imagedraw_rectangle_width.png\"\n\n    # Act\n    draw.rectangle(bbox, outline=\"green\", width=5)\n\n    # Assert\n    assert_image_equal_tofile(im, expected)\n\n\n@pytest.mark.parametrize(\"bbox\", BBOX)\ndef test_rectangle_width_fill(bbox: Coords) -> None:\n    # Arrange\n    im = Image.new(\"RGB\", (W, H))\n    draw = ImageDraw.Draw(im)\n    expected = \"Tests/images/imagedraw_rectangle_width_fill.png\"\n\n    # Act\n    draw.rectangle(bbox, fill=\"blue\", outline=\"green\", width=5)\n\n    # Assert\n    assert_image_equal_tofile(im, expected)\n\n\n@pytest.mark.parametrize(\"bbox\", BBOX)\ndef test_rectangle_zero_width(bbox: Coords) -> None:\n    # Arrange\n    im = Image.new(\"RGB\", (W, H))\n    draw = ImageDraw.Draw(im)\n\n    # Act\n    draw.rectangle(bbox, fill=\"blue\", outline=\"green\", width=0)\n\n    # Assert\n    assert_image_equal_tofile(im, \"Tests/images/imagedraw_rectangle_zero_width.png\")\n\n\n@pytest.mark.parametrize(\"bbox\", BBOX)\ndef test_rectangle_I16(bbox: Coords) -> None:\n    # Arrange\n    im = Image.new(\"I;16\", (W, H))\n    draw = ImageDraw.Draw(im)\n\n    # Act\n    draw.rectangle(bbox, outline=0xFFFF)\n\n    # Assert\n    assert_image_equal_tofile(im, \"Tests/images/imagedraw_rectangle_I.tiff\")\n\n\n@pytest.mark.parametrize(\"bbox\", BBOX)\ndef test_rectangle_translucent_outline(bbox: Coords) -> None:\n    # Arrange\n    im = Image.new(\"RGB\", (W, H))\n    draw = ImageDraw.Draw(im, \"RGBA\")\n\n    # Act\n    draw.rectangle(bbox, fill=\"black\", outline=(0, 255, 0, 127), width=5)\n\n    # Assert\n    assert_image_equal_tofile(\n        im, \"Tests/images/imagedraw_rectangle_translucent_outline.png\"\n    )\n\n\n@pytest.mark.parametrize(\n    \"xy\",\n    [(10, 20, 190, 180), ([10, 20], [190, 180]), ((10, 20), (190, 180))],\n)\ndef test_rounded_rectangle(\n    xy: (\n        tuple[int, int, int, int]\n        | tuple[list[int]]\n        | tuple[tuple[int, int], tuple[int, int]]\n    )\n) -> None:\n    # Arrange\n    im = Image.new(\"RGB\", (200, 200))\n    draw = ImageDraw.Draw(im)\n\n    # Act\n    draw.rounded_rectangle(xy, 30, fill=\"red\", outline=\"green\", width=5)\n\n    # Assert\n    assert_image_equal_tofile(im, \"Tests/images/imagedraw_rounded_rectangle.png\")\n\n\n@pytest.mark.parametrize(\"top_left\", (True, False))\n@pytest.mark.parametrize(\"top_right\", (True, False))\n@pytest.mark.parametrize(\"bottom_right\", (True, False))\n@pytest.mark.parametrize(\"bottom_left\", (True, False))\ndef test_rounded_rectangle_corners(\n    top_left: bool, top_right: bool, bottom_right: bool, bottom_left: bool\n) -> None:\n    corners = (top_left, top_right, bottom_right, bottom_left)\n\n    # Arrange\n    im = Image.new(\"RGB\", (200, 200))\n    draw = ImageDraw.Draw(im)\n\n    # Act\n    draw.rounded_rectangle(\n        (10, 20, 190, 180), 30, fill=\"red\", outline=\"green\", width=5, corners=corners\n    )\n\n    # Assert\n    suffix = \"\".join(\n        (\n            (\"y\" if top_left else \"n\"),\n            (\"y\" if top_right else \"n\"),\n            (\"y\" if bottom_right else \"n\"),\n            (\"y\" if bottom_left else \"n\"),\n        )\n    )\n    assert_image_equal_tofile(\n        im, \"Tests/images/imagedraw_rounded_rectangle_corners_\" + suffix + \".png\"\n    )\n\n\n@pytest.mark.parametrize(\n    \"xy, radius, type\",\n    [\n        ((10, 20, 190, 180), 30.5, \"given\"),\n        ((10, 10, 181, 190), 90, \"width\"),\n        ((10, 20, 190, 181), 85, \"height\"),\n    ],\n)\ndef test_rounded_rectangle_non_integer_radius(\n    xy: tuple[int, int, int, int], radius: float, type: str\n) -> None:\n    # Arrange\n    im = Image.new(\"RGB\", (200, 200))\n    draw = ImageDraw.Draw(im)\n\n    # Act\n    draw.rounded_rectangle(xy, radius, fill=\"red\", outline=\"green\", width=5)\n\n    # Assert\n    assert_image_equal_tofile(\n        im,\n        \"Tests/images/imagedraw_rounded_rectangle_non_integer_radius_\" + type + \".png\",\n    )\n\n\n@pytest.mark.parametrize(\"bbox\", BBOX)\ndef test_rounded_rectangle_zero_radius(bbox: Coords) -> None:\n    # Arrange\n    im = Image.new(\"RGB\", (W, H))\n    draw = ImageDraw.Draw(im)\n\n    # Act\n    draw.rounded_rectangle(bbox, 0, fill=\"blue\", outline=\"green\", width=5)\n\n    # Assert\n    assert_image_equal_tofile(im, \"Tests/images/imagedraw_rectangle_width_fill.png\")\n\n\n@pytest.mark.parametrize(\n    \"xy, suffix\",\n    [\n        ((20, 10, 80, 90), \"x\"),\n        ((20, 10, 81, 90), \"x_odd\"),\n        ((20, 10, 81.1, 90), \"x_odd\"),\n        ((10, 20, 90, 80), \"y\"),\n        ((10, 20, 90, 81), \"y_odd\"),\n        ((10, 20, 90, 81.1), \"y_odd\"),\n        ((20, 20, 80, 80), \"both\"),\n    ],\n)\ndef test_rounded_rectangle_translucent(\n    xy: tuple[int, int, int, int], suffix: str\n) -> None:\n    # Arrange\n    im = Image.new(\"RGB\", (W, H))\n    draw = ImageDraw.Draw(im, \"RGBA\")\n\n    # Act\n    draw.rounded_rectangle(\n        xy, 30, fill=(255, 0, 0, 127), outline=(0, 255, 0, 127), width=5\n    )\n\n    # Assert\n    assert_image_equal_tofile(\n        im, \"Tests/images/imagedraw_rounded_rectangle_\" + suffix + \".png\"\n    )\n\n\n@pytest.mark.parametrize(\"bbox\", BBOX)\ndef test_floodfill(bbox: Coords) -> None:\n    red = ImageColor.getrgb(\"red\")\n\n    for mode, value in [(\"L\", 1), (\"RGBA\", (255, 0, 0, 0)), (\"RGB\", red)]:\n        # Arrange\n        im = Image.new(mode, (W, H))\n        draw = ImageDraw.Draw(im)\n        draw.rectangle(bbox, outline=\"yellow\", fill=\"green\")\n        centre_point = (int(W / 2), int(H / 2))\n\n        # Act\n        ImageDraw.floodfill(im, centre_point, value)\n\n        # Assert\n        expected = \"Tests/images/imagedraw_floodfill_\" + mode + \".png\"\n        with Image.open(expected) as im_floodfill:\n            assert_image_equal(im, im_floodfill)\n\n    # Test that using the same colour does not change the image\n    ImageDraw.floodfill(im, centre_point, red)\n    assert_image_equal(im, im_floodfill)\n\n    # Test that filling outside the image does not change the image\n    ImageDraw.floodfill(im, (W, H), red)\n    assert_image_equal(im, im_floodfill)\n\n    # Test filling at the edge of an image\n    im = Image.new(\"RGB\", (1, 1))\n    ImageDraw.floodfill(im, (0, 0), red)\n    assert_image_equal(im, Image.new(\"RGB\", (1, 1), red))\n\n\n@pytest.mark.parametrize(\"bbox\", BBOX)\ndef test_floodfill_border(bbox: Coords) -> None:\n    # floodfill() is experimental\n\n    # Arrange\n    im = Image.new(\"RGB\", (W, H))\n    draw = ImageDraw.Draw(im)\n    draw.rectangle(bbox, outline=\"yellow\", fill=\"green\")\n    centre_point = (int(W / 2), int(H / 2))\n\n    # Act\n    ImageDraw.floodfill(\n        im,\n        centre_point,\n        ImageColor.getrgb(\"red\"),\n        border=ImageColor.getrgb(\"black\"),\n    )\n\n    # Assert\n    assert_image_equal_tofile(im, \"Tests/images/imagedraw_floodfill2.png\")\n\n\n@pytest.mark.parametrize(\"bbox\", BBOX)\ndef test_floodfill_thresh(bbox: Coords) -> None:\n    # floodfill() is experimental\n\n    # Arrange\n    im = Image.new(\"RGB\", (W, H))\n    draw = ImageDraw.Draw(im)\n    draw.rectangle(bbox, outline=\"darkgreen\", fill=\"green\")\n    centre_point = (int(W / 2), int(H / 2))\n\n    # Act\n    ImageDraw.floodfill(im, centre_point, ImageColor.getrgb(\"red\"), thresh=30)\n\n    # Assert\n    assert_image_equal_tofile(im, \"Tests/images/imagedraw_floodfill2.png\")\n\n\ndef test_floodfill_not_negative() -> None:\n    # floodfill() is experimental\n    # Test that floodfill does not extend into negative coordinates\n\n    # Arrange\n    im = Image.new(\"RGB\", (W, H))\n    draw = ImageDraw.Draw(im)\n    draw.line((W / 2, 0, W / 2, H / 2), fill=\"green\")\n    draw.line((0, H / 2, W / 2, H / 2), fill=\"green\")\n\n    # Act\n    ImageDraw.floodfill(im, (int(W / 4), int(H / 4)), ImageColor.getrgb(\"red\"))\n\n    # Assert\n    assert_image_equal_tofile(im, \"Tests/images/imagedraw_floodfill_not_negative.png\")\n\n\ndef create_base_image_draw(\n    size: tuple[int, int],\n    mode: str = DEFAULT_MODE,\n    background1: tuple[int, int, int] = WHITE,\n    background2: tuple[int, int, int] = GRAY,\n) -> tuple[Image.Image, ImageDraw.ImageDraw]:\n    img = Image.new(mode, size, background1)\n    for x in range(0, size[0]):\n        for y in range(0, size[1]):\n            if (x + y) % 2 == 0:\n                img.putpixel((x, y), background2)\n    return img, ImageDraw.Draw(img)\n\n\ndef test_square() -> None:\n    expected = os.path.join(IMAGES_PATH, \"square.png\")\n    img, draw = create_base_image_draw((10, 10))\n    draw.polygon([(2, 2), (2, 7), (7, 7), (7, 2)], BLACK)\n    assert_image_equal_tofile(img, expected, \"square as normal polygon failed\")\n    img, draw = create_base_image_draw((10, 10))\n    draw.polygon([(7, 7), (7, 2), (2, 2), (2, 7)], BLACK)\n    assert_image_equal_tofile(img, expected, \"square as inverted polygon failed\")\n    img, draw = create_base_image_draw((10, 10))\n    draw.rectangle((2, 2, 7, 7), BLACK)\n    assert_image_equal_tofile(img, expected, \"square as normal rectangle failed\")\n\n\ndef test_triangle_right() -> None:\n    img, draw = create_base_image_draw((20, 20))\n    draw.polygon([(3, 5), (17, 5), (10, 12)], BLACK)\n    assert_image_equal_tofile(\n        img, os.path.join(IMAGES_PATH, \"triangle_right.png\"), \"triangle right failed\"\n    )\n\n\n@pytest.mark.parametrize(\n    \"fill, suffix\",\n    ((BLACK, \"width\"), (None, \"width_no_fill\")),\n)\ndef test_triangle_right_width(fill: tuple[int, int, int] | None, suffix: str) -> None:\n    img, draw = create_base_image_draw((100, 100))\n    draw.polygon([(15, 25), (85, 25), (50, 60)], fill, WHITE, width=5)\n    assert_image_equal_tofile(\n        img, os.path.join(IMAGES_PATH, \"triangle_right_\" + suffix + \".png\")\n    )\n\n\ndef test_line_horizontal() -> None:\n    img, draw = create_base_image_draw((20, 20))\n    draw.line((5, 5, 14, 5), BLACK, 2)\n    assert_image_equal_tofile(\n        img,\n        os.path.join(IMAGES_PATH, \"line_horizontal_w2px_normal.png\"),\n        \"line straight horizontal normal 2px wide failed\",\n    )\n\n    img, draw = create_base_image_draw((20, 20))\n    draw.line((14, 5, 5, 5), BLACK, 2)\n    assert_image_equal_tofile(\n        img,\n        os.path.join(IMAGES_PATH, \"line_horizontal_w2px_inverted.png\"),\n        \"line straight horizontal inverted 2px wide failed\",\n    )\n\n    expected = os.path.join(IMAGES_PATH, \"line_horizontal_w3px.png\")\n    img, draw = create_base_image_draw((20, 20))\n    draw.line((5, 5, 14, 5), BLACK, 3)\n    assert_image_equal_tofile(\n        img, expected, \"line straight horizontal normal 3px wide failed\"\n    )\n    img, draw = create_base_image_draw((20, 20))\n    draw.line((14, 5, 5, 5), BLACK, 3)\n    assert_image_equal_tofile(\n        img, expected, \"line straight horizontal inverted 3px wide failed\"\n    )\n\n    img, draw = create_base_image_draw((200, 110))\n    draw.line((5, 55, 195, 55), BLACK, 101)\n    assert_image_equal_tofile(\n        img,\n        os.path.join(IMAGES_PATH, \"line_horizontal_w101px.png\"),\n        \"line straight horizontal 101px wide failed\",\n    )\n\n\ndef test_line_h_s1_w2() -> None:\n    pytest.skip(\"failing\")\n    img, draw = create_base_image_draw((20, 20))\n    draw.line((5, 5, 14, 6), BLACK, 2)\n    assert_image_equal_tofile(\n        img,\n        os.path.join(IMAGES_PATH, \"line_horizontal_slope1px_w2px.png\"),\n        \"line horizontal 1px slope 2px wide failed\",\n    )\n\n\ndef test_line_vertical() -> None:\n    img, draw = create_base_image_draw((20, 20))\n    draw.line((5, 5, 5, 14), BLACK, 2)\n    assert_image_equal_tofile(\n        img,\n        os.path.join(IMAGES_PATH, \"line_vertical_w2px_normal.png\"),\n        \"line straight vertical normal 2px wide failed\",\n    )\n\n    img, draw = create_base_image_draw((20, 20))\n    draw.line((5, 14, 5, 5), BLACK, 2)\n    assert_image_equal_tofile(\n        img,\n        os.path.join(IMAGES_PATH, \"line_vertical_w2px_inverted.png\"),\n        \"line straight vertical inverted 2px wide failed\",\n    )\n\n    expected = os.path.join(IMAGES_PATH, \"line_vertical_w3px.png\")\n    img, draw = create_base_image_draw((20, 20))\n    draw.line((5, 5, 5, 14), BLACK, 3)\n    assert_image_equal_tofile(\n        img, expected, \"line straight vertical normal 3px wide failed\"\n    )\n    img, draw = create_base_image_draw((20, 20))\n    draw.line((5, 14, 5, 5), BLACK, 3)\n    assert_image_equal_tofile(\n        img, expected, \"line straight vertical inverted 3px wide failed\"\n    )\n\n    img, draw = create_base_image_draw((110, 200))\n    draw.line((55, 5, 55, 195), BLACK, 101)\n    assert_image_equal_tofile(\n        img,\n        os.path.join(IMAGES_PATH, \"line_vertical_w101px.png\"),\n        \"line straight vertical 101px wide failed\",\n    )\n\n    img, draw = create_base_image_draw((20, 20))\n    draw.line((5, 5, 6, 14), BLACK, 2)\n    assert_image_equal_tofile(\n        img,\n        os.path.join(IMAGES_PATH, \"line_vertical_slope1px_w2px.png\"),\n        \"line vertical 1px slope 2px wide failed\",\n    )\n\n\ndef test_line_oblique_45() -> None:\n    expected = os.path.join(IMAGES_PATH, \"line_oblique_45_w3px_a.png\")\n    img, draw = create_base_image_draw((20, 20))\n    draw.line((5, 5, 14, 14), BLACK, 3)\n    assert_image_equal_tofile(img, expected, \"line oblique 45 normal 3px wide A failed\")\n    img, draw = create_base_image_draw((20, 20))\n    draw.line((14, 14, 5, 5), BLACK, 3)\n    assert_image_equal_tofile(\n        img, expected, \"line oblique 45 inverted 3px wide A failed\"\n    )\n\n    expected = os.path.join(IMAGES_PATH, \"line_oblique_45_w3px_b.png\")\n    img, draw = create_base_image_draw((20, 20))\n    draw.line((14, 5, 5, 14), BLACK, 3)\n    assert_image_equal_tofile(img, expected, \"line oblique 45 normal 3px wide B failed\")\n    img, draw = create_base_image_draw((20, 20))\n    draw.line((5, 14, 14, 5), BLACK, 3)\n    assert_image_equal_tofile(\n        img, expected, \"line oblique 45 inverted 3px wide B failed\"\n    )\n\n\ndef test_wide_line_dot() -> None:\n    # Test drawing a wide \"line\" from one point to another just draws a single point\n    # Arrange\n    im = Image.new(\"RGB\", (W, H))\n    draw = ImageDraw.Draw(im)\n\n    # Act\n    draw.line([(50, 50), (50, 50)], width=3)\n\n    # Assert\n    assert_image_similar_tofile(im, \"Tests/images/imagedraw_wide_line_dot.png\", 1)\n\n\ndef test_wide_line_larger_than_int() -> None:\n    # Arrange\n    im = Image.new(\"RGB\", (W, H))\n    draw = ImageDraw.Draw(im)\n    expected = \"Tests/images/imagedraw_wide_line_larger_than_int.png\"\n\n    # Act\n    draw.line([(0, 0), (32768, 32768)], width=3)\n\n    # Assert\n    assert_image_similar_tofile(im, expected, 1)\n\n\n@pytest.mark.parametrize(\n    \"xy\",\n    [\n        [\n            (400, 280),\n            (380, 280),\n            (450, 280),\n            (440, 120),\n            (350, 200),\n            (310, 280),\n            (300, 280),\n            (250, 280),\n            (250, 200),\n            (150, 200),\n            (150, 260),\n            (50, 200),\n            (150, 50),\n            (250, 100),\n        ],\n        (\n            400,\n            280,\n            380,\n            280,\n            450,\n            280,\n            440,\n            120,\n            350,\n            200,\n            310,\n            280,\n            300,\n            280,\n            250,\n            280,\n            250,\n            200,\n            150,\n            200,\n            150,\n            260,\n            50,\n            200,\n            150,\n            50,\n            250,\n            100,\n        ),\n        [\n            400,\n            280,\n            380,\n            280,\n            450,\n            280,\n            440,\n            120,\n            350,\n            200,\n            310,\n            280,\n            300,\n            280,\n            250,\n            280,\n            250,\n            200,\n            150,\n            200,\n            150,\n            260,\n            50,\n            200,\n            150,\n            50,\n            250,\n            100,\n        ],\n    ],\n)\ndef test_line_joint(xy: list[tuple[int, int]] | tuple[int, ...] | list[int]) -> None:\n    im = Image.new(\"RGB\", (500, 325))\n    draw = ImageDraw.Draw(im)\n\n    # Act\n    draw.line(xy, GRAY, 50, \"curve\")\n\n    # Assert\n    assert_image_similar_tofile(im, \"Tests/images/imagedraw_line_joint_curve.png\", 3)\n\n\ndef test_textsize_empty_string() -> None:\n    # https://github.com/python-pillow/Pillow/issues/2783\n    # Arrange\n    im = Image.new(\"RGB\", (W, H))\n    draw = ImageDraw.Draw(im)\n\n    # Act\n    # Should not cause 'SystemError: <built-in method getsize of\n    # ImagingFont object at 0x...> returned NULL without setting an error'\n    draw.textbbox((0, 0), \"\")\n    draw.textbbox((0, 0), \"\\n\")\n    draw.textbbox((0, 0), \"test\\n\")\n    draw.textlength(\"\")\n\n\n@skip_unless_feature(\"freetype2\")\ndef test_textbbox_stroke() -> None:\n    # Arrange\n    im = Image.new(\"RGB\", (W, H))\n    draw = ImageDraw.Draw(im)\n    font = ImageFont.truetype(\"Tests/fonts/FreeMono.ttf\", 20)\n\n    # Act / Assert\n    assert draw.textbbox((2, 2), \"A\", font, stroke_width=2) == (0, 4, 16, 20)\n    assert draw.textbbox((2, 2), \"A\", font, stroke_width=4) == (-2, 2, 18, 22)\n    assert draw.textbbox((2, 2), \"ABC\\nAaaa\", font, stroke_width=2) == (0, 4, 52, 44)\n    assert draw.textbbox((2, 2), \"ABC\\nAaaa\", font, stroke_width=4) == (-2, 2, 54, 50)\n\n\n@skip_unless_feature(\"freetype2\")\ndef test_stroke() -> None:\n    for suffix, stroke_fill in {\"same\": None, \"different\": \"#0f0\"}.items():\n        # Arrange\n        im = Image.new(\"RGB\", (120, 130))\n        draw = ImageDraw.Draw(im)\n        font = ImageFont.truetype(\"Tests/fonts/FreeMono.ttf\", 120)\n\n        # Act\n        draw.text((12, 12), \"A\", \"#f00\", font, stroke_width=2, stroke_fill=stroke_fill)\n\n        # Assert\n        assert_image_similar_tofile(\n            im, \"Tests/images/imagedraw_stroke_\" + suffix + \".png\", 3.1\n        )\n\n\n@skip_unless_feature(\"freetype2\")\ndef test_stroke_descender() -> None:\n    # Arrange\n    im = Image.new(\"RGB\", (120, 130))\n    draw = ImageDraw.Draw(im)\n    font = ImageFont.truetype(\"Tests/fonts/FreeMono.ttf\", 120)\n\n    # Act\n    draw.text((12, 2), \"y\", \"#f00\", font, stroke_width=2, stroke_fill=\"#0f0\")\n\n    # Assert\n    assert_image_similar_tofile(im, \"Tests/images/imagedraw_stroke_descender.png\", 6.76)\n\n\n@skip_unless_feature(\"freetype2\")\ndef test_split_word() -> None:\n    # Arrange\n    im = Image.new(\"RGB\", (230, 55))\n    expected = im.copy()\n    expected_draw = ImageDraw.Draw(expected)\n    font = ImageFont.truetype(\"Tests/fonts/FreeMono.ttf\", 48)\n    expected_draw.text((0, 0), \"paradise\", font=font)\n\n    draw = ImageDraw.Draw(im)\n\n    # Act\n    draw.text((0, 0), \"par\", font=font)\n\n    length = draw.textlength(\"par\", font=font)\n    draw.text((length, 0), \"adise\", font=font)\n\n    # Assert\n    assert_image_equal(im, expected)\n\n\n@skip_unless_feature(\"freetype2\")\ndef test_stroke_multiline() -> None:\n    # Arrange\n    im = Image.new(\"RGB\", (100, 250))\n    draw = ImageDraw.Draw(im)\n    font = ImageFont.truetype(\"Tests/fonts/FreeMono.ttf\", 120)\n\n    # Act\n    draw.multiline_text(\n        (12, 12), \"A\\nB\", \"#f00\", font, stroke_width=2, stroke_fill=\"#0f0\"\n    )\n\n    # Assert\n    assert_image_similar_tofile(im, \"Tests/images/imagedraw_stroke_multiline.png\", 3.3)\n\n\n@skip_unless_feature(\"freetype2\")\ndef test_setting_default_font() -> None:\n    # Arrange\n    im = Image.new(\"RGB\", (100, 250))\n    draw = ImageDraw.Draw(im)\n    font = ImageFont.truetype(\"Tests/fonts/FreeMono.ttf\", 120)\n\n    # Act\n    ImageDraw.ImageDraw.font = font\n\n    # Assert\n    try:\n        assert draw.getfont() == font\n    finally:\n        ImageDraw.ImageDraw.font = None\n        assert isinstance(draw.getfont(), ImageFont.load_default().__class__)\n\n\ndef test_default_font_size() -> None:\n    freetype_support = features.check_module(\"freetype2\")\n    text = \"Default font at a specific size.\"\n\n    im = Image.new(\"RGB\", (220, 25))\n    draw = ImageDraw.Draw(im)\n    with contextlib.nullcontext() if freetype_support else pytest.raises(ImportError):\n        draw.text((0, 0), text, font_size=16)\n        assert_image_equal_tofile(im, \"Tests/images/imagedraw_default_font_size.png\")\n\n    with contextlib.nullcontext() if freetype_support else pytest.raises(ImportError):\n        assert draw.textlength(text, font_size=16) == 216\n\n    with contextlib.nullcontext() if freetype_support else pytest.raises(ImportError):\n        assert draw.textbbox((0, 0), text, font_size=16) == (0, 3, 216, 19)\n\n    im = Image.new(\"RGB\", (220, 25))\n    draw = ImageDraw.Draw(im)\n    with contextlib.nullcontext() if freetype_support else pytest.raises(ImportError):\n        draw.multiline_text((0, 0), text, font_size=16)\n        assert_image_equal_tofile(im, \"Tests/images/imagedraw_default_font_size.png\")\n\n    with contextlib.nullcontext() if freetype_support else pytest.raises(ImportError):\n        assert draw.multiline_textbbox((0, 0), text, font_size=16) == (0, 3, 216, 19)\n\n\n@pytest.mark.parametrize(\"bbox\", BBOX)\ndef test_same_color_outline(bbox: Coords) -> None:\n    # Prepare shape\n    x0, y0 = 5, 5\n    x1, y1 = 5, 50\n    x2, y2 = 95, 50\n    x3, y3 = 95, 5\n\n    s = ImageDraw.Outline()\n    s.move(x0, y0)\n    s.curve(x1, y1, x2, y2, x3, y3)\n    s.line(x0, y0)\n\n    # Begin\n    for mode in [\"RGB\", \"L\"]:\n        fill = \"red\"\n        for outline in [None, \"red\", \"#f00\"]:\n            for operation, args in {\n                \"chord\": [bbox, 0, 180],\n                \"ellipse\": [bbox],\n                \"shape\": [s],\n                \"pieslice\": [bbox, -90, 45],\n                \"polygon\": [[(18, 30), (85, 30), (60, 72)]],\n                \"rectangle\": [bbox],\n            }.items():\n                # Arrange\n                im = Image.new(mode, (W, H))\n                draw = ImageDraw.Draw(im)\n\n                # Act\n                draw_method = getattr(draw, operation)\n                assert isinstance(args, list)\n                args += [fill, outline]\n                draw_method(*args)\n\n                # Assert\n                expected = f\"Tests/images/imagedraw_outline_{operation}_{mode}.png\"\n                assert_image_similar_tofile(im, expected, 1)\n\n\n@pytest.mark.parametrize(\n    \"n_sides, polygon_name, args\",\n    [\n        (4, \"square\", {}),\n        (8, \"regular_octagon\", {}),\n        (4, \"square_rotate_45\", {\"rotation\": 45}),\n        (3, \"triangle_width\", {\"width\": 5, \"outline\": \"yellow\"}),\n    ],\n)\ndef test_draw_regular_polygon(\n    n_sides: int, polygon_name: str, args: dict[str, int | str]\n) -> None:\n    im = Image.new(\"RGBA\", size=(W, H), color=(255, 0, 0, 0))\n    filename = f\"Tests/images/imagedraw_{polygon_name}.png\"\n    draw = ImageDraw.Draw(im)\n    bounding_circle = ((W // 2, H // 2), 25)\n    draw.regular_polygon(bounding_circle, n_sides, fill=\"red\", **args)\n    assert_image_equal_tofile(im, filename)\n\n\n@pytest.mark.parametrize(\n    \"n_sides, expected_vertices\",\n    [\n        (3, [(28.35, 62.5), (71.65, 62.5), (50.0, 25.0)]),\n        (4, [(32.32, 67.68), (67.68, 67.68), (67.68, 32.32), (32.32, 32.32)]),\n        (\n            5,\n            [\n                (35.31, 70.23),\n                (64.69, 70.23),\n                (73.78, 42.27),\n                (50.0, 25.0),\n                (26.22, 42.27),\n            ],\n        ),\n        (\n            6,\n            [\n                (37.5, 71.65),\n                (62.5, 71.65),\n                (75.0, 50.0),\n                (62.5, 28.35),\n                (37.5, 28.35),\n                (25.0, 50.0),\n            ],\n        ),\n    ],\n)\ndef test_compute_regular_polygon_vertices(\n    n_sides: int, expected_vertices: list[tuple[float, float]]\n) -> None:\n    bounding_circle = (W // 2, H // 2, 25)\n    vertices = ImageDraw._compute_regular_polygon_vertices(bounding_circle, n_sides, 0)\n    assert vertices == expected_vertices\n\n\n@pytest.mark.parametrize(\n    \"n_sides, bounding_circle, rotation, expected_error, error_message\",\n    [\n        (None, (50, 50, 25), 0, TypeError, \"n_sides should be an int\"),\n        (1, (50, 50, 25), 0, ValueError, \"n_sides should be an int > 2\"),\n        (3, 50, 0, TypeError, \"bounding_circle should be a sequence\"),\n        (\n            3,\n            (50, 50, 100, 100),\n            0,\n            ValueError,\n            \"bounding_circle should contain 2D coordinates \"\n            \"and a radius (e.g. (x, y, r) or ((x, y), r) )\",\n        ),\n        (\n            3,\n            (50, 50, \"25\"),\n            0,\n            ValueError,\n            \"bounding_circle should only contain numeric data\",\n        ),\n        (\n            3,\n            ((50, 50, 50), 25),\n            0,\n            ValueError,\n            \"bounding_circle centre should contain 2D coordinates (e.g. (x, y))\",\n        ),\n        (\n            3,\n            (50, 50, 0),\n            0,\n            ValueError,\n            \"bounding_circle radius should be > 0\",\n        ),\n        (\n            3,\n            (50, 50, 25),\n            \"0\",\n            ValueError,\n            \"rotation should be an int or float\",\n        ),\n    ],\n)\ndef test_compute_regular_polygon_vertices_input_error_handling(\n    n_sides, bounding_circle, rotation, expected_error, error_message\n) -> None:\n    with pytest.raises(expected_error) as e:\n        ImageDraw._compute_regular_polygon_vertices(bounding_circle, n_sides, rotation)\n    assert str(e.value) == error_message\n\n\ndef test_continuous_horizontal_edges_polygon() -> None:\n    xy = [\n        (2, 6),\n        (6, 6),\n        (12, 6),\n        (12, 12),\n        (8, 12),\n        (8, 8),\n        (4, 8),\n        (2, 8),\n    ]\n    img, draw = create_base_image_draw((16, 16))\n    draw.polygon(xy, BLACK)\n    expected = os.path.join(IMAGES_PATH, \"continuous_horizontal_edges_polygon.png\")\n    assert_image_equal_tofile(\n        img, expected, \"continuous horizontal edges polygon failed\"\n    )\n\n\ndef test_discontiguous_corners_polygon() -> None:\n    img, draw = create_base_image_draw((84, 68))\n    draw.polygon(((1, 21), (34, 4), (71, 1), (38, 18)), BLACK)\n    draw.polygon(((71, 44), (38, 27), (1, 24)), BLACK)\n    draw.polygon(\n        ((38, 66), (5, 49), (77, 49), (47, 66), (82, 63), (82, 47), (1, 47), (1, 63)),\n        BLACK,\n    )\n    expected = os.path.join(IMAGES_PATH, \"discontiguous_corners_polygon.png\")\n    assert_image_similar_tofile(img, expected, 1)\n\n\ndef test_polygon2() -> None:\n    im = Image.new(\"RGB\", (W, H))\n    draw = ImageDraw.Draw(im)\n    draw.polygon([(18, 30), (19, 31), (18, 30), (85, 30), (60, 72)], \"red\")\n    expected = \"Tests/images/imagedraw_outline_polygon_RGB.png\"\n    assert_image_similar_tofile(im, expected, 1)\n\n\n@pytest.mark.parametrize(\"xy\", ((1, 1, 0, 1), (1, 1, 1, 0)))\ndef test_incorrectly_ordered_coordinates(xy: tuple[int, int, int, int]) -> None:\n    im = Image.new(\"RGB\", (W, H))\n    draw = ImageDraw.Draw(im)\n    with pytest.raises(ValueError):\n        draw.arc(xy, 10, 260)\n    with pytest.raises(ValueError):\n        draw.chord(xy, 10, 260)\n    with pytest.raises(ValueError):\n        draw.ellipse(xy)\n    with pytest.raises(ValueError):\n        draw.pieslice(xy, 10, 260)\n    with pytest.raises(ValueError):\n        draw.rectangle(xy)\n    with pytest.raises(ValueError):\n        draw.rounded_rectangle(xy)\n",
            "new_file_content": "from __future__ import annotations\n\nimport contextlib\nimport os.path\nfrom typing import Sequence\n\nimport pytest\n\nfrom PIL import Image, ImageColor, ImageDraw, ImageFont, features\nfrom PIL._typing import Coords\n\nfrom .helper import (\n    assert_image_equal,\n    assert_image_equal_tofile,\n    assert_image_similar_tofile,\n    hopper,\n    skip_unless_feature,\n)\n\nBLACK = (0, 0, 0)\nWHITE = (255, 255, 255)\nGRAY = (190, 190, 190)\nDEFAULT_MODE = \"RGB\"\nIMAGES_PATH = os.path.join(\"Tests\", \"images\", \"imagedraw\")\n\n# Image size\nW, H = 100, 100\n\n# Bounding box points\nX0 = int(W / 4)\nX1 = int(X0 * 3)\nY0 = int(H / 4)\nY1 = int(X0 * 3)\n\n# Bounding boxes\nBBOX = (((X0, Y0), (X1, Y1)), [(X0, Y0), (X1, Y1)], (X0, Y0, X1, Y1), [X0, Y0, X1, Y1])\n\n# Coordinate sequences\nPOINTS = (\n    ((10, 10), (20, 40), (30, 30)),\n    [(10, 10), (20, 40), (30, 30)],\n    (10, 10, 20, 40, 30, 30),\n    [10, 10, 20, 40, 30, 30],\n)\n\nKITE_POINTS = (\n    ((10, 50), (70, 10), (90, 50), (70, 90), (10, 50)),\n    [(10, 50), (70, 10), (90, 50), (70, 90), (10, 50)],\n)\n\n\ndef test_sanity() -> None:\n    im = hopper(\"RGB\").copy()\n\n    draw = ImageDraw.ImageDraw(im)\n    draw = ImageDraw.Draw(im)\n\n    draw.ellipse(list(range(4)))\n    draw.line(list(range(10)))\n    draw.polygon(list(range(100)))\n    draw.rectangle(list(range(4)))\n\n\ndef test_valueerror() -> None:\n    with Image.open(\"Tests/images/chi.gif\") as im:\n        draw = ImageDraw.Draw(im)\n        draw.line((0, 0), fill=(0, 0, 0))\n\n\ndef test_mode_mismatch() -> None:\n    im = hopper(\"RGB\").copy()\n\n    with pytest.raises(ValueError):\n        ImageDraw.ImageDraw(im, mode=\"L\")\n\n\n@pytest.mark.parametrize(\"bbox\", BBOX)\n@pytest.mark.parametrize(\"start, end\", ((0, 180), (0.5, 180.4)))\ndef test_arc(bbox: Coords, start: float, end: float) -> None:\n    # Arrange\n    im = Image.new(\"RGB\", (W, H))\n    draw = ImageDraw.Draw(im)\n\n    # Act\n    draw.arc(bbox, start, end)\n\n    # Assert\n    assert_image_similar_tofile(im, \"Tests/images/imagedraw_arc.png\", 1)\n\n\n@pytest.mark.parametrize(\"bbox\", BBOX)\ndef test_arc_end_le_start(bbox: Coords) -> None:\n    # Arrange\n    im = Image.new(\"RGB\", (W, H))\n    draw = ImageDraw.Draw(im)\n    start = 270.5\n    end = 0\n\n    # Act\n    draw.arc(bbox, start=start, end=end)\n\n    # Assert\n    assert_image_equal_tofile(im, \"Tests/images/imagedraw_arc_end_le_start.png\")\n\n\n@pytest.mark.parametrize(\"bbox\", BBOX)\ndef test_arc_no_loops(bbox: Coords) -> None:\n    # No need to go in loops\n    # Arrange\n    im = Image.new(\"RGB\", (W, H))\n    draw = ImageDraw.Draw(im)\n    start = 5\n    end = 370\n\n    # Act\n    draw.arc(bbox, start=start, end=end)\n\n    # Assert\n    assert_image_similar_tofile(im, \"Tests/images/imagedraw_arc_no_loops.png\", 1)\n\n\n@pytest.mark.parametrize(\"bbox\", BBOX)\ndef test_arc_width(bbox: Coords) -> None:\n    # Arrange\n    im = Image.new(\"RGB\", (W, H))\n    draw = ImageDraw.Draw(im)\n\n    # Act\n    draw.arc(bbox, 10, 260, width=5)\n\n    # Assert\n    assert_image_similar_tofile(im, \"Tests/images/imagedraw_arc_width.png\", 1)\n\n\n@pytest.mark.parametrize(\"bbox\", BBOX)\ndef test_arc_width_pieslice_large(bbox: Coords) -> None:\n    # Tests an arc with a large enough width that it is a pieslice\n    # Arrange\n    im = Image.new(\"RGB\", (W, H))\n    draw = ImageDraw.Draw(im)\n\n    # Act\n    draw.arc(bbox, 10, 260, fill=\"yellow\", width=100)\n\n    # Assert\n    assert_image_similar_tofile(im, \"Tests/images/imagedraw_arc_width_pieslice.png\", 1)\n\n\n@pytest.mark.parametrize(\"bbox\", BBOX)\ndef test_arc_width_fill(bbox: Coords) -> None:\n    # Arrange\n    im = Image.new(\"RGB\", (W, H))\n    draw = ImageDraw.Draw(im)\n\n    # Act\n    draw.arc(bbox, 10, 260, fill=\"yellow\", width=5)\n\n    # Assert\n    assert_image_similar_tofile(im, \"Tests/images/imagedraw_arc_width_fill.png\", 1)\n\n\n@pytest.mark.parametrize(\"bbox\", BBOX)\ndef test_arc_width_non_whole_angle(bbox: Coords) -> None:\n    # Arrange\n    im = Image.new(\"RGB\", (W, H))\n    draw = ImageDraw.Draw(im)\n    expected = \"Tests/images/imagedraw_arc_width_non_whole_angle.png\"\n\n    # Act\n    draw.arc(bbox, 10, 259.5, width=5)\n\n    # Assert\n    assert_image_similar_tofile(im, expected, 1)\n\n\ndef test_arc_high() -> None:\n    # Arrange\n    im = Image.new(\"RGB\", (200, 200))\n    draw = ImageDraw.Draw(im)\n\n    # Act\n    draw.arc([10, 10, 89, 189], 20, 330, width=20, fill=\"white\")\n    draw.arc([110, 10, 189, 189], 20, 150, width=20, fill=\"white\")\n\n    # Assert\n    assert_image_equal_tofile(im, \"Tests/images/imagedraw_arc_high.png\")\n\n\ndef test_bitmap() -> None:\n    # Arrange\n    im = Image.new(\"RGB\", (W, H))\n    draw = ImageDraw.Draw(im)\n    with Image.open(\"Tests/images/pil123rgba.png\") as small:\n        small = small.resize((50, 50), Image.Resampling.NEAREST)\n\n        # Act\n        draw.bitmap((10, 10), small)\n\n    # Assert\n    assert_image_equal_tofile(im, \"Tests/images/imagedraw_bitmap.png\")\n\n\n@pytest.mark.parametrize(\"mode\", (\"RGB\", \"L\"))\n@pytest.mark.parametrize(\"bbox\", BBOX)\ndef test_chord(mode: str, bbox: Coords) -> None:\n    # Arrange\n    im = Image.new(mode, (W, H))\n    draw = ImageDraw.Draw(im)\n    expected = f\"Tests/images/imagedraw_chord_{mode}.png\"\n\n    # Act\n    draw.chord(bbox, 0, 180, fill=\"red\", outline=\"yellow\")\n\n    # Assert\n    assert_image_similar_tofile(im, expected, 1)\n\n\n@pytest.mark.parametrize(\"bbox\", BBOX)\ndef test_chord_width(bbox: Coords) -> None:\n    # Arrange\n    im = Image.new(\"RGB\", (W, H))\n    draw = ImageDraw.Draw(im)\n\n    # Act\n    draw.chord(bbox, 10, 260, outline=\"yellow\", width=5)\n\n    # Assert\n    assert_image_similar_tofile(im, \"Tests/images/imagedraw_chord_width.png\", 1)\n\n\n@pytest.mark.parametrize(\"bbox\", BBOX)\ndef test_chord_width_fill(bbox: Coords) -> None:\n    # Arrange\n    im = Image.new(\"RGB\", (W, H))\n    draw = ImageDraw.Draw(im)\n\n    # Act\n    draw.chord(bbox, 10, 260, fill=\"red\", outline=\"yellow\", width=5)\n\n    # Assert\n    assert_image_similar_tofile(im, \"Tests/images/imagedraw_chord_width_fill.png\", 1)\n\n\n@pytest.mark.parametrize(\"bbox\", BBOX)\ndef test_chord_zero_width(bbox: Coords) -> None:\n    # Arrange\n    im = Image.new(\"RGB\", (W, H))\n    draw = ImageDraw.Draw(im)\n\n    # Act\n    draw.chord(bbox, 10, 260, fill=\"red\", outline=\"yellow\", width=0)\n\n    # Assert\n    assert_image_equal_tofile(im, \"Tests/images/imagedraw_chord_zero_width.png\")\n\n\ndef test_chord_too_fat() -> None:\n    # Arrange\n    im = Image.new(\"RGB\", (100, 100))\n    draw = ImageDraw.Draw(im)\n\n    # Act\n    draw.chord([-150, -150, 99, 99], 15, 60, width=10, fill=\"white\", outline=\"red\")\n\n    # Assert\n    assert_image_equal_tofile(im, \"Tests/images/imagedraw_chord_too_fat.png\")\n\n\n@pytest.mark.parametrize(\"mode\", (\"RGB\", \"L\"))\n@pytest.mark.parametrize(\"xy\", ((W / 2, H / 2), [W / 2, H / 2]))\ndef test_circle(mode: str, xy: Sequence[float]) -> None:\n    # Arrange\n    im = Image.new(mode, (W, H))\n    draw = ImageDraw.Draw(im)\n    expected = f\"Tests/images/imagedraw_ellipse_{mode}.png\"\n\n    # Act\n    draw.circle(xy, 25, fill=\"green\", outline=\"blue\")\n\n    # Assert\n    assert_image_similar_tofile(im, expected, 1)\n\n\n@pytest.mark.parametrize(\"mode\", (\"RGB\", \"L\"))\n@pytest.mark.parametrize(\"bbox\", BBOX)\ndef test_ellipse(mode: str, bbox: Coords) -> None:\n    # Arrange\n    im = Image.new(mode, (W, H))\n    draw = ImageDraw.Draw(im)\n    expected = f\"Tests/images/imagedraw_ellipse_{mode}.png\"\n\n    # Act\n    draw.ellipse(bbox, fill=\"green\", outline=\"blue\")\n\n    # Assert\n    assert_image_similar_tofile(im, expected, 1)\n\n\n@pytest.mark.parametrize(\"bbox\", BBOX)\ndef test_ellipse_translucent(bbox: Coords) -> None:\n    # Arrange\n    im = Image.new(\"RGB\", (W, H))\n    draw = ImageDraw.Draw(im, \"RGBA\")\n\n    # Act\n    draw.ellipse(bbox, fill=(0, 255, 0, 127))\n\n    # Assert\n    expected = \"Tests/images/imagedraw_ellipse_translucent.png\"\n    assert_image_similar_tofile(im, expected, 1)\n\n\ndef test_ellipse_edge() -> None:\n    # Arrange\n    im = Image.new(\"RGB\", (W, H))\n    draw = ImageDraw.Draw(im)\n\n    # Act\n    draw.ellipse(((0, 0), (W - 1, H - 1)), fill=\"white\")\n\n    # Assert\n    assert_image_similar_tofile(im, \"Tests/images/imagedraw_ellipse_edge.png\", 1)\n\n\ndef test_ellipse_symmetric() -> None:\n    for width, bbox in (\n        (100, (24, 24, 75, 75)),\n        (101, (25, 25, 75, 75)),\n    ):\n        im = Image.new(\"RGB\", (width, 100))\n        draw = ImageDraw.Draw(im)\n        draw.ellipse(bbox, fill=\"green\", outline=\"blue\")\n        assert_image_equal(im, im.transpose(Image.Transpose.FLIP_LEFT_RIGHT))\n\n\n@pytest.mark.parametrize(\"bbox\", BBOX)\ndef test_ellipse_width(bbox: Coords) -> None:\n    # Arrange\n    im = Image.new(\"RGB\", (W, H))\n    draw = ImageDraw.Draw(im)\n\n    # Act\n    draw.ellipse(bbox, outline=\"blue\", width=5)\n\n    # Assert\n    assert_image_similar_tofile(im, \"Tests/images/imagedraw_ellipse_width.png\", 1)\n\n\ndef test_ellipse_width_large() -> None:\n    # Arrange\n    im = Image.new(\"RGB\", (500, 500))\n    draw = ImageDraw.Draw(im)\n\n    # Act\n    draw.ellipse((25, 25, 475, 475), outline=\"blue\", width=75)\n\n    # Assert\n    assert_image_similar_tofile(im, \"Tests/images/imagedraw_ellipse_width_large.png\", 1)\n\n\n@pytest.mark.parametrize(\"bbox\", BBOX)\ndef test_ellipse_width_fill(bbox: Coords) -> None:\n    # Arrange\n    im = Image.new(\"RGB\", (W, H))\n    draw = ImageDraw.Draw(im)\n\n    # Act\n    draw.ellipse(bbox, fill=\"green\", outline=\"blue\", width=5)\n\n    # Assert\n    assert_image_similar_tofile(im, \"Tests/images/imagedraw_ellipse_width_fill.png\", 1)\n\n\n@pytest.mark.parametrize(\"bbox\", BBOX)\ndef test_ellipse_zero_width(bbox: Coords) -> None:\n    # Arrange\n    im = Image.new(\"RGB\", (W, H))\n    draw = ImageDraw.Draw(im)\n\n    # Act\n    draw.ellipse(bbox, fill=\"green\", outline=\"blue\", width=0)\n\n    # Assert\n    assert_image_equal_tofile(im, \"Tests/images/imagedraw_ellipse_zero_width.png\")\n\n\ndef ellipse_various_sizes_helper(filled: bool) -> Image.Image:\n    ellipse_sizes = range(32)\n    image_size = sum(ellipse_sizes) + len(ellipse_sizes) + 1\n    im = Image.new(\"RGB\", (image_size, image_size))\n    draw = ImageDraw.Draw(im)\n\n    x = 1\n    for w in ellipse_sizes:\n        y = 1\n        for h in ellipse_sizes:\n            x1 = x + w\n            if w:\n                x1 -= 1\n            y1 = y + h\n            if h:\n                y1 -= 1\n            border = [x, y, x1, y1]\n            if filled:\n                draw.ellipse(border, fill=\"white\")\n            else:\n                draw.ellipse(border, outline=\"white\")\n            y += h + 1\n        x += w + 1\n\n    return im\n\n\ndef test_ellipse_various_sizes() -> None:\n    im = ellipse_various_sizes_helper(False)\n\n    assert_image_equal_tofile(im, \"Tests/images/imagedraw_ellipse_various_sizes.png\")\n\n\ndef test_ellipse_various_sizes_filled() -> None:\n    im = ellipse_various_sizes_helper(True)\n\n    assert_image_equal_tofile(\n        im, \"Tests/images/imagedraw_ellipse_various_sizes_filled.png\"\n    )\n\n\n@pytest.mark.parametrize(\"points\", POINTS)\ndef test_line(points: Coords) -> None:\n    # Arrange\n    im = Image.new(\"RGB\", (W, H))\n    draw = ImageDraw.Draw(im)\n\n    # Act\n    draw.line(points, fill=\"yellow\", width=2)\n\n    # Assert\n    assert_image_equal_tofile(im, \"Tests/images/imagedraw_line.png\")\n\n\ndef test_shape1() -> None:\n    # Arrange\n    im = Image.new(\"RGB\", (100, 100), \"white\")\n    draw = ImageDraw.Draw(im)\n    x0, y0 = 5, 5\n    x1, y1 = 5, 50\n    x2, y2 = 95, 50\n    x3, y3 = 95, 5\n\n    # Act\n    s = ImageDraw.Outline()\n    s.move(x0, y0)\n    s.curve(x1, y1, x2, y2, x3, y3)\n    s.line(x0, y0)\n\n    draw.shape(s, fill=1)\n\n    # Assert\n    assert_image_equal_tofile(im, \"Tests/images/imagedraw_shape1.png\")\n\n\ndef test_shape2() -> None:\n    # Arrange\n    im = Image.new(\"RGB\", (100, 100), \"white\")\n    draw = ImageDraw.Draw(im)\n    x0, y0 = 95, 95\n    x1, y1 = 95, 50\n    x2, y2 = 5, 50\n    x3, y3 = 5, 95\n\n    # Act\n    s = ImageDraw.Outline()\n    s.move(x0, y0)\n    s.curve(x1, y1, x2, y2, x3, y3)\n    s.line(x0, y0)\n\n    draw.shape(s, outline=\"blue\")\n\n    # Assert\n    assert_image_equal_tofile(im, \"Tests/images/imagedraw_shape2.png\")\n\n\ndef test_transform() -> None:\n    # Arrange\n    im = Image.new(\"RGB\", (100, 100), \"white\")\n    expected = im.copy()\n    draw = ImageDraw.Draw(im)\n\n    # Act\n    s = ImageDraw.Outline()\n    s.line(0, 0)\n    s.transform((0, 0, 0, 0, 0, 0))\n\n    draw.shape(s, fill=1)\n\n    # Assert\n    assert_image_equal(im, expected)\n\n\n@pytest.mark.parametrize(\"bbox\", BBOX)\n@pytest.mark.parametrize(\"start, end\", ((-92, 46), (-92.2, 46.2)))\ndef test_pieslice(bbox: Coords, start: float, end: float) -> None:\n    # Arrange\n    im = Image.new(\"RGB\", (W, H))\n    draw = ImageDraw.Draw(im)\n\n    # Act\n    draw.pieslice(bbox, start, end, fill=\"white\", outline=\"blue\")\n\n    # Assert\n    assert_image_similar_tofile(im, \"Tests/images/imagedraw_pieslice.png\", 1)\n\n\n@pytest.mark.parametrize(\"bbox\", BBOX)\ndef test_pieslice_width(bbox: Coords) -> None:\n    # Arrange\n    im = Image.new(\"RGB\", (W, H))\n    draw = ImageDraw.Draw(im)\n\n    # Act\n    draw.pieslice(bbox, 10, 260, outline=\"blue\", width=5)\n\n    # Assert\n    assert_image_similar_tofile(im, \"Tests/images/imagedraw_pieslice_width.png\", 1)\n\n\n@pytest.mark.parametrize(\"bbox\", BBOX)\ndef test_pieslice_width_fill(bbox: Coords) -> None:\n    # Arrange\n    im = Image.new(\"RGB\", (W, H))\n    draw = ImageDraw.Draw(im)\n    expected = \"Tests/images/imagedraw_pieslice_width_fill.png\"\n\n    # Act\n    draw.pieslice(bbox, 10, 260, fill=\"white\", outline=\"blue\", width=5)\n\n    # Assert\n    assert_image_similar_tofile(im, expected, 1)\n\n\n@pytest.mark.parametrize(\"bbox\", BBOX)\ndef test_pieslice_zero_width(bbox: Coords) -> None:\n    # Arrange\n    im = Image.new(\"RGB\", (W, H))\n    draw = ImageDraw.Draw(im)\n\n    # Act\n    draw.pieslice(bbox, 10, 260, fill=\"white\", outline=\"blue\", width=0)\n\n    # Assert\n    assert_image_equal_tofile(im, \"Tests/images/imagedraw_pieslice_zero_width.png\")\n\n\ndef test_pieslice_wide() -> None:\n    # Arrange\n    im = Image.new(\"RGB\", (200, 100))\n    draw = ImageDraw.Draw(im)\n\n    # Act\n    draw.pieslice([0, 0, 199, 99], 190, 170, width=10, fill=\"white\", outline=\"red\")\n\n    # Assert\n    assert_image_equal_tofile(im, \"Tests/images/imagedraw_pieslice_wide.png\")\n\n\ndef test_pieslice_no_spikes() -> None:\n    im = Image.new(\"RGB\", (161, 161), \"white\")\n    draw = ImageDraw.Draw(im)\n    cxs = (\n        [140] * 3\n        + list(range(140, 19, -20))\n        + [20] * 5\n        + list(range(20, 141, 20))\n        + [140] * 2\n    )\n    cys = (\n        list(range(80, 141, 20))\n        + [140] * 5\n        + list(range(140, 19, -20))\n        + [20] * 5\n        + list(range(20, 80, 20))\n    )\n\n    for cx, cy, angle in zip(cxs, cys, range(0, 360, 15)):\n        draw.pieslice(\n            [cx - 100, cy - 100, cx + 100, cy + 100], angle, angle + 1, fill=\"black\"\n        )\n        draw.point([cx, cy], fill=\"red\")\n\n    im_pre_erase = im.copy()\n    draw.rectangle([21, 21, 139, 139], fill=\"white\")\n\n    assert_image_equal(im, im_pre_erase)\n\n\n@pytest.mark.parametrize(\"points\", POINTS)\ndef test_point(points: Coords) -> None:\n    # Arrange\n    im = Image.new(\"RGB\", (W, H))\n    draw = ImageDraw.Draw(im)\n\n    # Act\n    draw.point(points, fill=\"yellow\")\n\n    # Assert\n    assert_image_equal_tofile(im, \"Tests/images/imagedraw_point.png\")\n\n\ndef test_point_I16() -> None:\n    # Arrange\n    im = Image.new(\"I;16\", (1, 1))\n    draw = ImageDraw.Draw(im)\n\n    # Act\n    draw.point((0, 0), fill=0x1234)\n\n    # Assert\n    assert im.getpixel((0, 0)) == 0x1234\n\n\n@pytest.mark.parametrize(\"points\", POINTS)\ndef test_polygon(points: Coords) -> None:\n    # Arrange\n    im = Image.new(\"RGB\", (W, H))\n    draw = ImageDraw.Draw(im)\n\n    # Act\n    draw.polygon(points, fill=\"red\", outline=\"blue\")\n\n    # Assert\n    assert_image_equal_tofile(im, \"Tests/images/imagedraw_polygon.png\")\n\n\n@pytest.mark.parametrize(\"mode\", (\"RGB\", \"L\"))\n@pytest.mark.parametrize(\"kite_points\", KITE_POINTS)\ndef test_polygon_kite(\n    mode: str, kite_points: tuple[tuple[int, int], ...] | list[tuple[int, int]]\n) -> None:\n    # Test drawing lines of different gradients (dx>dy, dy>dx) and\n    # vertical (dx==0) and horizontal (dy==0) lines\n    # Arrange\n    im = Image.new(mode, (W, H))\n    draw = ImageDraw.Draw(im)\n    expected = f\"Tests/images/imagedraw_polygon_kite_{mode}.png\"\n\n    # Act\n    draw.polygon(kite_points, fill=\"blue\", outline=\"yellow\")\n\n    # Assert\n    assert_image_equal_tofile(im, expected)\n\n\ndef test_polygon_1px_high() -> None:\n    # Test drawing a 1px high polygon\n    # Arrange\n    im = Image.new(\"RGB\", (3, 3))\n    draw = ImageDraw.Draw(im)\n    expected = \"Tests/images/imagedraw_polygon_1px_high.png\"\n\n    # Act\n    draw.polygon([(0, 1), (0, 1), (2, 1), (2, 1)], \"#f00\")\n\n    # Assert\n    assert_image_equal_tofile(im, expected)\n\n\ndef test_polygon_1px_high_translucent() -> None:\n    # Test drawing a translucent 1px high polygon\n    # Arrange\n    im = Image.new(\"RGB\", (4, 3))\n    draw = ImageDraw.Draw(im, \"RGBA\")\n    expected = \"Tests/images/imagedraw_polygon_1px_high_translucent.png\"\n\n    # Act\n    draw.polygon([(1, 1), (1, 1), (3, 1), (3, 1)], (255, 0, 0, 127))\n\n    # Assert\n    assert_image_equal_tofile(im, expected)\n\n\ndef test_polygon_translucent() -> None:\n    # Arrange\n    im = Image.new(\"RGB\", (W, H))\n    draw = ImageDraw.Draw(im, \"RGBA\")\n\n    # Act\n    draw.polygon([(20, 80), (80, 80), (80, 20)], fill=(0, 255, 0, 127))\n\n    # Assert\n    expected = \"Tests/images/imagedraw_polygon_translucent.png\"\n    assert_image_equal_tofile(im, expected)\n\n\n@pytest.mark.parametrize(\"bbox\", BBOX)\ndef test_rectangle(bbox: Coords) -> None:\n    # Arrange\n    im = Image.new(\"RGB\", (W, H))\n    draw = ImageDraw.Draw(im)\n\n    # Act\n    draw.rectangle(bbox, fill=\"black\", outline=\"green\")\n\n    # Assert\n    assert_image_equal_tofile(im, \"Tests/images/imagedraw_rectangle.png\")\n\n\ndef test_big_rectangle() -> None:\n    # Test drawing a rectangle bigger than the image\n    # Arrange\n    im = Image.new(\"RGB\", (W, H))\n    bbox = [(-1, -1), (W + 1, H + 1)]\n    draw = ImageDraw.Draw(im)\n\n    # Act\n    draw.rectangle(bbox, fill=\"orange\")\n\n    # Assert\n    assert_image_similar_tofile(im, \"Tests/images/imagedraw_big_rectangle.png\", 1)\n\n\n@pytest.mark.parametrize(\"bbox\", BBOX)\ndef test_rectangle_width(bbox: Coords) -> None:\n    # Arrange\n    im = Image.new(\"RGB\", (W, H))\n    draw = ImageDraw.Draw(im)\n    expected = \"Tests/images/imagedraw_rectangle_width.png\"\n\n    # Act\n    draw.rectangle(bbox, outline=\"green\", width=5)\n\n    # Assert\n    assert_image_equal_tofile(im, expected)\n\n\n@pytest.mark.parametrize(\"bbox\", BBOX)\ndef test_rectangle_width_fill(bbox: Coords) -> None:\n    # Arrange\n    im = Image.new(\"RGB\", (W, H))\n    draw = ImageDraw.Draw(im)\n    expected = \"Tests/images/imagedraw_rectangle_width_fill.png\"\n\n    # Act\n    draw.rectangle(bbox, fill=\"blue\", outline=\"green\", width=5)\n\n    # Assert\n    assert_image_equal_tofile(im, expected)\n\n\n@pytest.mark.parametrize(\"bbox\", BBOX)\ndef test_rectangle_zero_width(bbox: Coords) -> None:\n    # Arrange\n    im = Image.new(\"RGB\", (W, H))\n    draw = ImageDraw.Draw(im)\n\n    # Act\n    draw.rectangle(bbox, fill=\"blue\", outline=\"green\", width=0)\n\n    # Assert\n    assert_image_equal_tofile(im, \"Tests/images/imagedraw_rectangle_zero_width.png\")\n\n\n@pytest.mark.parametrize(\"bbox\", BBOX)\ndef test_rectangle_I16(bbox: Coords) -> None:\n    # Arrange\n    im = Image.new(\"I;16\", (W, H))\n    draw = ImageDraw.Draw(im)\n\n    # Act\n    draw.rectangle(bbox, outline=0xFFFF)\n\n    # Assert\n    assert_image_equal_tofile(im, \"Tests/images/imagedraw_rectangle_I.tiff\")\n\n\n@pytest.mark.parametrize(\"bbox\", BBOX)\ndef test_rectangle_translucent_outline(bbox: Coords) -> None:\n    # Arrange\n    im = Image.new(\"RGB\", (W, H))\n    draw = ImageDraw.Draw(im, \"RGBA\")\n\n    # Act\n    draw.rectangle(bbox, fill=\"black\", outline=(0, 255, 0, 127), width=5)\n\n    # Assert\n    assert_image_equal_tofile(\n        im, \"Tests/images/imagedraw_rectangle_translucent_outline.png\"\n    )\n\n\n@pytest.mark.parametrize(\n    \"xy\",\n    [(10, 20, 190, 180), ([10, 20], [190, 180]), ((10, 20), (190, 180))],\n)\ndef test_rounded_rectangle(\n    xy: (\n        tuple[int, int, int, int]\n        | tuple[list[int]]\n        | tuple[tuple[int, int], tuple[int, int]]\n    )\n) -> None:\n    # Arrange\n    im = Image.new(\"RGB\", (200, 200))\n    draw = ImageDraw.Draw(im)\n\n    # Act\n    draw.rounded_rectangle(xy, 30, fill=\"red\", outline=\"green\", width=5)\n\n    # Assert\n    assert_image_equal_tofile(im, \"Tests/images/imagedraw_rounded_rectangle.png\")\n\n\n@pytest.mark.parametrize(\"top_left\", (True, False))\n@pytest.mark.parametrize(\"top_right\", (True, False))\n@pytest.mark.parametrize(\"bottom_right\", (True, False))\n@pytest.mark.parametrize(\"bottom_left\", (True, False))\ndef test_rounded_rectangle_corners(\n    top_left: bool, top_right: bool, bottom_right: bool, bottom_left: bool\n) -> None:\n    corners = (top_left, top_right, bottom_right, bottom_left)\n\n    # Arrange\n    im = Image.new(\"RGB\", (200, 200))\n    draw = ImageDraw.Draw(im)\n\n    # Act\n    draw.rounded_rectangle(\n        (10, 20, 190, 180), 30, fill=\"red\", outline=\"green\", width=5, corners=corners\n    )\n\n    # Assert\n    suffix = \"\".join(\n        (\n            (\"y\" if top_left else \"n\"),\n            (\"y\" if top_right else \"n\"),\n            (\"y\" if bottom_right else \"n\"),\n            (\"y\" if bottom_left else \"n\"),\n        )\n    )\n    assert_image_equal_tofile(\n        im, \"Tests/images/imagedraw_rounded_rectangle_corners_\" + suffix + \".png\"\n    )\n\n\n@pytest.mark.parametrize(\n    \"xy, radius, type\",\n    [\n        ((10, 20, 190, 180), 30.5, \"given\"),\n        ((10, 10, 181, 190), 90, \"width\"),\n        ((10, 20, 190, 181), 85, \"height\"),\n    ],\n)\ndef test_rounded_rectangle_non_integer_radius(\n    xy: tuple[int, int, int, int], radius: float, type: str\n) -> None:\n    # Arrange\n    im = Image.new(\"RGB\", (200, 200))\n    draw = ImageDraw.Draw(im)\n\n    # Act\n    draw.rounded_rectangle(xy, radius, fill=\"red\", outline=\"green\", width=5)\n\n    # Assert\n    assert_image_equal_tofile(\n        im,\n        \"Tests/images/imagedraw_rounded_rectangle_non_integer_radius_\" + type + \".png\",\n    )\n\n\n@pytest.mark.parametrize(\"bbox\", BBOX)\ndef test_rounded_rectangle_zero_radius(bbox: Coords) -> None:\n    # Arrange\n    im = Image.new(\"RGB\", (W, H))\n    draw = ImageDraw.Draw(im)\n\n    # Act\n    draw.rounded_rectangle(bbox, 0, fill=\"blue\", outline=\"green\", width=5)\n\n    # Assert\n    assert_image_equal_tofile(im, \"Tests/images/imagedraw_rectangle_width_fill.png\")\n\n\n@pytest.mark.parametrize(\n    \"xy, suffix\",\n    [\n        ((20, 10, 80, 90), \"x\"),\n        ((20, 10, 81, 90), \"x_odd\"),\n        ((20, 10, 81.1, 90), \"x_odd\"),\n        ((10, 20, 90, 80), \"y\"),\n        ((10, 20, 90, 81), \"y_odd\"),\n        ((10, 20, 90, 81.1), \"y_odd\"),\n        ((20, 20, 80, 80), \"both\"),\n    ],\n)\ndef test_rounded_rectangle_translucent(\n    xy: tuple[int, int, int, int], suffix: str\n) -> None:\n    # Arrange\n    im = Image.new(\"RGB\", (W, H))\n    draw = ImageDraw.Draw(im, \"RGBA\")\n\n    # Act\n    draw.rounded_rectangle(\n        xy, 30, fill=(255, 0, 0, 127), outline=(0, 255, 0, 127), width=5\n    )\n\n    # Assert\n    assert_image_equal_tofile(\n        im, \"Tests/images/imagedraw_rounded_rectangle_\" + suffix + \".png\"\n    )\n\n\n@pytest.mark.parametrize(\"bbox\", BBOX)\ndef test_floodfill(bbox: Coords) -> None:\n    red = ImageColor.getrgb(\"red\")\n\n    for mode, value in [(\"L\", 1), (\"RGBA\", (255, 0, 0, 0)), (\"RGB\", red)]:\n        # Arrange\n        im = Image.new(mode, (W, H))\n        draw = ImageDraw.Draw(im)\n        draw.rectangle(bbox, outline=\"yellow\", fill=\"green\")\n        centre_point = (int(W / 2), int(H / 2))\n\n        # Act\n        ImageDraw.floodfill(im, centre_point, value)\n\n        # Assert\n        expected = \"Tests/images/imagedraw_floodfill_\" + mode + \".png\"\n        with Image.open(expected) as im_floodfill:\n            assert_image_equal(im, im_floodfill)\n\n    # Test that using the same colour does not change the image\n    ImageDraw.floodfill(im, centre_point, red)\n    assert_image_equal(im, im_floodfill)\n\n    # Test that filling outside the image does not change the image\n    ImageDraw.floodfill(im, (W, H), red)\n    assert_image_equal(im, im_floodfill)\n\n    # Test filling at the edge of an image\n    im = Image.new(\"RGB\", (1, 1))\n    ImageDraw.floodfill(im, (0, 0), red)\n    assert_image_equal(im, Image.new(\"RGB\", (1, 1), red))\n\n\n@pytest.mark.parametrize(\"bbox\", BBOX)\ndef test_floodfill_border(bbox: Coords) -> None:\n    # floodfill() is experimental\n\n    # Arrange\n    im = Image.new(\"RGB\", (W, H))\n    draw = ImageDraw.Draw(im)\n    draw.rectangle(bbox, outline=\"yellow\", fill=\"green\")\n    centre_point = (int(W / 2), int(H / 2))\n\n    # Act\n    ImageDraw.floodfill(\n        im,\n        centre_point,\n        ImageColor.getrgb(\"red\"),\n        border=ImageColor.getrgb(\"black\"),\n    )\n\n    # Assert\n    assert_image_equal_tofile(im, \"Tests/images/imagedraw_floodfill2.png\")\n\n\n@pytest.mark.parametrize(\"bbox\", BBOX)\ndef test_floodfill_thresh(bbox: Coords) -> None:\n    # floodfill() is experimental\n\n    # Arrange\n    im = Image.new(\"RGB\", (W, H))\n    draw = ImageDraw.Draw(im)\n    draw.rectangle(bbox, outline=\"darkgreen\", fill=\"green\")\n    centre_point = (int(W / 2), int(H / 2))\n\n    # Act\n    ImageDraw.floodfill(im, centre_point, ImageColor.getrgb(\"red\"), thresh=30)\n\n    # Assert\n    assert_image_equal_tofile(im, \"Tests/images/imagedraw_floodfill2.png\")\n\n\ndef test_floodfill_not_negative() -> None:\n    # floodfill() is experimental\n    # Test that floodfill does not extend into negative coordinates\n\n    # Arrange\n    im = Image.new(\"RGB\", (W, H))\n    draw = ImageDraw.Draw(im)\n    draw.line((W / 2, 0, W / 2, H / 2), fill=\"green\")\n    draw.line((0, H / 2, W / 2, H / 2), fill=\"green\")\n\n    # Act\n    ImageDraw.floodfill(im, (int(W / 4), int(H / 4)), ImageColor.getrgb(\"red\"))\n\n    # Assert\n    assert_image_equal_tofile(im, \"Tests/images/imagedraw_floodfill_not_negative.png\")\n\n\ndef create_base_image_draw(\n    size: tuple[int, int],\n    mode: str = DEFAULT_MODE,\n    background1: tuple[int, int, int] = WHITE,\n    background2: tuple[int, int, int] = GRAY,\n) -> tuple[Image.Image, ImageDraw.ImageDraw]:\n    img = Image.new(mode, size, background1)\n    for x in range(0, size[0]):\n        for y in range(0, size[1]):\n            if (x + y) % 2 == 0:\n                img.putpixel((x, y), background2)\n    return img, ImageDraw.Draw(img)\n\n\ndef test_square() -> None:\n    expected = os.path.join(IMAGES_PATH, \"square.png\")\n    img, draw = create_base_image_draw((10, 10))\n    draw.polygon([(2, 2), (2, 7), (7, 7), (7, 2)], BLACK)\n    assert_image_equal_tofile(img, expected, \"square as normal polygon failed\")\n    img, draw = create_base_image_draw((10, 10))\n    draw.polygon([(7, 7), (7, 2), (2, 2), (2, 7)], BLACK)\n    assert_image_equal_tofile(img, expected, \"square as inverted polygon failed\")\n    img, draw = create_base_image_draw((10, 10))\n    draw.rectangle((2, 2, 7, 7), BLACK)\n    assert_image_equal_tofile(img, expected, \"square as normal rectangle failed\")\n\n\ndef test_triangle_right() -> None:\n    img, draw = create_base_image_draw((20, 20))\n    draw.polygon([(3, 5), (17, 5), (10, 12)], BLACK)\n    assert_image_equal_tofile(\n        img, os.path.join(IMAGES_PATH, \"triangle_right.png\"), \"triangle right failed\"\n    )\n\n\n@pytest.mark.parametrize(\n    \"fill, suffix\",\n    ((BLACK, \"width\"), (None, \"width_no_fill\")),\n)\ndef test_triangle_right_width(fill: tuple[int, int, int] | None, suffix: str) -> None:\n    img, draw = create_base_image_draw((100, 100))\n    draw.polygon([(15, 25), (85, 25), (50, 60)], fill, WHITE, width=5)\n    assert_image_equal_tofile(\n        img, os.path.join(IMAGES_PATH, \"triangle_right_\" + suffix + \".png\")\n    )\n\n\ndef test_line_horizontal() -> None:\n    img, draw = create_base_image_draw((20, 20))\n    draw.line((5, 5, 14, 5), BLACK, 2)\n    assert_image_equal_tofile(\n        img,\n        os.path.join(IMAGES_PATH, \"line_horizontal_w2px_normal.png\"),\n        \"line straight horizontal normal 2px wide failed\",\n    )\n\n    img, draw = create_base_image_draw((20, 20))\n    draw.line((14, 5, 5, 5), BLACK, 2)\n    assert_image_equal_tofile(\n        img,\n        os.path.join(IMAGES_PATH, \"line_horizontal_w2px_inverted.png\"),\n        \"line straight horizontal inverted 2px wide failed\",\n    )\n\n    expected = os.path.join(IMAGES_PATH, \"line_horizontal_w3px.png\")\n    img, draw = create_base_image_draw((20, 20))\n    draw.line((5, 5, 14, 5), BLACK, 3)\n    assert_image_equal_tofile(\n        img, expected, \"line straight horizontal normal 3px wide failed\"\n    )\n    img, draw = create_base_image_draw((20, 20))\n    draw.line((14, 5, 5, 5), BLACK, 3)\n    assert_image_equal_tofile(\n        img, expected, \"line straight horizontal inverted 3px wide failed\"\n    )\n\n    img, draw = create_base_image_draw((200, 110))\n    draw.line((5, 55, 195, 55), BLACK, 101)\n    assert_image_equal_tofile(\n        img,\n        os.path.join(IMAGES_PATH, \"line_horizontal_w101px.png\"),\n        \"line straight horizontal 101px wide failed\",\n    )\n\n\ndef test_line_h_s1_w2() -> None:\n    pytest.skip(\"failing\")\n    img, draw = create_base_image_draw((20, 20))\n    draw.line((5, 5, 14, 6), BLACK, 2)\n    assert_image_equal_tofile(\n        img,\n        os.path.join(IMAGES_PATH, \"line_horizontal_slope1px_w2px.png\"),\n        \"line horizontal 1px slope 2px wide failed\",\n    )\n\n\ndef test_line_vertical() -> None:\n    img, draw = create_base_image_draw((20, 20))\n    draw.line((5, 5, 5, 14), BLACK, 2)\n    assert_image_equal_tofile(\n        img,\n        os.path.join(IMAGES_PATH, \"line_vertical_w2px_normal.png\"),\n        \"line straight vertical normal 2px wide failed\",\n    )\n\n    img, draw = create_base_image_draw((20, 20))\n    draw.line((5, 14, 5, 5), BLACK, 2)\n    assert_image_equal_tofile(\n        img,\n        os.path.join(IMAGES_PATH, \"line_vertical_w2px_inverted.png\"),\n        \"line straight vertical inverted 2px wide failed\",\n    )\n\n    expected = os.path.join(IMAGES_PATH, \"line_vertical_w3px.png\")\n    img, draw = create_base_image_draw((20, 20))\n    draw.line((5, 5, 5, 14), BLACK, 3)\n    assert_image_equal_tofile(\n        img, expected, \"line straight vertical normal 3px wide failed\"\n    )\n    img, draw = create_base_image_draw((20, 20))\n    draw.line((5, 14, 5, 5), BLACK, 3)\n    assert_image_equal_tofile(\n        img, expected, \"line straight vertical inverted 3px wide failed\"\n    )\n\n    img, draw = create_base_image_draw((110, 200))\n    draw.line((55, 5, 55, 195), BLACK, 101)\n    assert_image_equal_tofile(\n        img,\n        os.path.join(IMAGES_PATH, \"line_vertical_w101px.png\"),\n        \"line straight vertical 101px wide failed\",\n    )\n\n    img, draw = create_base_image_draw((20, 20))\n    draw.line((5, 5, 6, 14), BLACK, 2)\n    assert_image_equal_tofile(\n        img,\n        os.path.join(IMAGES_PATH, \"line_vertical_slope1px_w2px.png\"),\n        \"line vertical 1px slope 2px wide failed\",\n    )\n\n\ndef test_line_oblique_45() -> None:\n    expected = os.path.join(IMAGES_PATH, \"line_oblique_45_w3px_a.png\")\n    img, draw = create_base_image_draw((20, 20))\n    draw.line((5, 5, 14, 14), BLACK, 3)\n    assert_image_equal_tofile(img, expected, \"line oblique 45 normal 3px wide A failed\")\n    img, draw = create_base_image_draw((20, 20))\n    draw.line((14, 14, 5, 5), BLACK, 3)\n    assert_image_equal_tofile(\n        img, expected, \"line oblique 45 inverted 3px wide A failed\"\n    )\n\n    expected = os.path.join(IMAGES_PATH, \"line_oblique_45_w3px_b.png\")\n    img, draw = create_base_image_draw((20, 20))\n    draw.line((14, 5, 5, 14), BLACK, 3)\n    assert_image_equal_tofile(img, expected, \"line oblique 45 normal 3px wide B failed\")\n    img, draw = create_base_image_draw((20, 20))\n    draw.line((5, 14, 14, 5), BLACK, 3)\n    assert_image_equal_tofile(\n        img, expected, \"line oblique 45 inverted 3px wide B failed\"\n    )\n\n\ndef test_wide_line_dot() -> None:\n    # Test drawing a wide \"line\" from one point to another just draws a single point\n    # Arrange\n    im = Image.new(\"RGB\", (W, H))\n    draw = ImageDraw.Draw(im)\n\n    # Act\n    draw.line([(50, 50), (50, 50)], width=3)\n\n    # Assert\n    assert_image_similar_tofile(im, \"Tests/images/imagedraw_wide_line_dot.png\", 1)\n\n\ndef test_wide_line_larger_than_int() -> None:\n    # Arrange\n    im = Image.new(\"RGB\", (W, H))\n    draw = ImageDraw.Draw(im)\n    expected = \"Tests/images/imagedraw_wide_line_larger_than_int.png\"\n\n    # Act\n    draw.line([(0, 0), (32768, 32768)], width=3)\n\n    # Assert\n    assert_image_similar_tofile(im, expected, 1)\n\n\n@pytest.mark.parametrize(\n    \"xy\",\n    [\n        [\n            (400, 280),\n            (380, 280),\n            (450, 280),\n            (440, 120),\n            (350, 200),\n            (310, 280),\n            (300, 280),\n            (250, 280),\n            (250, 200),\n            (150, 200),\n            (150, 260),\n            (50, 200),\n            (150, 50),\n            (250, 100),\n        ],\n        (\n            400,\n            280,\n            380,\n            280,\n            450,\n            280,\n            440,\n            120,\n            350,\n            200,\n            310,\n            280,\n            300,\n            280,\n            250,\n            280,\n            250,\n            200,\n            150,\n            200,\n            150,\n            260,\n            50,\n            200,\n            150,\n            50,\n            250,\n            100,\n        ),\n        [\n            400,\n            280,\n            380,\n            280,\n            450,\n            280,\n            440,\n            120,\n            350,\n            200,\n            310,\n            280,\n            300,\n            280,\n            250,\n            280,\n            250,\n            200,\n            150,\n            200,\n            150,\n            260,\n            50,\n            200,\n            150,\n            50,\n            250,\n            100,\n        ],\n    ],\n)\ndef test_line_joint(xy: list[tuple[int, int]] | tuple[int, ...] | list[int]) -> None:\n    im = Image.new(\"RGB\", (500, 325))\n    draw = ImageDraw.Draw(im)\n\n    # Act\n    draw.line(xy, GRAY, 50, \"curve\")\n\n    # Assert\n    assert_image_similar_tofile(im, \"Tests/images/imagedraw_line_joint_curve.png\", 3)\n\n\ndef test_textsize_empty_string() -> None:\n    # https://github.com/python-pillow/Pillow/issues/2783\n    # Arrange\n    im = Image.new(\"RGB\", (W, H))\n    draw = ImageDraw.Draw(im)\n\n    # Act\n    # Should not cause 'SystemError: <built-in method getsize of\n    # ImagingFont object at 0x...> returned NULL without setting an error'\n    draw.textbbox((0, 0), \"\")\n    draw.textbbox((0, 0), \"\\n\")\n    draw.textbbox((0, 0), \"test\\n\")\n    draw.textlength(\"\")\n\n\n@skip_unless_feature(\"freetype2\")\ndef test_textbbox_stroke() -> None:\n    # Arrange\n    im = Image.new(\"RGB\", (W, H))\n    draw = ImageDraw.Draw(im)\n    font = ImageFont.truetype(\"Tests/fonts/FreeMono.ttf\", 20)\n\n    # Act / Assert\n    assert draw.textbbox((2, 2), \"A\", font, stroke_width=2) == (0, 4, 16, 20)\n    assert draw.textbbox((2, 2), \"A\", font, stroke_width=4) == (-2, 2, 18, 22)\n    assert draw.textbbox((2, 2), \"ABC\\nAaaa\", font, stroke_width=2) == (0, 4, 52, 44)\n    assert draw.textbbox((2, 2), \"ABC\\nAaaa\", font, stroke_width=4) == (-2, 2, 54, 50)\n\n\n@skip_unless_feature(\"freetype2\")\ndef test_stroke() -> None:\n    for suffix, stroke_fill in {\"same\": None, \"different\": \"#0f0\"}.items():\n        # Arrange\n        im = Image.new(\"RGB\", (120, 130))\n        draw = ImageDraw.Draw(im)\n        font = ImageFont.truetype(\"Tests/fonts/FreeMono.ttf\", 120)\n\n        # Act\n        draw.text((12, 12), \"A\", \"#f00\", font, stroke_width=2, stroke_fill=stroke_fill)\n\n        # Assert\n        assert_image_similar_tofile(\n            im, \"Tests/images/imagedraw_stroke_\" + suffix + \".png\", 3.1\n        )\n\n\n@skip_unless_feature(\"freetype2\")\ndef test_stroke_descender() -> None:\n    # Arrange\n    im = Image.new(\"RGB\", (120, 130))\n    draw = ImageDraw.Draw(im)\n    font = ImageFont.truetype(\"Tests/fonts/FreeMono.ttf\", 120)\n\n    # Act\n    draw.text((12, 2), \"y\", \"#f00\", font, stroke_width=2, stroke_fill=\"#0f0\")\n\n    # Assert\n    assert_image_similar_tofile(im, \"Tests/images/imagedraw_stroke_descender.png\", 6.76)\n\n\n@skip_unless_feature(\"freetype2\")\ndef test_split_word() -> None:\n    # Arrange\n    im = Image.new(\"RGB\", (230, 55))\n    expected = im.copy()\n    expected_draw = ImageDraw.Draw(expected)\n    font = ImageFont.truetype(\"Tests/fonts/FreeMono.ttf\", 48)\n    expected_draw.text((0, 0), \"paradise\", font=font)\n\n    draw = ImageDraw.Draw(im)\n\n    # Act\n    draw.text((0, 0), \"par\", font=font)\n\n    length = draw.textlength(\"par\", font=font)\n    draw.text((length, 0), \"adise\", font=font)\n\n    # Assert\n    assert_image_equal(im, expected)\n\n\n@skip_unless_feature(\"freetype2\")\ndef test_stroke_multiline() -> None:\n    # Arrange\n    im = Image.new(\"RGB\", (100, 250))\n    draw = ImageDraw.Draw(im)\n    font = ImageFont.truetype(\"Tests/fonts/FreeMono.ttf\", 120)\n\n    # Act\n    draw.multiline_text(\n        (12, 12), \"A\\nB\", \"#f00\", font, stroke_width=2, stroke_fill=\"#0f0\"\n    )\n\n    # Assert\n    assert_image_similar_tofile(im, \"Tests/images/imagedraw_stroke_multiline.png\", 3.3)\n\n\n@skip_unless_feature(\"freetype2\")\ndef test_setting_default_font() -> None:\n    # Arrange\n    im = Image.new(\"RGB\", (100, 250))\n    draw = ImageDraw.Draw(im)\n    font = ImageFont.truetype(\"Tests/fonts/FreeMono.ttf\", 120)\n\n    # Act\n    ImageDraw.ImageDraw.font = font\n\n    # Assert\n    try:\n        assert draw.getfont() == font\n    finally:\n        ImageDraw.ImageDraw.font = None\n        assert isinstance(draw.getfont(), ImageFont.load_default().__class__)\n\n\ndef test_default_font_size() -> None:\n    freetype_support = features.check_module(\"freetype2\")\n    text = \"Default font at a specific size.\"\n\n    im = Image.new(\"RGB\", (220, 25))\n    draw = ImageDraw.Draw(im)\n    with contextlib.nullcontext() if freetype_support else pytest.raises(ImportError):\n        draw.text((0, 0), text, font_size=16)\n        assert_image_equal_tofile(im, \"Tests/images/imagedraw_default_font_size.png\")\n\n    with contextlib.nullcontext() if freetype_support else pytest.raises(ImportError):\n        assert draw.textlength(text, font_size=16) == 216\n\n    with contextlib.nullcontext() if freetype_support else pytest.raises(ImportError):\n        assert draw.textbbox((0, 0), text, font_size=16) == (0, 3, 216, 19)\n\n    im = Image.new(\"RGB\", (220, 25))\n    draw = ImageDraw.Draw(im)\n    with contextlib.nullcontext() if freetype_support else pytest.raises(ImportError):\n        draw.multiline_text((0, 0), text, font_size=16)\n        assert_image_equal_tofile(im, \"Tests/images/imagedraw_default_font_size.png\")\n\n    with contextlib.nullcontext() if freetype_support else pytest.raises(ImportError):\n        assert draw.multiline_textbbox((0, 0), text, font_size=16) == (0, 3, 216, 19)\n\n\n@pytest.mark.parametrize(\"bbox\", BBOX)\ndef test_same_color_outline(bbox: Coords) -> None:\n    # Prepare shape\n    x0, y0 = 5, 5\n    x1, y1 = 5, 50\n    x2, y2 = 95, 50\n    x3, y3 = 95, 5\n\n    s = ImageDraw.Outline()\n    s.move(x0, y0)\n    s.curve(x1, y1, x2, y2, x3, y3)\n    s.line(x0, y0)\n\n    # Begin\n    for mode in [\"RGB\", \"L\"]:\n        fill = \"red\"\n        for outline in [None, \"red\", \"#f00\"]:\n            for operation, args in {\n                \"chord\": [bbox, 0, 180],\n                \"ellipse\": [bbox],\n                \"shape\": [s],\n                \"pieslice\": [bbox, -90, 45],\n                \"polygon\": [[(18, 30), (85, 30), (60, 72)]],\n                \"rectangle\": [bbox],\n            }.items():\n                # Arrange\n                im = Image.new(mode, (W, H))\n                draw = ImageDraw.Draw(im)\n\n                # Act\n                draw_method = getattr(draw, operation)\n                assert isinstance(args, list)\n                args += [fill, outline]\n                draw_method(*args)\n\n                # Assert\n                expected = f\"Tests/images/imagedraw_outline_{operation}_{mode}.png\"\n                assert_image_similar_tofile(im, expected, 1)\n\n\n@pytest.mark.parametrize(\n    \"n_sides, polygon_name, args\",\n    [\n        (4, \"square\", {}),\n        (8, \"regular_octagon\", {}),\n        (4, \"square_rotate_45\", {\"rotation\": 45}),\n        (3, \"triangle_width\", {\"width\": 5, \"outline\": \"yellow\"}),\n    ],\n)\ndef test_draw_regular_polygon(\n    n_sides: int, polygon_name: str, args: dict[str, int | str]\n) -> None:\n    im = Image.new(\"RGBA\", size=(W, H), color=(255, 0, 0, 0))\n    filename = f\"Tests/images/imagedraw_{polygon_name}.png\"\n    draw = ImageDraw.Draw(im)\n    bounding_circle = ((W // 2, H // 2), 25)\n    draw.regular_polygon(bounding_circle, n_sides, fill=\"red\", **args)\n    assert_image_equal_tofile(im, filename)\n\n\n@pytest.mark.parametrize(\n    \"n_sides, expected_vertices\",\n    [\n        (3, [(28.35, 62.5), (71.65, 62.5), (50.0, 25.0)]),\n        (4, [(32.32, 67.68), (67.68, 67.68), (67.68, 32.32), (32.32, 32.32)]),\n        (\n            5,\n            [\n                (35.31, 70.23),\n                (64.69, 70.23),\n                (73.78, 42.27),\n                (50.0, 25.0),\n                (26.22, 42.27),\n            ],\n        ),\n        (\n            6,\n            [\n                (37.5, 71.65),\n                (62.5, 71.65),\n                (75.0, 50.0),\n                (62.5, 28.35),\n                (37.5, 28.35),\n                (25.0, 50.0),\n            ],\n        ),\n    ],\n)\ndef test_compute_regular_polygon_vertices(\n    n_sides: int, expected_vertices: list[tuple[float, float]]\n) -> None:\n    bounding_circle = (W // 2, H // 2, 25)\n    vertices = ImageDraw._compute_regular_polygon_vertices(bounding_circle, n_sides, 0)\n    assert vertices == expected_vertices\n\n\n@pytest.mark.parametrize(\n    \"n_sides, bounding_circle, rotation, expected_error, error_message\",\n    [\n        (None, (50, 50, 25), 0, TypeError, \"n_sides should be an int\"),\n        (1, (50, 50, 25), 0, ValueError, \"n_sides should be an int > 2\"),\n        (3, 50, 0, TypeError, \"bounding_circle should be a sequence\"),\n        (\n            3,\n            (50, 50, 100, 100),\n            0,\n            ValueError,\n            \"bounding_circle should contain 2D coordinates \"\n            \"and a radius (e.g. (x, y, r) or ((x, y), r) )\",\n        ),\n        (\n            3,\n            (50, 50, \"25\"),\n            0,\n            ValueError,\n            \"bounding_circle should only contain numeric data\",\n        ),\n        (\n            3,\n            ((50, 50, 50), 25),\n            0,\n            ValueError,\n            \"bounding_circle centre should contain 2D coordinates (e.g. (x, y))\",\n        ),\n        (\n            3,\n            (50, 50, 0),\n            0,\n            ValueError,\n            \"bounding_circle radius should be > 0\",\n        ),\n        (\n            3,\n            (50, 50, 25),\n            \"0\",\n            ValueError,\n            \"rotation should be an int or float\",\n        ),\n    ],\n)\ndef test_compute_regular_polygon_vertices_input_error_handling(\n    n_sides, bounding_circle, rotation, expected_error, error_message\n) -> None:\n    with pytest.raises(expected_error) as e:\n        ImageDraw._compute_regular_polygon_vertices(bounding_circle, n_sides, rotation)\n    assert str(e.value) == error_message\n\n\ndef test_continuous_horizontal_edges_polygon() -> None:\n    xy = [\n        (2, 6),\n        (6, 6),\n        (12, 6),\n        (12, 12),\n        (8, 12),\n        (8, 8),\n        (4, 8),\n        (2, 8),\n    ]\n    img, draw = create_base_image_draw((16, 16))\n    draw.polygon(xy, BLACK)\n    expected = os.path.join(IMAGES_PATH, \"continuous_horizontal_edges_polygon.png\")\n    assert_image_equal_tofile(\n        img, expected, \"continuous horizontal edges polygon failed\"\n    )\n\n\ndef test_discontiguous_corners_polygon() -> None:\n    img, draw = create_base_image_draw((84, 68))\n    draw.polygon(((1, 21), (34, 4), (71, 1), (38, 18)), BLACK)\n    draw.polygon(((71, 44), (38, 27), (1, 24)), BLACK)\n    draw.polygon(\n        ((38, 66), (5, 49), (77, 49), (47, 66), (82, 63), (82, 47), (1, 47), (1, 63)),\n        BLACK,\n    )\n    expected = os.path.join(IMAGES_PATH, \"discontiguous_corners_polygon.png\")\n    assert_image_similar_tofile(img, expected, 1)\n\n\ndef test_polygon2() -> None:\n    im = Image.new(\"RGB\", (W, H))\n    draw = ImageDraw.Draw(im)\n    draw.polygon([(18, 30), (19, 31), (18, 30), (85, 30), (60, 72)], \"red\")\n    expected = \"Tests/images/imagedraw_outline_polygon_RGB.png\"\n    assert_image_similar_tofile(im, expected, 1)\n\n\n@pytest.mark.parametrize(\"xy\", ((1, 1, 0, 1), (1, 1, 1, 0)))\ndef test_incorrectly_ordered_coordinates(xy: tuple[int, int, int, int]) -> None:\n    im = Image.new(\"RGB\", (W, H))\n    draw = ImageDraw.Draw(im)\n    with pytest.raises(ValueError):\n        draw.arc(xy, 10, 260)\n    with pytest.raises(ValueError):\n        draw.chord(xy, 10, 260)\n    with pytest.raises(ValueError):\n        draw.ellipse(xy)\n    with pytest.raises(ValueError):\n        draw.pieslice(xy, 10, 260)\n    with pytest.raises(ValueError):\n        draw.rectangle(xy)\n    with pytest.raises(ValueError):\n        draw.rounded_rectangle(xy)\n",
            "header": {
                "file": {
                    "path": "Tests/test_imagedraw.py"
                },
                "misc_line": null
            },
            "index_line": {
                "old_commit_hash": "0a699e2ab",
                "new_commit_hash": "69d09e03d",
                "mode": "100644"
            },
            "is_binary_file": false,
            "binary_line": null,
            "minus_file": {
                "path": "a/Tests/test_imagedraw.py"
            },
            "plus_file": {
                "path": "b/Tests/test_imagedraw.py"
            },
            "hunks": [
                {
                    "descriptor": {
                        "old_range": {
                            "start": 2,
                            "length": 6
                        },
                        "new_range": {
                            "start": 2,
                            "length": 7
                        },
                        "section": "from __future__ import annotations"
                    },
                    "line_group": {
                        "all_lines": [
                            {
                                "content": "",
                                "type": "context"
                            },
                            {
                                "content": "import contextlib",
                                "type": "context"
                            },
                            {
                                "content": "import os.path",
                                "type": "context"
                            },
                            {
                                "content": "from typing import Sequence",
                                "type": "added"
                            },
                            {
                                "content": "",
                                "type": "context"
                            },
                            {
                                "content": "import pytest",
                                "type": "context"
                            },
                            {
                                "content": "",
                                "type": "context"
                            }
                        ]
                    },
                    "modified_entities": [],
                    "added_entities": [
                        {
                            "file_name": "Tests/test_imagedraw.py",
                            "type": "statement",
                            "name": "",
                            "content": "from typing import Sequence",
                            "ast_type_str": "ImportFrom",
                            "start_lineno": 5,
                            "end_lineno": 5,
                            "parent": null
                        }
                    ],
                    "deleted_entities": []
                },
                {
                    "descriptor": {
                        "old_range": {
                            "start": 265,
                            "length": 6
                        },
                        "new_range": {
                            "start": 266,
                            "length": 21
                        },
                        "section": "def test_chord_too_fat() -> None:"
                    },
                    "line_group": {
                        "all_lines": [
                            {
                                "content": "    assert_image_equal_tofile(im, \"Tests/images/imagedraw_chord_too_fat.png\")",
                                "type": "context"
                            },
                            {
                                "content": "",
                                "type": "context"
                            },
                            {
                                "content": "",
                                "type": "context"
                            },
                            {
                                "content": "@pytest.mark.parametrize(\"mode\", (\"RGB\", \"L\"))",
                                "type": "added"
                            },
                            {
                                "content": "@pytest.mark.parametrize(\"xy\", ((W / 2, H / 2), [W / 2, H / 2]))",
                                "type": "added"
                            },
                            {
                                "content": "def test_circle(mode: str, xy: Sequence[float]) -> None:",
                                "type": "added"
                            },
                            {
                                "content": "    # Arrange",
                                "type": "added"
                            },
                            {
                                "content": "    im = Image.new(mode, (W, H))",
                                "type": "added"
                            },
                            {
                                "content": "    draw = ImageDraw.Draw(im)",
                                "type": "added"
                            },
                            {
                                "content": "    expected = f\"Tests/images/imagedraw_ellipse_{mode}.png\"",
                                "type": "added"
                            },
                            {
                                "content": "",
                                "type": "added"
                            },
                            {
                                "content": "    # Act",
                                "type": "added"
                            },
                            {
                                "content": "    draw.circle(xy, 25, fill=\"green\", outline=\"blue\")",
                                "type": "added"
                            },
                            {
                                "content": "",
                                "type": "added"
                            },
                            {
                                "content": "    # Assert",
                                "type": "added"
                            },
                            {
                                "content": "    assert_image_similar_tofile(im, expected, 1)",
                                "type": "added"
                            },
                            {
                                "content": "",
                                "type": "added"
                            },
                            {
                                "content": "",
                                "type": "added"
                            },
                            {
                                "content": "@pytest.mark.parametrize(\"mode\", (\"RGB\", \"L\"))",
                                "type": "context"
                            },
                            {
                                "content": "@pytest.mark.parametrize(\"bbox\", BBOX)",
                                "type": "context"
                            },
                            {
                                "content": "def test_ellipse(mode: str, bbox: Coords) -> None:",
                                "type": "context"
                            }
                        ]
                    },
                    "modified_entities": [],
                    "added_entities": [
                        {
                            "file_name": "Tests/test_imagedraw.py",
                            "type": "function",
                            "name": "test_circle",
                            "content": "def test_circle(mode: str, xy: Sequence[float]) -> None:\n    # Arrange\n    im = Image.new(mode, (W, H))\n    draw = ImageDraw.Draw(im)\n    expected = f\"Tests/images/imagedraw_ellipse_{mode}.png\"\n\n    # Act\n    draw.circle(xy, 25, fill=\"green\", outline=\"blue\")\n\n    # Assert\n    assert_image_similar_tofile(im, expected, 1)",
                            "ast_type_str": "FunctionDef",
                            "start_lineno": 271,
                            "end_lineno": 281,
                            "parent": null
                        }
                    ],
                    "deleted_entities": []
                }
            ]
        },
        {
            "old_file_content": ".. py:module:: PIL.ImageDraw\n.. py:currentmodule:: PIL.ImageDraw\n\n:py:mod:`~PIL.ImageDraw` Module\n===============================\n\nThe :py:mod:`~PIL.ImageDraw` module provides simple 2D graphics for\n:py:class:`~PIL.Image.Image` objects.  You can use this module to create new\nimages, annotate or retouch existing images, and to generate graphics on the\nfly for web use.\n\nFor a more advanced drawing library for PIL, see the `aggdraw module`_.\n\n.. _aggdraw module: https://github.com/pytroll/aggdraw\n\nExample: Draw a gray cross over an image\n----------------------------------------\n\n::\n\n    import sys\n    from PIL import Image, ImageDraw\n\n    with Image.open(\"hopper.jpg\") as im:\n\n        draw = ImageDraw.Draw(im)\n        draw.line((0, 0) + im.size, fill=128)\n        draw.line((0, im.size[1], im.size[0], 0), fill=128)\n\n        # write to stdout\n        im.save(sys.stdout, \"PNG\")\n\n\nConcepts\n--------\n\nCoordinates\n^^^^^^^^^^^\n\nThe graphics interface uses the same coordinate system as PIL itself, with (0,\n0) in the upper left corner. Any pixels drawn outside of the image bounds will\nbe discarded.\n\nColors\n^^^^^^\n\nTo specify colors, you can use numbers or tuples just as you would use with\n:py:meth:`PIL.Image.new` or :py:meth:`PIL.Image.Image.putpixel`. For “1”,\n“L”, and “I” images, use integers. For “RGB” images, use a 3-tuple containing\ninteger values. For “F” images, use integer or floating point values.\n\nFor palette images (mode “P”), use integers as color indexes. In 1.1.4 and\nlater, you can also use RGB 3-tuples or color names (see below). The drawing\nlayer will automatically assign color indexes, as long as you don’t draw with\nmore than 256 colors.\n\nColor Names\n^^^^^^^^^^^\n\nSee :ref:`color-names` for the color names supported by Pillow.\n\nFonts\n^^^^^\n\nPIL can use bitmap fonts or OpenType/TrueType fonts.\n\nBitmap fonts are stored in PIL's own format, where each font typically consists\nof two files, one named .pil and the other usually named .pbm. The former\ncontains font metrics, the latter raster data.\n\nTo load a bitmap font, use the load functions in the :py:mod:`~PIL.ImageFont`\nmodule.\n\nTo load a OpenType/TrueType font, use the truetype function in the\n:py:mod:`~PIL.ImageFont` module. Note that this function depends on third-party\nlibraries, and may not available in all PIL builds.\n\nExample: Draw Partial Opacity Text\n----------------------------------\n\n::\n\n    from PIL import Image, ImageDraw, ImageFont\n\n    # get an image\n    with Image.open(\"Pillow/Tests/images/hopper.png\").convert(\"RGBA\") as base:\n\n        # make a blank image for the text, initialized to transparent text color\n        txt = Image.new(\"RGBA\", base.size, (255, 255, 255, 0))\n\n        # get a font\n        fnt = ImageFont.truetype(\"Pillow/Tests/fonts/FreeMono.ttf\", 40)\n        # get a drawing context\n        d = ImageDraw.Draw(txt)\n\n        # draw text, half opacity\n        d.text((10, 10), \"Hello\", font=fnt, fill=(255, 255, 255, 128))\n        # draw text, full opacity\n        d.text((10, 60), \"World\", font=fnt, fill=(255, 255, 255, 255))\n\n        out = Image.alpha_composite(base, txt)\n\n        out.show()\n\nExample: Draw Multiline Text\n----------------------------\n\n::\n\n    from PIL import Image, ImageDraw, ImageFont\n\n    # create an image\n    out = Image.new(\"RGB\", (150, 100), (255, 255, 255))\n\n    # get a font\n    fnt = ImageFont.truetype(\"Pillow/Tests/fonts/FreeMono.ttf\", 40)\n    # get a drawing context\n    d = ImageDraw.Draw(out)\n\n    # draw multiline text\n    d.multiline_text((10, 10), \"Hello\\nWorld\", font=fnt, fill=(0, 0, 0))\n\n    out.show()\n\n\nFunctions\n---------\n\n.. py:method:: Draw(im, mode=None)\n\n    Creates an object that can be used to draw in the given image.\n\n    Note that the image will be modified in place.\n\n    :param im: The image to draw in.\n    :param mode: Optional mode to use for color values.  For RGB\n        images, this argument can be RGB or RGBA (to blend the\n        drawing into the image).  For all other modes, this argument\n        must be the same as the image mode.  If omitted, the mode\n        defaults to the mode of the image.\n\nAttributes\n----------\n\n.. py:attribute:: ImageDraw.fill\n    :type: bool\n    :value: False\n\n    Selects whether :py:attr:`ImageDraw.ink` should be used as a fill or outline color.\n\n.. py:attribute:: ImageDraw.font\n\n    The current default font.\n\n    Can be set per instance::\n\n        from PIL import ImageDraw, ImageFont\n        draw = ImageDraw.Draw(image)\n        draw.font = ImageFont.truetype(\"Tests/fonts/FreeMono.ttf\")\n\n    Or globally for all future ImageDraw instances::\n\n        from PIL import ImageDraw, ImageFont\n        ImageDraw.ImageDraw.font = ImageFont.truetype(\"Tests/fonts/FreeMono.ttf\")\n\n.. py:attribute:: ImageDraw.fontmode\n\n    The current font drawing mode.\n\n    Set to ``\"1\"`` to disable antialiasing or ``\"L\"`` to enable it.\n\n.. py:attribute:: ImageDraw.ink\n    :type: int\n\n    The internal representation of the current default color.\n\nMethods\n-------\n\n.. py:method:: ImageDraw.getfont()\n\n    Get the current default font, :py:attr:`ImageDraw.font`.\n\n    If the current default font is ``None``,\n    it is initialized with :py:func:`.ImageFont.load_default`.\n\n    :returns: An image font.\n\n.. py:method:: ImageDraw.arc(xy, start, end, fill=None, width=0)\n\n    Draws an arc (a portion of a circle outline) between the start and end\n    angles, inside the given bounding box.\n\n    :param xy: Two points to define the bounding box. Sequence of ``[(x0, y0),\n        (x1, y1)]`` or ``[x0, y0, x1, y1]``, where ``x1 >= x0`` and ``y1 >=\n        y0``.\n    :param start: Starting angle, in degrees. Angles are measured from 3\n        o'clock, increasing clockwise.\n    :param end: Ending angle, in degrees.\n    :param fill: Color to use for the arc.\n    :param width: The line width, in pixels.\n\n        .. versionadded:: 5.3.0\n\n.. py:method:: ImageDraw.bitmap(xy, bitmap, fill=None)\n\n    Draws a bitmap (mask) at the given position, using the current fill color\n    for the non-zero portions. The bitmap should be a valid transparency mask\n    (mode “1”) or matte (mode “L” or “RGBA”).\n\n    This is equivalent to doing ``image.paste(xy, color, bitmap)``.\n\n    To paste pixel data into an image, use the\n    :py:meth:`~PIL.Image.Image.paste` method on the image itself.\n\n.. py:method:: ImageDraw.chord(xy, start, end, fill=None, outline=None, width=1)\n\n    Same as :py:meth:`~PIL.ImageDraw.ImageDraw.arc`, but connects the end points\n    with a straight line.\n\n    :param xy: Two points to define the bounding box. Sequence of ``[(x0, y0),\n        (x1, y1)]`` or ``[x0, y0, x1, y1]``, where ``x1 >= x0`` and ``y1 >=\n        y0``.\n    :param outline: Color to use for the outline.\n    :param fill: Color to use for the fill.\n    :param width: The line width, in pixels.\n\n        .. versionadded:: 5.3.0\n\n.. py:method:: ImageDraw.ellipse(xy, fill=None, outline=None, width=1)\n\n    Draws an ellipse inside the given bounding box.\n\n    :param xy: Two points to define the bounding box. Sequence of either\n        ``[(x0, y0), (x1, y1)]`` or ``[x0, y0, x1, y1]``, where ``x1 >= x0``\n        and ``y1 >= y0``.\n    :param outline: Color to use for the outline.\n    :param fill: Color to use for the fill.\n    :param width: The line width, in pixels.\n\n        .. versionadded:: 5.3.0\n\n.. py:method:: ImageDraw.line(xy, fill=None, width=0, joint=None)\n\n    Draws a line between the coordinates in the ``xy`` list.\n    The coordinate pixels are included in the drawn line.\n\n    :param xy: Sequence of either 2-tuples like ``[(x, y), (x, y), ...]`` or\n               numeric values like ``[x, y, x, y, ...]``.\n    :param fill: Color to use for the line.\n    :param width: The line width, in pixels.\n\n        .. versionadded:: 1.1.5\n\n        .. note:: This option was broken until version 1.1.6.\n    :param joint: Joint type between a sequence of lines. It can be ``\"curve\"``, for rounded edges, or :data:`None`.\n\n        .. versionadded:: 5.3.0\n\n.. py:method:: ImageDraw.pieslice(xy, start, end, fill=None, outline=None, width=1)\n\n    Same as arc, but also draws straight lines between the end points and the\n    center of the bounding box.\n\n    :param xy: Two points to define the bounding box. Sequence of ``[(x0, y0),\n        (x1, y1)]`` or ``[x0, y0, x1, y1]``, where ``x1 >= x0`` and ``y1 >=\n        y0``.\n    :param start: Starting angle, in degrees. Angles are measured from 3\n        o'clock, increasing clockwise.\n    :param end: Ending angle, in degrees.\n    :param fill: Color to use for the fill.\n    :param outline: Color to use for the outline.\n    :param width: The line width, in pixels.\n\n        .. versionadded:: 5.3.0\n\n.. py:method:: ImageDraw.point(xy, fill=None)\n\n    Draws points (individual pixels) at the given coordinates.\n\n    :param xy: Sequence of either 2-tuples like ``[(x, y), (x, y), ...]`` or\n               numeric values like ``[x, y, x, y, ...]``.\n    :param fill: Color to use for the point.\n\n.. py:method:: ImageDraw.polygon(xy, fill=None, outline=None, width=1)\n\n    Draws a polygon.\n\n    The polygon outline consists of straight lines between the given\n    coordinates, plus a straight line between the last and the first\n    coordinate. The coordinate pixels are included in the drawn polygon.\n\n    :param xy: Sequence of either 2-tuples like ``[(x, y), (x, y), ...]`` or\n               numeric values like ``[x, y, x, y, ...]``.\n    :param fill: Color to use for the fill.\n    :param outline: Color to use for the outline.\n    :param width: The line width, in pixels.\n\n\n.. py:method:: ImageDraw.regular_polygon(bounding_circle, n_sides, rotation=0, fill=None, outline=None, width=1)\n\n    Draws a regular polygon inscribed in ``bounding_circle``,\n    with ``n_sides``, and rotation of ``rotation`` degrees.\n\n    :param bounding_circle: The bounding circle is a tuple defined\n        by a point and radius.\n        (e.g. ``bounding_circle=(x, y, r)`` or ``((x, y), r)``).\n        The polygon is inscribed in this circle.\n    :param n_sides: Number of sides\n        (e.g. ``n_sides=3`` for a triangle, ``6`` for a hexagon).\n    :param rotation: Apply an arbitrary rotation to the polygon\n        (e.g. ``rotation=90``, applies a 90 degree rotation).\n    :param fill: Color to use for the fill.\n    :param outline: Color to use for the outline.\n    :param width: The line width, in pixels.\n\n\n.. py:method:: ImageDraw.rectangle(xy, fill=None, outline=None, width=1)\n\n    Draws a rectangle.\n\n    :param xy: Two points to define the bounding box. Sequence of either\n            ``[(x0, y0), (x1, y1)]`` or ``[x0, y0, x1, y1]``, where ``x1 >= x0`` and\n            ``y1 >= y0``. The bounding box is inclusive of both endpoints.\n    :param fill: Color to use for the fill.\n    :param outline: Color to use for the outline.\n    :param width: The line width, in pixels.\n\n        .. versionadded:: 5.3.0\n\n.. py:method:: ImageDraw.rounded_rectangle(xy, radius=0, fill=None, outline=None, width=1, corners=None)\n\n    Draws a rounded rectangle.\n\n    :param xy: Two points to define the bounding box. Sequence of either\n            ``[(x0, y0), (x1, y1)]`` or ``[x0, y0, x1, y1]``, where ``x1 >= x0`` and\n            ``y1 >= y0``. The bounding box is inclusive of both endpoints.\n    :param radius: Radius of the corners.\n    :param fill: Color to use for the fill.\n    :param outline: Color to use for the outline.\n    :param width: The line width, in pixels.\n    :param corners: A tuple of whether to round each corner,\n                    ``(top_left, top_right, bottom_right, bottom_left)``.\n                    Keyword-only argument.\n\n    .. versionadded:: 8.2.0\n\n.. py:method:: ImageDraw.shape(shape, fill=None, outline=None)\n\n    .. warning:: This method is experimental.\n\n    Draw a shape.\n\n.. py:method:: ImageDraw.text(xy, text, fill=None, font=None, anchor=None, spacing=4, align=\"left\", direction=None, features=None, language=None, stroke_width=0, stroke_fill=None, embedded_color=False, font_size=None)\n\n    Draws the string at the given position.\n\n    :param xy: The anchor coordinates of the text.\n    :param text: String to be drawn. If it contains any newline characters,\n                 the text is passed on to\n                 :py:meth:`~PIL.ImageDraw.ImageDraw.multiline_text`.\n    :param fill: Color to use for the text.\n    :param font: An :py:class:`~PIL.ImageFont.ImageFont` instance.\n    :param anchor: The text anchor alignment. Determines the relative location of\n                   the anchor to the text. The default alignment is top left,\n                   specifically ``la`` for horizontal text and ``lt`` for\n                   vertical text. See :ref:`text-anchors` for details.\n                   This parameter is ignored for non-TrueType fonts.\n\n                    .. note:: This parameter was present in earlier versions\n                              of Pillow, but implemented only in version 8.0.0.\n\n    :param spacing: If the text is passed on to\n                    :py:meth:`~PIL.ImageDraw.ImageDraw.multiline_text`,\n                    the number of pixels between lines.\n    :param align: If the text is passed on to\n                  :py:meth:`~PIL.ImageDraw.ImageDraw.multiline_text`,\n                  ``\"left\"``, ``\"center\"`` or ``\"right\"``. Determines the relative alignment of lines.\n                  Use the ``anchor`` parameter to specify the alignment to ``xy``.\n    :param direction: Direction of the text. It can be ``\"rtl\"`` (right to\n                      left), ``\"ltr\"`` (left to right) or ``\"ttb\"`` (top to bottom).\n                      Requires libraqm.\n\n                      .. versionadded:: 4.2.0\n\n    :param features: A list of OpenType font features to be used during text\n                     layout. This is usually used to turn on optional\n                     font features that are not enabled by default,\n                     for example ``\"dlig\"`` or ``\"ss01\"``, but can be also\n                     used to turn off default font features, for\n                     example ``\"-liga\"`` to disable ligatures or ``\"-kern\"``\n                     to disable kerning.  To get all supported\n                     features, see `OpenType docs`_.\n                     Requires libraqm.\n\n                     .. versionadded:: 4.2.0\n\n    :param language: Language of the text. Different languages may use\n                     different glyph shapes or ligatures. This parameter tells\n                     the font which language the text is in, and to apply the\n                     correct substitutions as appropriate, if available.\n                     It should be a `BCP 47 language code`_.\n                     Requires libraqm.\n\n                     .. versionadded:: 6.0.0\n\n    :param stroke_width: The width of the text stroke.\n\n                     .. versionadded:: 6.2.0\n\n    :param stroke_fill: Color to use for the text stroke. If not given, will default to\n        the ``fill`` parameter.\n\n        .. versionadded:: 6.2.0\n\n    :param embedded_color: Whether to use font embedded color glyphs (COLR, CBDT, SBIX).\n\n                    .. versionadded:: 8.0.0\n\n    :param font_size: If ``font`` is not provided, then the size to use for the default\n                      font.\n                      Keyword-only argument.\n\n                    .. versionadded:: 10.1.0\n\n\n.. py:method:: ImageDraw.multiline_text(xy, text, fill=None, font=None, anchor=None, spacing=4, align=\"left\", direction=None, features=None, language=None, stroke_width=0, stroke_fill=None, embedded_color=False, font_size=None)\n\n    Draws the string at the given position.\n\n    :param xy: The anchor coordinates of the text.\n    :param text: String to be drawn.\n    :param fill: Color to use for the text.\n    :param font: An :py:class:`~PIL.ImageFont.ImageFont` instance.\n\n    :param anchor: The text anchor alignment. Determines the relative location of\n                   the anchor to the text. The default alignment is top left,\n                   specifically ``la`` for horizontal text and ``lt`` for\n                   vertical text. See :ref:`text-anchors` for details.\n                   This parameter is ignored for non-TrueType fonts.\n\n                    .. note:: This parameter was present in earlier versions\n                              of Pillow, but implemented only in version 8.0.0.\n\n    :param spacing: The number of pixels between lines.\n    :param align: ``\"left\"``, ``\"center\"`` or ``\"right\"``. Determines the relative alignment of lines.\n                  Use the ``anchor`` parameter to specify the alignment to ``xy``.\n    :param direction: Direction of the text. It can be ``\"rtl\"`` (right to\n                      left), ``\"ltr\"`` (left to right) or ``\"ttb\"`` (top to bottom).\n                      Requires libraqm.\n\n                      .. versionadded:: 4.2.0\n\n    :param features: A list of OpenType font features to be used during text\n                     layout. This is usually used to turn on optional\n                     font features that are not enabled by default,\n                     for example ``\"dlig\"`` or ``\"ss01\"``, but can be also\n                     used to turn off default font features, for\n                     example ``\"-liga\"`` to disable ligatures or ``\"-kern\"``\n                     to disable kerning.  To get all supported\n                     features, see `OpenType docs`_.\n                     Requires libraqm.\n\n                     .. versionadded:: 4.2.0\n\n    :param language: Language of the text. Different languages may use\n                     different glyph shapes or ligatures. This parameter tells\n                     the font which language the text is in, and to apply the\n                     correct substitutions as appropriate, if available.\n                     It should be a `BCP 47 language code`_.\n                     Requires libraqm.\n\n                     .. versionadded:: 6.0.0\n\n    :param stroke_width: The width of the text stroke.\n\n                     .. versionadded:: 6.2.0\n\n    :param stroke_fill: Color to use for the text stroke. If not given, will default to\n                        the ``fill`` parameter.\n\n                     .. versionadded:: 6.2.0\n\n    :param embedded_color: Whether to use font embedded color glyphs (COLR, CBDT, SBIX).\n\n                     .. versionadded:: 8.0.0\n\n    :param font_size: If ``font`` is not provided, then the size to use for the default\n                      font.\n                      Keyword-only argument.\n\n                    .. versionadded:: 10.1.0\n\n.. py:method:: ImageDraw.textlength(text, font=None, direction=None, features=None, language=None, embedded_color=False, font_size=None)\n\n    Returns length (in pixels with 1/64 precision) of given text when rendered\n    in font with provided direction, features, and language.\n\n    This is the amount by which following text should be offset.\n    Text bounding box may extend past the length in some fonts,\n    e.g. when using italics or accents.\n\n    The result is returned as a float; it is a whole number if using basic layout.\n\n    Note that the sum of two lengths may not equal the length of a concatenated\n    string due to kerning. If you need to adjust for kerning, include the following\n    character and subtract its length.\n\n    For example, instead of ::\n\n        hello = draw.textlength(\"Hello\", font)\n        world = draw.textlength(\"World\", font)\n        hello_world = hello + world  # not adjusted for kerning\n        assert hello_world == draw.textlength(\"HelloWorld\", font)  # may fail\n\n    use ::\n\n        hello = draw.textlength(\"HelloW\", font) - draw.textlength(\n            \"W\", font\n        )  # adjusted for kerning\n        world = draw.textlength(\"World\", font)\n        hello_world = hello + world  # adjusted for kerning\n        assert hello_world == draw.textlength(\"HelloWorld\", font)  # True\n\n    or disable kerning with (requires libraqm) ::\n\n        hello = draw.textlength(\"Hello\", font, features=[\"-kern\"])\n        world = draw.textlength(\"World\", font, features=[\"-kern\"])\n        hello_world = hello + world  # kerning is disabled, no need to adjust\n        assert hello_world == draw.textlength(\"HelloWorld\", font, features=[\"-kern\"])  # True\n\n    .. versionadded:: 8.0.0\n\n    :param text: Text to be measured. May not contain any newline characters.\n    :param font: An :py:class:`~PIL.ImageFont.ImageFont` instance.\n    :param direction: Direction of the text. It can be ``\"rtl\"`` (right to\n                      left), ``\"ltr\"`` (left to right) or ``\"ttb\"`` (top to bottom).\n                      Requires libraqm.\n    :param features: A list of OpenType font features to be used during text\n                     layout. This is usually used to turn on optional\n                     font features that are not enabled by default,\n                     for example ``\"dlig\"`` or ``\"ss01\"``, but can be also\n                     used to turn off default font features, for\n                     example ``\"-liga\"`` to disable ligatures or ``\"-kern\"``\n                     to disable kerning.  To get all supported\n                     features, see `OpenType docs`_.\n                     Requires libraqm.\n    :param language: Language of the text. Different languages may use\n                     different glyph shapes or ligatures. This parameter tells\n                     the font which language the text is in, and to apply the\n                     correct substitutions as appropriate, if available.\n                     It should be a `BCP 47 language code`_.\n                     Requires libraqm.\n    :param embedded_color: Whether to use font embedded color glyphs (COLR, CBDT, SBIX).\n    :param font_size: If ``font`` is not provided, then the size to use for the default\n                      font.\n                      Keyword-only argument.\n\n                    .. versionadded:: 10.1.0\n\n    :return: Either width for horizontal text, or height for vertical text.\n\n.. py:method:: ImageDraw.textbbox(xy, text, font=None, anchor=None, spacing=4, align=\"left\", direction=None, features=None, language=None, stroke_width=0, embedded_color=False, font_size=None)\n\n    Returns bounding box (in pixels) of given text relative to given anchor\n    when rendered in font with provided direction, features, and language.\n    Only supported for TrueType fonts.\n\n    Use :py:meth:`textlength` to get the offset of following text with\n    1/64 pixel precision. The bounding box includes extra margins for\n    some fonts, e.g. italics or accents.\n\n    .. versionadded:: 8.0.0\n\n    :param xy: The anchor coordinates of the text.\n    :param text: Text to be measured. If it contains any newline characters,\n                 the text is passed on to\n                 :py:meth:`~PIL.ImageDraw.ImageDraw.multiline_textbbox`.\n    :param font: A :py:class:`~PIL.ImageFont.FreeTypeFont` instance.\n    :param anchor: The text anchor alignment. Determines the relative location of\n                   the anchor to the text. The default alignment is top left,\n                   specifically ``la`` for horizontal text and ``lt`` for\n                   vertical text. See :ref:`text-anchors` for details.\n                   This parameter is ignored for non-TrueType fonts.\n    :param spacing: If the text is passed on to\n                    :py:meth:`~PIL.ImageDraw.ImageDraw.multiline_textbbox`,\n                    the number of pixels between lines.\n    :param align: If the text is passed on to\n                  :py:meth:`~PIL.ImageDraw.ImageDraw.multiline_textbbox`,\n                  ``\"left\"``, ``\"center\"`` or ``\"right\"``. Determines the relative alignment of lines.\n                  Use the ``anchor`` parameter to specify the alignment to ``xy``.\n    :param direction: Direction of the text. It can be ``\"rtl\"`` (right to\n                      left), ``\"ltr\"`` (left to right) or ``\"ttb\"`` (top to bottom).\n                      Requires libraqm.\n    :param features: A list of OpenType font features to be used during text\n                     layout. This is usually used to turn on optional\n                     font features that are not enabled by default,\n                     for example ``\"dlig\"`` or ``\"ss01\"``, but can be also\n                     used to turn off default font features, for\n                     example ``\"-liga\"`` to disable ligatures or ``\"-kern\"``\n                     to disable kerning.  To get all supported\n                     features, see `OpenType docs`_.\n                     Requires libraqm.\n    :param language: Language of the text. Different languages may use\n                     different glyph shapes or ligatures. This parameter tells\n                     the font which language the text is in, and to apply the\n                     correct substitutions as appropriate, if available.\n                     It should be a `BCP 47 language code`_.\n                     Requires libraqm.\n    :param stroke_width: The width of the text stroke.\n    :param embedded_color: Whether to use font embedded color glyphs (COLR, CBDT, SBIX).\n    :param font_size: If ``font`` is not provided, then the size to use for the default\n                      font.\n                      Keyword-only argument.\n\n                    .. versionadded:: 10.1.0\n\n    :return: ``(left, top, right, bottom)`` bounding box\n\n.. py:method:: ImageDraw.multiline_textbbox(xy, text, font=None, anchor=None, spacing=4, align=\"left\", direction=None, features=None, language=None, stroke_width=0, embedded_color=False, font_size=None)\n\n    Returns bounding box (in pixels) of given text relative to given anchor\n    when rendered in font with provided direction, features, and language.\n    Only supported for TrueType fonts.\n\n    Use :py:meth:`textlength` to get the offset of following text with\n    1/64 pixel precision. The bounding box includes extra margins for\n    some fonts, e.g. italics or accents.\n\n    .. versionadded:: 8.0.0\n\n    :param xy: The anchor coordinates of the text.\n    :param text: Text to be measured.\n    :param font: A :py:class:`~PIL.ImageFont.FreeTypeFont` instance.\n    :param anchor: The text anchor alignment. Determines the relative location of\n                   the anchor to the text. The default alignment is top left,\n                   specifically ``la`` for horizontal text and ``lt`` for\n                   vertical text. See :ref:`text-anchors` for details.\n                   This parameter is ignored for non-TrueType fonts.\n    :param spacing: The number of pixels between lines.\n    :param align: ``\"left\"``, ``\"center\"`` or ``\"right\"``. Determines the relative alignment of lines.\n                  Use the ``anchor`` parameter to specify the alignment to ``xy``.\n    :param direction: Direction of the text. It can be ``\"rtl\"`` (right to\n                      left), ``\"ltr\"`` (left to right) or ``\"ttb\"`` (top to bottom).\n                      Requires libraqm.\n    :param features: A list of OpenType font features to be used during text\n                     layout. This is usually used to turn on optional\n                     font features that are not enabled by default,\n                     for example ``\"dlig\"`` or ``\"ss01\"``, but can be also\n                     used to turn off default font features, for\n                     example ``\"-liga\"`` to disable ligatures or ``\"-kern\"``\n                     to disable kerning.  To get all supported\n                     features, see `OpenType docs`_.\n                     Requires libraqm.\n    :param language: Language of the text. Different languages may use\n                     different glyph shapes or ligatures. This parameter tells\n                     the font which language the text is in, and to apply the\n                     correct substitutions as appropriate, if available.\n                     It should be a `BCP 47 language code`_.\n                     Requires libraqm.\n    :param stroke_width: The width of the text stroke.\n    :param embedded_color: Whether to use font embedded color glyphs (COLR, CBDT, SBIX).\n    :param font_size: If ``font`` is not provided, then the size to use for the default\n                      font.\n                      Keyword-only argument.\n\n                    .. versionadded:: 10.1.0\n\n    :return: ``(left, top, right, bottom)`` bounding box\n\n.. py:method:: getdraw(im=None, hints=None)\n\n    .. warning:: This method is experimental.\n\n    A more advanced 2D drawing interface for PIL images,\n    based on the WCK interface.\n\n    :param im: The image to draw in.\n    :param hints: An optional list of hints.\n    :returns: A (drawing context, drawing resource factory) tuple.\n\n.. py:method:: floodfill(image, xy, value, border=None, thresh=0)\n\n    .. warning:: This method is experimental.\n\n    Fills a bounded region with a given color.\n\n    :param image: Target image.\n    :param xy: Seed position (a 2-item coordinate tuple).\n    :param value: Fill color.\n    :param border: Optional border value.  If given, the region consists of\n        pixels with a color different from the border color.  If not given,\n        the region consists of pixels having the same color as the seed\n        pixel.\n    :param thresh: Optional threshold value which specifies a maximum\n        tolerable difference of a pixel value from the 'background' in\n        order for it to be replaced. Useful for filling regions of non-\n        homogeneous, but similar, colors.\n\n.. _BCP 47 language code: https://www.w3.org/International/articles/language-tags/\n.. _OpenType docs: https://learn.microsoft.com/en-us/typography/opentype/spec/featurelist\n",
            "new_file_content": ".. py:module:: PIL.ImageDraw\n.. py:currentmodule:: PIL.ImageDraw\n\n:py:mod:`~PIL.ImageDraw` Module\n===============================\n\nThe :py:mod:`~PIL.ImageDraw` module provides simple 2D graphics for\n:py:class:`~PIL.Image.Image` objects.  You can use this module to create new\nimages, annotate or retouch existing images, and to generate graphics on the\nfly for web use.\n\nFor a more advanced drawing library for PIL, see the `aggdraw module`_.\n\n.. _aggdraw module: https://github.com/pytroll/aggdraw\n\nExample: Draw a gray cross over an image\n----------------------------------------\n\n::\n\n    import sys\n    from PIL import Image, ImageDraw\n\n    with Image.open(\"hopper.jpg\") as im:\n\n        draw = ImageDraw.Draw(im)\n        draw.line((0, 0) + im.size, fill=128)\n        draw.line((0, im.size[1], im.size[0], 0), fill=128)\n\n        # write to stdout\n        im.save(sys.stdout, \"PNG\")\n\n\nConcepts\n--------\n\nCoordinates\n^^^^^^^^^^^\n\nThe graphics interface uses the same coordinate system as PIL itself, with (0,\n0) in the upper left corner. Any pixels drawn outside of the image bounds will\nbe discarded.\n\nColors\n^^^^^^\n\nTo specify colors, you can use numbers or tuples just as you would use with\n:py:meth:`PIL.Image.new` or :py:meth:`PIL.Image.Image.putpixel`. For “1”,\n“L”, and “I” images, use integers. For “RGB” images, use a 3-tuple containing\ninteger values. For “F” images, use integer or floating point values.\n\nFor palette images (mode “P”), use integers as color indexes. In 1.1.4 and\nlater, you can also use RGB 3-tuples or color names (see below). The drawing\nlayer will automatically assign color indexes, as long as you don’t draw with\nmore than 256 colors.\n\nColor Names\n^^^^^^^^^^^\n\nSee :ref:`color-names` for the color names supported by Pillow.\n\nFonts\n^^^^^\n\nPIL can use bitmap fonts or OpenType/TrueType fonts.\n\nBitmap fonts are stored in PIL's own format, where each font typically consists\nof two files, one named .pil and the other usually named .pbm. The former\ncontains font metrics, the latter raster data.\n\nTo load a bitmap font, use the load functions in the :py:mod:`~PIL.ImageFont`\nmodule.\n\nTo load a OpenType/TrueType font, use the truetype function in the\n:py:mod:`~PIL.ImageFont` module. Note that this function depends on third-party\nlibraries, and may not available in all PIL builds.\n\nExample: Draw Partial Opacity Text\n----------------------------------\n\n::\n\n    from PIL import Image, ImageDraw, ImageFont\n\n    # get an image\n    with Image.open(\"Pillow/Tests/images/hopper.png\").convert(\"RGBA\") as base:\n\n        # make a blank image for the text, initialized to transparent text color\n        txt = Image.new(\"RGBA\", base.size, (255, 255, 255, 0))\n\n        # get a font\n        fnt = ImageFont.truetype(\"Pillow/Tests/fonts/FreeMono.ttf\", 40)\n        # get a drawing context\n        d = ImageDraw.Draw(txt)\n\n        # draw text, half opacity\n        d.text((10, 10), \"Hello\", font=fnt, fill=(255, 255, 255, 128))\n        # draw text, full opacity\n        d.text((10, 60), \"World\", font=fnt, fill=(255, 255, 255, 255))\n\n        out = Image.alpha_composite(base, txt)\n\n        out.show()\n\nExample: Draw Multiline Text\n----------------------------\n\n::\n\n    from PIL import Image, ImageDraw, ImageFont\n\n    # create an image\n    out = Image.new(\"RGB\", (150, 100), (255, 255, 255))\n\n    # get a font\n    fnt = ImageFont.truetype(\"Pillow/Tests/fonts/FreeMono.ttf\", 40)\n    # get a drawing context\n    d = ImageDraw.Draw(out)\n\n    # draw multiline text\n    d.multiline_text((10, 10), \"Hello\\nWorld\", font=fnt, fill=(0, 0, 0))\n\n    out.show()\n\n\nFunctions\n---------\n\n.. py:method:: Draw(im, mode=None)\n\n    Creates an object that can be used to draw in the given image.\n\n    Note that the image will be modified in place.\n\n    :param im: The image to draw in.\n    :param mode: Optional mode to use for color values.  For RGB\n        images, this argument can be RGB or RGBA (to blend the\n        drawing into the image).  For all other modes, this argument\n        must be the same as the image mode.  If omitted, the mode\n        defaults to the mode of the image.\n\nAttributes\n----------\n\n.. py:attribute:: ImageDraw.fill\n    :type: bool\n    :value: False\n\n    Selects whether :py:attr:`ImageDraw.ink` should be used as a fill or outline color.\n\n.. py:attribute:: ImageDraw.font\n\n    The current default font.\n\n    Can be set per instance::\n\n        from PIL import ImageDraw, ImageFont\n        draw = ImageDraw.Draw(image)\n        draw.font = ImageFont.truetype(\"Tests/fonts/FreeMono.ttf\")\n\n    Or globally for all future ImageDraw instances::\n\n        from PIL import ImageDraw, ImageFont\n        ImageDraw.ImageDraw.font = ImageFont.truetype(\"Tests/fonts/FreeMono.ttf\")\n\n.. py:attribute:: ImageDraw.fontmode\n\n    The current font drawing mode.\n\n    Set to ``\"1\"`` to disable antialiasing or ``\"L\"`` to enable it.\n\n.. py:attribute:: ImageDraw.ink\n    :type: int\n\n    The internal representation of the current default color.\n\nMethods\n-------\n\n.. py:method:: ImageDraw.getfont()\n\n    Get the current default font, :py:attr:`ImageDraw.font`.\n\n    If the current default font is ``None``,\n    it is initialized with :py:func:`.ImageFont.load_default`.\n\n    :returns: An image font.\n\n.. py:method:: ImageDraw.arc(xy, start, end, fill=None, width=0)\n\n    Draws an arc (a portion of a circle outline) between the start and end\n    angles, inside the given bounding box.\n\n    :param xy: Two points to define the bounding box. Sequence of ``[(x0, y0),\n        (x1, y1)]`` or ``[x0, y0, x1, y1]``, where ``x1 >= x0`` and ``y1 >=\n        y0``.\n    :param start: Starting angle, in degrees. Angles are measured from 3\n        o'clock, increasing clockwise.\n    :param end: Ending angle, in degrees.\n    :param fill: Color to use for the arc.\n    :param width: The line width, in pixels.\n\n        .. versionadded:: 5.3.0\n\n.. py:method:: ImageDraw.bitmap(xy, bitmap, fill=None)\n\n    Draws a bitmap (mask) at the given position, using the current fill color\n    for the non-zero portions. The bitmap should be a valid transparency mask\n    (mode “1”) or matte (mode “L” or “RGBA”).\n\n    This is equivalent to doing ``image.paste(xy, color, bitmap)``.\n\n    To paste pixel data into an image, use the\n    :py:meth:`~PIL.Image.Image.paste` method on the image itself.\n\n.. py:method:: ImageDraw.chord(xy, start, end, fill=None, outline=None, width=1)\n\n    Same as :py:meth:`~PIL.ImageDraw.ImageDraw.arc`, but connects the end points\n    with a straight line.\n\n    :param xy: Two points to define the bounding box. Sequence of ``[(x0, y0),\n        (x1, y1)]`` or ``[x0, y0, x1, y1]``, where ``x1 >= x0`` and ``y1 >=\n        y0``.\n    :param outline: Color to use for the outline.\n    :param fill: Color to use for the fill.\n    :param width: The line width, in pixels.\n\n        .. versionadded:: 5.3.0\n\n.. py:method:: ImageDraw.circle(xy, radius, fill=None, outline=None, width=1)\n\n    Draws a circle with a given radius centering on a point.\n\n    .. versionadded:: 10.4.0\n\n    :param xy: The point for the center of the circle, e.g. ``(x, y)``.\n    :param radius: Radius of the circle.\n    :param outline: Color to use for the outline.\n    :param fill: Color to use for the fill.\n    :param width: The line width, in pixels.\n\n.. py:method:: ImageDraw.ellipse(xy, fill=None, outline=None, width=1)\n\n    Draws an ellipse inside the given bounding box.\n\n    :param xy: Two points to define the bounding box. Sequence of either\n        ``[(x0, y0), (x1, y1)]`` or ``[x0, y0, x1, y1]``, where ``x1 >= x0``\n        and ``y1 >= y0``.\n    :param outline: Color to use for the outline.\n    :param fill: Color to use for the fill.\n    :param width: The line width, in pixels.\n\n        .. versionadded:: 5.3.0\n\n.. py:method:: ImageDraw.line(xy, fill=None, width=0, joint=None)\n\n    Draws a line between the coordinates in the ``xy`` list.\n    The coordinate pixels are included in the drawn line.\n\n    :param xy: Sequence of either 2-tuples like ``[(x, y), (x, y), ...]`` or\n               numeric values like ``[x, y, x, y, ...]``.\n    :param fill: Color to use for the line.\n    :param width: The line width, in pixels.\n\n        .. versionadded:: 1.1.5\n\n        .. note:: This option was broken until version 1.1.6.\n    :param joint: Joint type between a sequence of lines. It can be ``\"curve\"``, for rounded edges, or :data:`None`.\n\n        .. versionadded:: 5.3.0\n\n.. py:method:: ImageDraw.pieslice(xy, start, end, fill=None, outline=None, width=1)\n\n    Same as arc, but also draws straight lines between the end points and the\n    center of the bounding box.\n\n    :param xy: Two points to define the bounding box. Sequence of ``[(x0, y0),\n        (x1, y1)]`` or ``[x0, y0, x1, y1]``, where ``x1 >= x0`` and ``y1 >=\n        y0``.\n    :param start: Starting angle, in degrees. Angles are measured from 3\n        o'clock, increasing clockwise.\n    :param end: Ending angle, in degrees.\n    :param fill: Color to use for the fill.\n    :param outline: Color to use for the outline.\n    :param width: The line width, in pixels.\n\n        .. versionadded:: 5.3.0\n\n.. py:method:: ImageDraw.point(xy, fill=None)\n\n    Draws points (individual pixels) at the given coordinates.\n\n    :param xy: Sequence of either 2-tuples like ``[(x, y), (x, y), ...]`` or\n               numeric values like ``[x, y, x, y, ...]``.\n    :param fill: Color to use for the point.\n\n.. py:method:: ImageDraw.polygon(xy, fill=None, outline=None, width=1)\n\n    Draws a polygon.\n\n    The polygon outline consists of straight lines between the given\n    coordinates, plus a straight line between the last and the first\n    coordinate. The coordinate pixels are included in the drawn polygon.\n\n    :param xy: Sequence of either 2-tuples like ``[(x, y), (x, y), ...]`` or\n               numeric values like ``[x, y, x, y, ...]``.\n    :param fill: Color to use for the fill.\n    :param outline: Color to use for the outline.\n    :param width: The line width, in pixels.\n\n\n.. py:method:: ImageDraw.regular_polygon(bounding_circle, n_sides, rotation=0, fill=None, outline=None, width=1)\n\n    Draws a regular polygon inscribed in ``bounding_circle``,\n    with ``n_sides``, and rotation of ``rotation`` degrees.\n\n    :param bounding_circle: The bounding circle is a tuple defined\n        by a point and radius.\n        (e.g. ``bounding_circle=(x, y, r)`` or ``((x, y), r)``).\n        The polygon is inscribed in this circle.\n    :param n_sides: Number of sides\n        (e.g. ``n_sides=3`` for a triangle, ``6`` for a hexagon).\n    :param rotation: Apply an arbitrary rotation to the polygon\n        (e.g. ``rotation=90``, applies a 90 degree rotation).\n    :param fill: Color to use for the fill.\n    :param outline: Color to use for the outline.\n    :param width: The line width, in pixels.\n\n\n.. py:method:: ImageDraw.rectangle(xy, fill=None, outline=None, width=1)\n\n    Draws a rectangle.\n\n    :param xy: Two points to define the bounding box. Sequence of either\n            ``[(x0, y0), (x1, y1)]`` or ``[x0, y0, x1, y1]``, where ``x1 >= x0`` and\n            ``y1 >= y0``. The bounding box is inclusive of both endpoints.\n    :param fill: Color to use for the fill.\n    :param outline: Color to use for the outline.\n    :param width: The line width, in pixels.\n\n        .. versionadded:: 5.3.0\n\n.. py:method:: ImageDraw.rounded_rectangle(xy, radius=0, fill=None, outline=None, width=1, corners=None)\n\n    Draws a rounded rectangle.\n\n    :param xy: Two points to define the bounding box. Sequence of either\n            ``[(x0, y0), (x1, y1)]`` or ``[x0, y0, x1, y1]``, where ``x1 >= x0`` and\n            ``y1 >= y0``. The bounding box is inclusive of both endpoints.\n    :param radius: Radius of the corners.\n    :param fill: Color to use for the fill.\n    :param outline: Color to use for the outline.\n    :param width: The line width, in pixels.\n    :param corners: A tuple of whether to round each corner,\n                    ``(top_left, top_right, bottom_right, bottom_left)``.\n                    Keyword-only argument.\n\n    .. versionadded:: 8.2.0\n\n.. py:method:: ImageDraw.shape(shape, fill=None, outline=None)\n\n    .. warning:: This method is experimental.\n\n    Draw a shape.\n\n.. py:method:: ImageDraw.text(xy, text, fill=None, font=None, anchor=None, spacing=4, align=\"left\", direction=None, features=None, language=None, stroke_width=0, stroke_fill=None, embedded_color=False, font_size=None)\n\n    Draws the string at the given position.\n\n    :param xy: The anchor coordinates of the text.\n    :param text: String to be drawn. If it contains any newline characters,\n                 the text is passed on to\n                 :py:meth:`~PIL.ImageDraw.ImageDraw.multiline_text`.\n    :param fill: Color to use for the text.\n    :param font: An :py:class:`~PIL.ImageFont.ImageFont` instance.\n    :param anchor: The text anchor alignment. Determines the relative location of\n                   the anchor to the text. The default alignment is top left,\n                   specifically ``la`` for horizontal text and ``lt`` for\n                   vertical text. See :ref:`text-anchors` for details.\n                   This parameter is ignored for non-TrueType fonts.\n\n                    .. note:: This parameter was present in earlier versions\n                              of Pillow, but implemented only in version 8.0.0.\n\n    :param spacing: If the text is passed on to\n                    :py:meth:`~PIL.ImageDraw.ImageDraw.multiline_text`,\n                    the number of pixels between lines.\n    :param align: If the text is passed on to\n                  :py:meth:`~PIL.ImageDraw.ImageDraw.multiline_text`,\n                  ``\"left\"``, ``\"center\"`` or ``\"right\"``. Determines the relative alignment of lines.\n                  Use the ``anchor`` parameter to specify the alignment to ``xy``.\n    :param direction: Direction of the text. It can be ``\"rtl\"`` (right to\n                      left), ``\"ltr\"`` (left to right) or ``\"ttb\"`` (top to bottom).\n                      Requires libraqm.\n\n                      .. versionadded:: 4.2.0\n\n    :param features: A list of OpenType font features to be used during text\n                     layout. This is usually used to turn on optional\n                     font features that are not enabled by default,\n                     for example ``\"dlig\"`` or ``\"ss01\"``, but can be also\n                     used to turn off default font features, for\n                     example ``\"-liga\"`` to disable ligatures or ``\"-kern\"``\n                     to disable kerning.  To get all supported\n                     features, see `OpenType docs`_.\n                     Requires libraqm.\n\n                     .. versionadded:: 4.2.0\n\n    :param language: Language of the text. Different languages may use\n                     different glyph shapes or ligatures. This parameter tells\n                     the font which language the text is in, and to apply the\n                     correct substitutions as appropriate, if available.\n                     It should be a `BCP 47 language code`_.\n                     Requires libraqm.\n\n                     .. versionadded:: 6.0.0\n\n    :param stroke_width: The width of the text stroke.\n\n                     .. versionadded:: 6.2.0\n\n    :param stroke_fill: Color to use for the text stroke. If not given, will default to\n        the ``fill`` parameter.\n\n        .. versionadded:: 6.2.0\n\n    :param embedded_color: Whether to use font embedded color glyphs (COLR, CBDT, SBIX).\n\n                    .. versionadded:: 8.0.0\n\n    :param font_size: If ``font`` is not provided, then the size to use for the default\n                      font.\n                      Keyword-only argument.\n\n                    .. versionadded:: 10.1.0\n\n\n.. py:method:: ImageDraw.multiline_text(xy, text, fill=None, font=None, anchor=None, spacing=4, align=\"left\", direction=None, features=None, language=None, stroke_width=0, stroke_fill=None, embedded_color=False, font_size=None)\n\n    Draws the string at the given position.\n\n    :param xy: The anchor coordinates of the text.\n    :param text: String to be drawn.\n    :param fill: Color to use for the text.\n    :param font: An :py:class:`~PIL.ImageFont.ImageFont` instance.\n\n    :param anchor: The text anchor alignment. Determines the relative location of\n                   the anchor to the text. The default alignment is top left,\n                   specifically ``la`` for horizontal text and ``lt`` for\n                   vertical text. See :ref:`text-anchors` for details.\n                   This parameter is ignored for non-TrueType fonts.\n\n                    .. note:: This parameter was present in earlier versions\n                              of Pillow, but implemented only in version 8.0.0.\n\n    :param spacing: The number of pixels between lines.\n    :param align: ``\"left\"``, ``\"center\"`` or ``\"right\"``. Determines the relative alignment of lines.\n                  Use the ``anchor`` parameter to specify the alignment to ``xy``.\n    :param direction: Direction of the text. It can be ``\"rtl\"`` (right to\n                      left), ``\"ltr\"`` (left to right) or ``\"ttb\"`` (top to bottom).\n                      Requires libraqm.\n\n                      .. versionadded:: 4.2.0\n\n    :param features: A list of OpenType font features to be used during text\n                     layout. This is usually used to turn on optional\n                     font features that are not enabled by default,\n                     for example ``\"dlig\"`` or ``\"ss01\"``, but can be also\n                     used to turn off default font features, for\n                     example ``\"-liga\"`` to disable ligatures or ``\"-kern\"``\n                     to disable kerning.  To get all supported\n                     features, see `OpenType docs`_.\n                     Requires libraqm.\n\n                     .. versionadded:: 4.2.0\n\n    :param language: Language of the text. Different languages may use\n                     different glyph shapes or ligatures. This parameter tells\n                     the font which language the text is in, and to apply the\n                     correct substitutions as appropriate, if available.\n                     It should be a `BCP 47 language code`_.\n                     Requires libraqm.\n\n                     .. versionadded:: 6.0.0\n\n    :param stroke_width: The width of the text stroke.\n\n                     .. versionadded:: 6.2.0\n\n    :param stroke_fill: Color to use for the text stroke. If not given, will default to\n                        the ``fill`` parameter.\n\n                     .. versionadded:: 6.2.0\n\n    :param embedded_color: Whether to use font embedded color glyphs (COLR, CBDT, SBIX).\n\n                     .. versionadded:: 8.0.0\n\n    :param font_size: If ``font`` is not provided, then the size to use for the default\n                      font.\n                      Keyword-only argument.\n\n                    .. versionadded:: 10.1.0\n\n.. py:method:: ImageDraw.textlength(text, font=None, direction=None, features=None, language=None, embedded_color=False, font_size=None)\n\n    Returns length (in pixels with 1/64 precision) of given text when rendered\n    in font with provided direction, features, and language.\n\n    This is the amount by which following text should be offset.\n    Text bounding box may extend past the length in some fonts,\n    e.g. when using italics or accents.\n\n    The result is returned as a float; it is a whole number if using basic layout.\n\n    Note that the sum of two lengths may not equal the length of a concatenated\n    string due to kerning. If you need to adjust for kerning, include the following\n    character and subtract its length.\n\n    For example, instead of ::\n\n        hello = draw.textlength(\"Hello\", font)\n        world = draw.textlength(\"World\", font)\n        hello_world = hello + world  # not adjusted for kerning\n        assert hello_world == draw.textlength(\"HelloWorld\", font)  # may fail\n\n    use ::\n\n        hello = draw.textlength(\"HelloW\", font) - draw.textlength(\n            \"W\", font\n        )  # adjusted for kerning\n        world = draw.textlength(\"World\", font)\n        hello_world = hello + world  # adjusted for kerning\n        assert hello_world == draw.textlength(\"HelloWorld\", font)  # True\n\n    or disable kerning with (requires libraqm) ::\n\n        hello = draw.textlength(\"Hello\", font, features=[\"-kern\"])\n        world = draw.textlength(\"World\", font, features=[\"-kern\"])\n        hello_world = hello + world  # kerning is disabled, no need to adjust\n        assert hello_world == draw.textlength(\"HelloWorld\", font, features=[\"-kern\"])  # True\n\n    .. versionadded:: 8.0.0\n\n    :param text: Text to be measured. May not contain any newline characters.\n    :param font: An :py:class:`~PIL.ImageFont.ImageFont` instance.\n    :param direction: Direction of the text. It can be ``\"rtl\"`` (right to\n                      left), ``\"ltr\"`` (left to right) or ``\"ttb\"`` (top to bottom).\n                      Requires libraqm.\n    :param features: A list of OpenType font features to be used during text\n                     layout. This is usually used to turn on optional\n                     font features that are not enabled by default,\n                     for example ``\"dlig\"`` or ``\"ss01\"``, but can be also\n                     used to turn off default font features, for\n                     example ``\"-liga\"`` to disable ligatures or ``\"-kern\"``\n                     to disable kerning.  To get all supported\n                     features, see `OpenType docs`_.\n                     Requires libraqm.\n    :param language: Language of the text. Different languages may use\n                     different glyph shapes or ligatures. This parameter tells\n                     the font which language the text is in, and to apply the\n                     correct substitutions as appropriate, if available.\n                     It should be a `BCP 47 language code`_.\n                     Requires libraqm.\n    :param embedded_color: Whether to use font embedded color glyphs (COLR, CBDT, SBIX).\n    :param font_size: If ``font`` is not provided, then the size to use for the default\n                      font.\n                      Keyword-only argument.\n\n                    .. versionadded:: 10.1.0\n\n    :return: Either width for horizontal text, or height for vertical text.\n\n.. py:method:: ImageDraw.textbbox(xy, text, font=None, anchor=None, spacing=4, align=\"left\", direction=None, features=None, language=None, stroke_width=0, embedded_color=False, font_size=None)\n\n    Returns bounding box (in pixels) of given text relative to given anchor\n    when rendered in font with provided direction, features, and language.\n    Only supported for TrueType fonts.\n\n    Use :py:meth:`textlength` to get the offset of following text with\n    1/64 pixel precision. The bounding box includes extra margins for\n    some fonts, e.g. italics or accents.\n\n    .. versionadded:: 8.0.0\n\n    :param xy: The anchor coordinates of the text.\n    :param text: Text to be measured. If it contains any newline characters,\n                 the text is passed on to\n                 :py:meth:`~PIL.ImageDraw.ImageDraw.multiline_textbbox`.\n    :param font: A :py:class:`~PIL.ImageFont.FreeTypeFont` instance.\n    :param anchor: The text anchor alignment. Determines the relative location of\n                   the anchor to the text. The default alignment is top left,\n                   specifically ``la`` for horizontal text and ``lt`` for\n                   vertical text. See :ref:`text-anchors` for details.\n                   This parameter is ignored for non-TrueType fonts.\n    :param spacing: If the text is passed on to\n                    :py:meth:`~PIL.ImageDraw.ImageDraw.multiline_textbbox`,\n                    the number of pixels between lines.\n    :param align: If the text is passed on to\n                  :py:meth:`~PIL.ImageDraw.ImageDraw.multiline_textbbox`,\n                  ``\"left\"``, ``\"center\"`` or ``\"right\"``. Determines the relative alignment of lines.\n                  Use the ``anchor`` parameter to specify the alignment to ``xy``.\n    :param direction: Direction of the text. It can be ``\"rtl\"`` (right to\n                      left), ``\"ltr\"`` (left to right) or ``\"ttb\"`` (top to bottom).\n                      Requires libraqm.\n    :param features: A list of OpenType font features to be used during text\n                     layout. This is usually used to turn on optional\n                     font features that are not enabled by default,\n                     for example ``\"dlig\"`` or ``\"ss01\"``, but can be also\n                     used to turn off default font features, for\n                     example ``\"-liga\"`` to disable ligatures or ``\"-kern\"``\n                     to disable kerning.  To get all supported\n                     features, see `OpenType docs`_.\n                     Requires libraqm.\n    :param language: Language of the text. Different languages may use\n                     different glyph shapes or ligatures. This parameter tells\n                     the font which language the text is in, and to apply the\n                     correct substitutions as appropriate, if available.\n                     It should be a `BCP 47 language code`_.\n                     Requires libraqm.\n    :param stroke_width: The width of the text stroke.\n    :param embedded_color: Whether to use font embedded color glyphs (COLR, CBDT, SBIX).\n    :param font_size: If ``font`` is not provided, then the size to use for the default\n                      font.\n                      Keyword-only argument.\n\n                    .. versionadded:: 10.1.0\n\n    :return: ``(left, top, right, bottom)`` bounding box\n\n.. py:method:: ImageDraw.multiline_textbbox(xy, text, font=None, anchor=None, spacing=4, align=\"left\", direction=None, features=None, language=None, stroke_width=0, embedded_color=False, font_size=None)\n\n    Returns bounding box (in pixels) of given text relative to given anchor\n    when rendered in font with provided direction, features, and language.\n    Only supported for TrueType fonts.\n\n    Use :py:meth:`textlength` to get the offset of following text with\n    1/64 pixel precision. The bounding box includes extra margins for\n    some fonts, e.g. italics or accents.\n\n    .. versionadded:: 8.0.0\n\n    :param xy: The anchor coordinates of the text.\n    :param text: Text to be measured.\n    :param font: A :py:class:`~PIL.ImageFont.FreeTypeFont` instance.\n    :param anchor: The text anchor alignment. Determines the relative location of\n                   the anchor to the text. The default alignment is top left,\n                   specifically ``la`` for horizontal text and ``lt`` for\n                   vertical text. See :ref:`text-anchors` for details.\n                   This parameter is ignored for non-TrueType fonts.\n    :param spacing: The number of pixels between lines.\n    :param align: ``\"left\"``, ``\"center\"`` or ``\"right\"``. Determines the relative alignment of lines.\n                  Use the ``anchor`` parameter to specify the alignment to ``xy``.\n    :param direction: Direction of the text. It can be ``\"rtl\"`` (right to\n                      left), ``\"ltr\"`` (left to right) or ``\"ttb\"`` (top to bottom).\n                      Requires libraqm.\n    :param features: A list of OpenType font features to be used during text\n                     layout. This is usually used to turn on optional\n                     font features that are not enabled by default,\n                     for example ``\"dlig\"`` or ``\"ss01\"``, but can be also\n                     used to turn off default font features, for\n                     example ``\"-liga\"`` to disable ligatures or ``\"-kern\"``\n                     to disable kerning.  To get all supported\n                     features, see `OpenType docs`_.\n                     Requires libraqm.\n    :param language: Language of the text. Different languages may use\n                     different glyph shapes or ligatures. This parameter tells\n                     the font which language the text is in, and to apply the\n                     correct substitutions as appropriate, if available.\n                     It should be a `BCP 47 language code`_.\n                     Requires libraqm.\n    :param stroke_width: The width of the text stroke.\n    :param embedded_color: Whether to use font embedded color glyphs (COLR, CBDT, SBIX).\n    :param font_size: If ``font`` is not provided, then the size to use for the default\n                      font.\n                      Keyword-only argument.\n\n                    .. versionadded:: 10.1.0\n\n    :return: ``(left, top, right, bottom)`` bounding box\n\n.. py:method:: getdraw(im=None, hints=None)\n\n    .. warning:: This method is experimental.\n\n    A more advanced 2D drawing interface for PIL images,\n    based on the WCK interface.\n\n    :param im: The image to draw in.\n    :param hints: An optional list of hints.\n    :returns: A (drawing context, drawing resource factory) tuple.\n\n.. py:method:: floodfill(image, xy, value, border=None, thresh=0)\n\n    .. warning:: This method is experimental.\n\n    Fills a bounded region with a given color.\n\n    :param image: Target image.\n    :param xy: Seed position (a 2-item coordinate tuple).\n    :param value: Fill color.\n    :param border: Optional border value.  If given, the region consists of\n        pixels with a color different from the border color.  If not given,\n        the region consists of pixels having the same color as the seed\n        pixel.\n    :param thresh: Optional threshold value which specifies a maximum\n        tolerable difference of a pixel value from the 'background' in\n        order for it to be replaced. Useful for filling regions of non-\n        homogeneous, but similar, colors.\n\n.. _BCP 47 language code: https://www.w3.org/International/articles/language-tags/\n.. _OpenType docs: https://learn.microsoft.com/en-us/typography/opentype/spec/featurelist\n",
            "header": {
                "file": {
                    "path": "docs/reference/ImageDraw.rst"
                },
                "misc_line": null
            },
            "index_line": {
                "old_commit_hash": "4ccfacae7",
                "new_commit_hash": "1404869ca",
                "mode": "100644"
            },
            "is_binary_file": false,
            "binary_line": null,
            "minus_file": {
                "path": "a/docs/reference/ImageDraw.rst"
            },
            "plus_file": {
                "path": "b/docs/reference/ImageDraw.rst"
            },
            "hunks": [
                {
                    "descriptor": {
                        "old_range": {
                            "start": 227,
                            "length": 6
                        },
                        "new_range": {
                            "start": 227,
                            "length": 18
                        },
                        "section": "Methods"
                    },
                    "line_group": {
                        "all_lines": [
                            {
                                "content": "",
                                "type": "context"
                            },
                            {
                                "content": "        .. versionadded:: 5.3.0",
                                "type": "context"
                            },
                            {
                                "content": "",
                                "type": "context"
                            },
                            {
                                "content": ".. py:method:: ImageDraw.circle(xy, radius, fill=None, outline=None, width=1)",
                                "type": "added"
                            },
                            {
                                "content": "",
                                "type": "added"
                            },
                            {
                                "content": "    Draws a circle with a given radius centering on a point.",
                                "type": "added"
                            },
                            {
                                "content": "",
                                "type": "added"
                            },
                            {
                                "content": "    .. versionadded:: 10.4.0",
                                "type": "added"
                            },
                            {
                                "content": "",
                                "type": "added"
                            },
                            {
                                "content": "    :param xy: The point for the center of the circle, e.g. ``(x, y)``.",
                                "type": "added"
                            },
                            {
                                "content": "    :param radius: Radius of the circle.",
                                "type": "added"
                            },
                            {
                                "content": "    :param outline: Color to use for the outline.",
                                "type": "added"
                            },
                            {
                                "content": "    :param fill: Color to use for the fill.",
                                "type": "added"
                            },
                            {
                                "content": "    :param width: The line width, in pixels.",
                                "type": "added"
                            },
                            {
                                "content": "",
                                "type": "added"
                            },
                            {
                                "content": ".. py:method:: ImageDraw.ellipse(xy, fill=None, outline=None, width=1)",
                                "type": "context"
                            },
                            {
                                "content": "",
                                "type": "context"
                            },
                            {
                                "content": "    Draws an ellipse inside the given bounding box.",
                                "type": "context"
                            }
                        ]
                    },
                    "modified_entities": [],
                    "added_entities": [],
                    "deleted_entities": []
                }
            ]
        },
        {
            "old_file_content": "10.4.0\n------\n\nSecurity\n========\n\nTODO\n^^^^\n\nTODO\n\n:cve:`YYYY-XXXXX`: TODO\n^^^^^^^^^^^^^^^^^^^^^^^\n\nTODO\n\nBackwards Incompatible Changes\n==============================\n\nTODO\n^^^^\n\nDeprecations\n============\n\nBGR;15, BGR 16 and BGR;24\n^^^^^^^^^^^^^^^^^^^^^^^^^\n\nThe experimental BGR;15, BGR;16 and BGR;24 modes have been deprecated.\n\nSupport for LibTIFF earlier than 4\n^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n\nSupport for LibTIFF earlier than version 4 has been deprecated.\nUpgrade to a newer version of LibTIFF instead.\n\nAPI Changes\n===========\n\nTODO\n^^^^\n\nTODO\n\nAPI Additions\n=============\n\nTODO\n^^^^\n\nTODO\n\nOther Changes\n=============\n\nPython 3.13 beta\n^^^^^^^^^^^^^^^^\n\nTo help others prepare for Python 3.13, wheels have been built against the 3.13 beta as\na preview. This is not official support for Python 3.13, but simply an opportunity for\nusers to test how Pillow works with the beta and report any problems.\n",
            "new_file_content": "10.4.0\n------\n\nSecurity\n========\n\nTODO\n^^^^\n\nTODO\n\n:cve:`YYYY-XXXXX`: TODO\n^^^^^^^^^^^^^^^^^^^^^^^\n\nTODO\n\nBackwards Incompatible Changes\n==============================\n\nTODO\n^^^^\n\nDeprecations\n============\n\nBGR;15, BGR 16 and BGR;24\n^^^^^^^^^^^^^^^^^^^^^^^^^\n\nThe experimental BGR;15, BGR;16 and BGR;24 modes have been deprecated.\n\nSupport for LibTIFF earlier than 4\n^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n\nSupport for LibTIFF earlier than version 4 has been deprecated.\nUpgrade to a newer version of LibTIFF instead.\n\nAPI Changes\n===========\n\nTODO\n^^^^\n\nTODO\n\nAPI Additions\n=============\n\nImageDraw.circle\n^^^^^^^^^^^^^^^^\n\nAdded :py:meth:`~PIL.ImageDraw.ImageDraw.circle`. It provides the same functionality as\n:py:meth:`~PIL.ImageDraw.ImageDraw.ellipse`, but instead of taking a bounding box, it\ntakes a center point and radius.\n\nTODO\n^^^^\n\nTODO\n\nOther Changes\n=============\n\nPython 3.13 beta\n^^^^^^^^^^^^^^^^\n\nTo help others prepare for Python 3.13, wheels have been built against the 3.13 beta as\na preview. This is not official support for Python 3.13, but simply an opportunity for\nusers to test how Pillow works with the beta and report any problems.\n",
            "header": {
                "file": {
                    "path": "docs/releasenotes/10.4.0.rst"
                },
                "misc_line": null
            },
            "index_line": {
                "old_commit_hash": "41f33102f",
                "new_commit_hash": "e0d695a8b",
                "mode": "100644"
            },
            "is_binary_file": false,
            "binary_line": null,
            "minus_file": {
                "path": "a/docs/releasenotes/10.4.0.rst"
            },
            "plus_file": {
                "path": "b/docs/releasenotes/10.4.0.rst"
            },
            "hunks": [
                {
                    "descriptor": {
                        "old_range": {
                            "start": 45,
                            "length": 6
                        },
                        "new_range": {
                            "start": 45,
                            "length": 13
                        },
                        "section": "TODO"
                    },
                    "line_group": {
                        "all_lines": [
                            {
                                "content": "API Additions",
                                "type": "context"
                            },
                            {
                                "content": "=============",
                                "type": "context"
                            },
                            {
                                "content": "",
                                "type": "context"
                            },
                            {
                                "content": "ImageDraw.circle",
                                "type": "added"
                            },
                            {
                                "content": "^^^^^^^^^^^^^^^^",
                                "type": "added"
                            },
                            {
                                "content": "",
                                "type": "added"
                            },
                            {
                                "content": "Added :py:meth:`~PIL.ImageDraw.ImageDraw.circle`. It provides the same functionality as",
                                "type": "added"
                            },
                            {
                                "content": ":py:meth:`~PIL.ImageDraw.ImageDraw.ellipse`, but instead of taking a bounding box, it",
                                "type": "added"
                            },
                            {
                                "content": "takes a center point and radius.",
                                "type": "added"
                            },
                            {
                                "content": "",
                                "type": "added"
                            },
                            {
                                "content": "TODO",
                                "type": "context"
                            },
                            {
                                "content": "^^^^",
                                "type": "context"
                            },
                            {
                                "content": "",
                                "type": "context"
                            }
                        ]
                    },
                    "modified_entities": [],
                    "added_entities": [],
                    "deleted_entities": []
                }
            ]
        },
        {
            "old_file_content": "#\n# The Python Imaging Library\n# $Id$\n#\n# drawing interface operations\n#\n# History:\n# 1996-04-13 fl   Created (experimental)\n# 1996-08-07 fl   Filled polygons, ellipses.\n# 1996-08-13 fl   Added text support\n# 1998-06-28 fl   Handle I and F images\n# 1998-12-29 fl   Added arc; use arc primitive to draw ellipses\n# 1999-01-10 fl   Added shape stuff (experimental)\n# 1999-02-06 fl   Added bitmap support\n# 1999-02-11 fl   Changed all primitives to take options\n# 1999-02-20 fl   Fixed backwards compatibility\n# 2000-10-12 fl   Copy on write, when necessary\n# 2001-02-18 fl   Use default ink for bitmap/text also in fill mode\n# 2002-10-24 fl   Added support for CSS-style color strings\n# 2002-12-10 fl   Added experimental support for RGBA-on-RGB drawing\n# 2002-12-11 fl   Refactored low-level drawing API (work in progress)\n# 2004-08-26 fl   Made Draw() a factory function, added getdraw() support\n# 2004-09-04 fl   Added width support to line primitive\n# 2004-09-10 fl   Added font mode handling\n# 2006-06-19 fl   Added font bearing support (getmask2)\n#\n# Copyright (c) 1997-2006 by Secret Labs AB\n# Copyright (c) 1996-2006 by Fredrik Lundh\n#\n# See the README file for information on usage and redistribution.\n#\nfrom __future__ import annotations\n\nimport math\nimport numbers\nimport struct\nfrom typing import TYPE_CHECKING, Sequence, cast\n\nfrom . import Image, ImageColor\nfrom ._typing import Coords\n\n\"\"\"\nA simple 2D drawing interface for PIL images.\n<p>\nApplication code should use the <b>Draw</b> factory, instead of\ndirectly.\n\"\"\"\n\n\nclass ImageDraw:\n    font = None\n\n    def __init__(self, im: Image.Image, mode: str | None = None) -> None:\n        \"\"\"\n        Create a drawing instance.\n\n        :param im: The image to draw in.\n        :param mode: Optional mode to use for color values.  For RGB\n           images, this argument can be RGB or RGBA (to blend the\n           drawing into the image).  For all other modes, this argument\n           must be the same as the image mode.  If omitted, the mode\n           defaults to the mode of the image.\n        \"\"\"\n        im.load()\n        if im.readonly:\n            im._copy()  # make it writeable\n        blend = 0\n        if mode is None:\n            mode = im.mode\n        if mode != im.mode:\n            if mode == \"RGBA\" and im.mode == \"RGB\":\n                blend = 1\n            else:\n                msg = \"mode mismatch\"\n                raise ValueError(msg)\n        if mode == \"P\":\n            self.palette = im.palette\n        else:\n            self.palette = None\n        self._image = im\n        self.im = im.im\n        self.draw = Image.core.draw(self.im, blend)\n        self.mode = mode\n        if mode in (\"I\", \"F\"):\n            self.ink = self.draw.draw_ink(1)\n        else:\n            self.ink = self.draw.draw_ink(-1)\n        if mode in (\"1\", \"P\", \"I\", \"F\"):\n            # FIXME: fix Fill2 to properly support matte for I+F images\n            self.fontmode = \"1\"\n        else:\n            self.fontmode = \"L\"  # aliasing is okay for other modes\n        self.fill = False\n\n    if TYPE_CHECKING:\n        from . import ImageFont\n\n    def getfont(self) -> ImageFont.FreeTypeFont | ImageFont.ImageFont:\n        \"\"\"\n        Get the current default font.\n\n        To set the default font for this ImageDraw instance::\n\n            from PIL import ImageDraw, ImageFont\n            draw.font = ImageFont.truetype(\"Tests/fonts/FreeMono.ttf\")\n\n        To set the default font for all future ImageDraw instances::\n\n            from PIL import ImageDraw, ImageFont\n            ImageDraw.ImageDraw.font = ImageFont.truetype(\"Tests/fonts/FreeMono.ttf\")\n\n        If the current default font is ``None``,\n        it is initialized with ``ImageFont.load_default()``.\n\n        :returns: An image font.\"\"\"\n        if not self.font:\n            # FIXME: should add a font repository\n            from . import ImageFont\n\n            self.font = ImageFont.load_default()\n        return self.font\n\n    def _getfont(self, font_size: float | None):\n        if font_size is not None:\n            from . import ImageFont\n\n            font = ImageFont.load_default(font_size)\n        else:\n            font = self.getfont()\n        return font\n\n    def _getink(self, ink, fill=None) -> tuple[int | None, int | None]:\n        if ink is None and fill is None:\n            if self.fill:\n                fill = self.ink\n            else:\n                ink = self.ink\n        else:\n            if ink is not None:\n                if isinstance(ink, str):\n                    ink = ImageColor.getcolor(ink, self.mode)\n                if self.palette and not isinstance(ink, numbers.Number):\n                    ink = self.palette.getcolor(ink, self._image)\n                ink = self.draw.draw_ink(ink)\n            if fill is not None:\n                if isinstance(fill, str):\n                    fill = ImageColor.getcolor(fill, self.mode)\n                if self.palette and not isinstance(fill, numbers.Number):\n                    fill = self.palette.getcolor(fill, self._image)\n                fill = self.draw.draw_ink(fill)\n        return ink, fill\n\n    def arc(self, xy: Coords, start, end, fill=None, width=1) -> None:\n        \"\"\"Draw an arc.\"\"\"\n        ink, fill = self._getink(fill)\n        if ink is not None:\n            self.draw.draw_arc(xy, start, end, ink, width)\n\n    def bitmap(self, xy: Sequence[int], bitmap, fill=None) -> None:\n        \"\"\"Draw a bitmap.\"\"\"\n        bitmap.load()\n        ink, fill = self._getink(fill)\n        if ink is None:\n            ink = fill\n        if ink is not None:\n            self.draw.draw_bitmap(xy, bitmap.im, ink)\n\n    def chord(self, xy: Coords, start, end, fill=None, outline=None, width=1) -> None:\n        \"\"\"Draw a chord.\"\"\"\n        ink, fill = self._getink(outline, fill)\n        if fill is not None:\n            self.draw.draw_chord(xy, start, end, fill, 1)\n        if ink is not None and ink != fill and width != 0:\n            self.draw.draw_chord(xy, start, end, ink, 0, width)\n\n    def ellipse(self, xy: Coords, fill=None, outline=None, width=1) -> None:\n        \"\"\"Draw an ellipse.\"\"\"\n        ink, fill = self._getink(outline, fill)\n        if fill is not None:\n            self.draw.draw_ellipse(xy, fill, 1)\n        if ink is not None and ink != fill and width != 0:\n            self.draw.draw_ellipse(xy, ink, 0, width)\n\n    def line(self, xy: Coords, fill=None, width=0, joint=None) -> None:\n        \"\"\"Draw a line, or a connected sequence of line segments.\"\"\"\n        ink = self._getink(fill)[0]\n        if ink is not None:\n            self.draw.draw_lines(xy, ink, width)\n            if joint == \"curve\" and width > 4:\n                points: Sequence[Sequence[float]]\n                if isinstance(xy[0], (list, tuple)):\n                    points = cast(Sequence[Sequence[float]], xy)\n                else:\n                    points = [\n                        cast(Sequence[float], tuple(xy[i : i + 2]))\n                        for i in range(0, len(xy), 2)\n                    ]\n                for i in range(1, len(points) - 1):\n                    point = points[i]\n                    angles = [\n                        math.degrees(math.atan2(end[0] - start[0], start[1] - end[1]))\n                        % 360\n                        for start, end in (\n                            (points[i - 1], point),\n                            (point, points[i + 1]),\n                        )\n                    ]\n                    if angles[0] == angles[1]:\n                        # This is a straight line, so no joint is required\n                        continue\n\n                    def coord_at_angle(coord, angle):\n                        x, y = coord\n                        angle -= 90\n                        distance = width / 2 - 1\n                        return tuple(\n                            p + (math.floor(p_d) if p_d > 0 else math.ceil(p_d))\n                            for p, p_d in (\n                                (x, distance * math.cos(math.radians(angle))),\n                                (y, distance * math.sin(math.radians(angle))),\n                            )\n                        )\n\n                    flipped = (\n                        angles[1] > angles[0] and angles[1] - 180 > angles[0]\n                    ) or (angles[1] < angles[0] and angles[1] + 180 > angles[0])\n                    coords = [\n                        (point[0] - width / 2 + 1, point[1] - width / 2 + 1),\n                        (point[0] + width / 2 - 1, point[1] + width / 2 - 1),\n                    ]\n                    if flipped:\n                        start, end = (angles[1] + 90, angles[0] + 90)\n                    else:\n                        start, end = (angles[0] - 90, angles[1] - 90)\n                    self.pieslice(coords, start - 90, end - 90, fill)\n\n                    if width > 8:\n                        # Cover potential gaps between the line and the joint\n                        if flipped:\n                            gap_coords = [\n                                coord_at_angle(point, angles[0] + 90),\n                                point,\n                                coord_at_angle(point, angles[1] + 90),\n                            ]\n                        else:\n                            gap_coords = [\n                                coord_at_angle(point, angles[0] - 90),\n                                point,\n                                coord_at_angle(point, angles[1] - 90),\n                            ]\n                        self.line(gap_coords, fill, width=3)\n\n    def shape(self, shape, fill=None, outline=None) -> None:\n        \"\"\"(Experimental) Draw a shape.\"\"\"\n        shape.close()\n        ink, fill = self._getink(outline, fill)\n        if fill is not None:\n            self.draw.draw_outline(shape, fill, 1)\n        if ink is not None and ink != fill:\n            self.draw.draw_outline(shape, ink, 0)\n\n    def pieslice(\n        self, xy: Coords, start, end, fill=None, outline=None, width=1\n    ) -> None:\n        \"\"\"Draw a pieslice.\"\"\"\n        ink, fill = self._getink(outline, fill)\n        if fill is not None:\n            self.draw.draw_pieslice(xy, start, end, fill, 1)\n        if ink is not None and ink != fill and width != 0:\n            self.draw.draw_pieslice(xy, start, end, ink, 0, width)\n\n    def point(self, xy: Coords, fill=None) -> None:\n        \"\"\"Draw one or more individual pixels.\"\"\"\n        ink, fill = self._getink(fill)\n        if ink is not None:\n            self.draw.draw_points(xy, ink)\n\n    def polygon(self, xy: Coords, fill=None, outline=None, width=1) -> None:\n        \"\"\"Draw a polygon.\"\"\"\n        ink, fill = self._getink(outline, fill)\n        if fill is not None:\n            self.draw.draw_polygon(xy, fill, 1)\n        if ink is not None and ink != fill and width != 0:\n            if width == 1:\n                self.draw.draw_polygon(xy, ink, 0, width)\n            elif self.im is not None:\n                # To avoid expanding the polygon outwards,\n                # use the fill as a mask\n                mask = Image.new(\"1\", self.im.size)\n                mask_ink = self._getink(1)[0]\n\n                fill_im = mask.copy()\n                draw = Draw(fill_im)\n                draw.draw.draw_polygon(xy, mask_ink, 1)\n\n                ink_im = mask.copy()\n                draw = Draw(ink_im)\n                width = width * 2 - 1\n                draw.draw.draw_polygon(xy, mask_ink, 0, width)\n\n                mask.paste(ink_im, mask=fill_im)\n\n                im = Image.new(self.mode, self.im.size)\n                draw = Draw(im)\n                draw.draw.draw_polygon(xy, ink, 0, width)\n                self.im.paste(im.im, (0, 0) + im.size, mask.im)\n\n    def regular_polygon(\n        self, bounding_circle, n_sides, rotation=0, fill=None, outline=None, width=1\n    ) -> None:\n        \"\"\"Draw a regular polygon.\"\"\"\n        xy = _compute_regular_polygon_vertices(bounding_circle, n_sides, rotation)\n        self.polygon(xy, fill, outline, width)\n\n    def rectangle(self, xy: Coords, fill=None, outline=None, width=1) -> None:\n        \"\"\"Draw a rectangle.\"\"\"\n        ink, fill = self._getink(outline, fill)\n        if fill is not None:\n            self.draw.draw_rectangle(xy, fill, 1)\n        if ink is not None and ink != fill and width != 0:\n            self.draw.draw_rectangle(xy, ink, 0, width)\n\n    def rounded_rectangle(\n        self, xy: Coords, radius=0, fill=None, outline=None, width=1, *, corners=None\n    ) -> None:\n        \"\"\"Draw a rounded rectangle.\"\"\"\n        if isinstance(xy[0], (list, tuple)):\n            (x0, y0), (x1, y1) = cast(Sequence[Sequence[float]], xy)\n        else:\n            x0, y0, x1, y1 = cast(Sequence[float], xy)\n        if x1 < x0:\n            msg = \"x1 must be greater than or equal to x0\"\n            raise ValueError(msg)\n        if y1 < y0:\n            msg = \"y1 must be greater than or equal to y0\"\n            raise ValueError(msg)\n        if corners is None:\n            corners = (True, True, True, True)\n\n        d = radius * 2\n\n        x0 = round(x0)\n        y0 = round(y0)\n        x1 = round(x1)\n        y1 = round(y1)\n        full_x, full_y = False, False\n        if all(corners):\n            full_x = d >= x1 - x0 - 1\n            if full_x:\n                # The two left and two right corners are joined\n                d = x1 - x0\n            full_y = d >= y1 - y0 - 1\n            if full_y:\n                # The two top and two bottom corners are joined\n                d = y1 - y0\n            if full_x and full_y:\n                # If all corners are joined, that is a circle\n                return self.ellipse(xy, fill, outline, width)\n\n        if d == 0 or not any(corners):\n            # If the corners have no curve,\n            # or there are no corners,\n            # that is a rectangle\n            return self.rectangle(xy, fill, outline, width)\n\n        r = d // 2\n        ink, fill = self._getink(outline, fill)\n\n        def draw_corners(pieslice) -> None:\n            parts: tuple[tuple[tuple[float, float, float, float], int, int], ...]\n            if full_x:\n                # Draw top and bottom halves\n                parts = (\n                    ((x0, y0, x0 + d, y0 + d), 180, 360),\n                    ((x0, y1 - d, x0 + d, y1), 0, 180),\n                )\n            elif full_y:\n                # Draw left and right halves\n                parts = (\n                    ((x0, y0, x0 + d, y0 + d), 90, 270),\n                    ((x1 - d, y0, x1, y0 + d), 270, 90),\n                )\n            else:\n                # Draw four separate corners\n                parts = tuple(\n                    part\n                    for i, part in enumerate(\n                        (\n                            ((x0, y0, x0 + d, y0 + d), 180, 270),\n                            ((x1 - d, y0, x1, y0 + d), 270, 360),\n                            ((x1 - d, y1 - d, x1, y1), 0, 90),\n                            ((x0, y1 - d, x0 + d, y1), 90, 180),\n                        )\n                    )\n                    if corners[i]\n                )\n            for part in parts:\n                if pieslice:\n                    self.draw.draw_pieslice(*(part + (fill, 1)))\n                else:\n                    self.draw.draw_arc(*(part + (ink, width)))\n\n        if fill is not None:\n            draw_corners(True)\n\n            if full_x:\n                self.draw.draw_rectangle((x0, y0 + r + 1, x1, y1 - r - 1), fill, 1)\n            else:\n                self.draw.draw_rectangle((x0 + r + 1, y0, x1 - r - 1, y1), fill, 1)\n            if not full_x and not full_y:\n                left = [x0, y0, x0 + r, y1]\n                if corners[0]:\n                    left[1] += r + 1\n                if corners[3]:\n                    left[3] -= r + 1\n                self.draw.draw_rectangle(left, fill, 1)\n\n                right = [x1 - r, y0, x1, y1]\n                if corners[1]:\n                    right[1] += r + 1\n                if corners[2]:\n                    right[3] -= r + 1\n                self.draw.draw_rectangle(right, fill, 1)\n        if ink is not None and ink != fill and width != 0:\n            draw_corners(False)\n\n            if not full_x:\n                top = [x0, y0, x1, y0 + width - 1]\n                if corners[0]:\n                    top[0] += r + 1\n                if corners[1]:\n                    top[2] -= r + 1\n                self.draw.draw_rectangle(top, ink, 1)\n\n                bottom = [x0, y1 - width + 1, x1, y1]\n                if corners[3]:\n                    bottom[0] += r + 1\n                if corners[2]:\n                    bottom[2] -= r + 1\n                self.draw.draw_rectangle(bottom, ink, 1)\n            if not full_y:\n                left = [x0, y0, x0 + width - 1, y1]\n                if corners[0]:\n                    left[1] += r + 1\n                if corners[3]:\n                    left[3] -= r + 1\n                self.draw.draw_rectangle(left, ink, 1)\n\n                right = [x1 - width + 1, y0, x1, y1]\n                if corners[1]:\n                    right[1] += r + 1\n                if corners[2]:\n                    right[3] -= r + 1\n                self.draw.draw_rectangle(right, ink, 1)\n\n    def _multiline_check(self, text) -> bool:\n        split_character = \"\\n\" if isinstance(text, str) else b\"\\n\"\n\n        return split_character in text\n\n    def _multiline_split(self, text) -> list[str | bytes]:\n        split_character = \"\\n\" if isinstance(text, str) else b\"\\n\"\n\n        return text.split(split_character)\n\n    def _multiline_spacing(self, font, spacing, stroke_width):\n        return (\n            self.textbbox((0, 0), \"A\", font, stroke_width=stroke_width)[3]\n            + stroke_width\n            + spacing\n        )\n\n    def text(\n        self,\n        xy,\n        text,\n        fill=None,\n        font=None,\n        anchor=None,\n        spacing=4,\n        align=\"left\",\n        direction=None,\n        features=None,\n        language=None,\n        stroke_width=0,\n        stroke_fill=None,\n        embedded_color=False,\n        *args,\n        **kwargs,\n    ) -> None:\n        \"\"\"Draw text.\"\"\"\n        if embedded_color and self.mode not in (\"RGB\", \"RGBA\"):\n            msg = \"Embedded color supported only in RGB and RGBA modes\"\n            raise ValueError(msg)\n\n        if font is None:\n            font = self._getfont(kwargs.get(\"font_size\"))\n\n        if self._multiline_check(text):\n            return self.multiline_text(\n                xy,\n                text,\n                fill,\n                font,\n                anchor,\n                spacing,\n                align,\n                direction,\n                features,\n                language,\n                stroke_width,\n                stroke_fill,\n                embedded_color,\n            )\n\n        def getink(fill):\n            ink, fill = self._getink(fill)\n            if ink is None:\n                return fill\n            return ink\n\n        def draw_text(ink, stroke_width=0, stroke_offset=None) -> None:\n            mode = self.fontmode\n            if stroke_width == 0 and embedded_color:\n                mode = \"RGBA\"\n            coord = []\n            start = []\n            for i in range(2):\n                coord.append(int(xy[i]))\n                start.append(math.modf(xy[i])[0])\n            try:\n                mask, offset = font.getmask2(\n                    text,\n                    mode,\n                    direction=direction,\n                    features=features,\n                    language=language,\n                    stroke_width=stroke_width,\n                    anchor=anchor,\n                    ink=ink,\n                    start=start,\n                    *args,\n                    **kwargs,\n                )\n                coord = [coord[0] + offset[0], coord[1] + offset[1]]\n            except AttributeError:\n                try:\n                    mask = font.getmask(\n                        text,\n                        mode,\n                        direction,\n                        features,\n                        language,\n                        stroke_width,\n                        anchor,\n                        ink,\n                        start=start,\n                        *args,\n                        **kwargs,\n                    )\n                except TypeError:\n                    mask = font.getmask(text)\n            if stroke_offset:\n                coord = [coord[0] + stroke_offset[0], coord[1] + stroke_offset[1]]\n            if mode == \"RGBA\":\n                # font.getmask2(mode=\"RGBA\") returns color in RGB bands and mask in A\n                # extract mask and set text alpha\n                color, mask = mask, mask.getband(3)\n                ink_alpha = struct.pack(\"i\", ink)[3]\n                color.fillband(3, ink_alpha)\n                x, y = coord\n                if self.im is not None:\n                    self.im.paste(\n                        color, (x, y, x + mask.size[0], y + mask.size[1]), mask\n                    )\n            else:\n                self.draw.draw_bitmap(coord, mask, ink)\n\n        ink = getink(fill)\n        if ink is not None:\n            stroke_ink = None\n            if stroke_width:\n                stroke_ink = getink(stroke_fill) if stroke_fill is not None else ink\n\n            if stroke_ink is not None:\n                # Draw stroked text\n                draw_text(stroke_ink, stroke_width)\n\n                # Draw normal text\n                draw_text(ink, 0)\n            else:\n                # Only draw normal text\n                draw_text(ink)\n\n    def multiline_text(\n        self,\n        xy,\n        text,\n        fill=None,\n        font=None,\n        anchor=None,\n        spacing=4,\n        align=\"left\",\n        direction=None,\n        features=None,\n        language=None,\n        stroke_width=0,\n        stroke_fill=None,\n        embedded_color=False,\n        *,\n        font_size=None,\n    ) -> None:\n        if direction == \"ttb\":\n            msg = \"ttb direction is unsupported for multiline text\"\n            raise ValueError(msg)\n\n        if anchor is None:\n            anchor = \"la\"\n        elif len(anchor) != 2:\n            msg = \"anchor must be a 2 character string\"\n            raise ValueError(msg)\n        elif anchor[1] in \"tb\":\n            msg = \"anchor not supported for multiline text\"\n            raise ValueError(msg)\n\n        if font is None:\n            font = self._getfont(font_size)\n\n        widths = []\n        max_width = 0\n        lines = self._multiline_split(text)\n        line_spacing = self._multiline_spacing(font, spacing, stroke_width)\n        for line in lines:\n            line_width = self.textlength(\n                line, font, direction=direction, features=features, language=language\n            )\n            widths.append(line_width)\n            max_width = max(max_width, line_width)\n\n        top = xy[1]\n        if anchor[1] == \"m\":\n            top -= (len(lines) - 1) * line_spacing / 2.0\n        elif anchor[1] == \"d\":\n            top -= (len(lines) - 1) * line_spacing\n\n        for idx, line in enumerate(lines):\n            left = xy[0]\n            width_difference = max_width - widths[idx]\n\n            # first align left by anchor\n            if anchor[0] == \"m\":\n                left -= width_difference / 2.0\n            elif anchor[0] == \"r\":\n                left -= width_difference\n\n            # then align by align parameter\n            if align == \"left\":\n                pass\n            elif align == \"center\":\n                left += width_difference / 2.0\n            elif align == \"right\":\n                left += width_difference\n            else:\n                msg = 'align must be \"left\", \"center\" or \"right\"'\n                raise ValueError(msg)\n\n            self.text(\n                (left, top),\n                line,\n                fill,\n                font,\n                anchor,\n                direction=direction,\n                features=features,\n                language=language,\n                stroke_width=stroke_width,\n                stroke_fill=stroke_fill,\n                embedded_color=embedded_color,\n            )\n            top += line_spacing\n\n    def textlength(\n        self,\n        text,\n        font=None,\n        direction=None,\n        features=None,\n        language=None,\n        embedded_color=False,\n        *,\n        font_size=None,\n    ):\n        \"\"\"Get the length of a given string, in pixels with 1/64 precision.\"\"\"\n        if self._multiline_check(text):\n            msg = \"can't measure length of multiline text\"\n            raise ValueError(msg)\n        if embedded_color and self.mode not in (\"RGB\", \"RGBA\"):\n            msg = \"Embedded color supported only in RGB and RGBA modes\"\n            raise ValueError(msg)\n\n        if font is None:\n            font = self._getfont(font_size)\n        mode = \"RGBA\" if embedded_color else self.fontmode\n        return font.getlength(text, mode, direction, features, language)\n\n    def textbbox(\n        self,\n        xy,\n        text,\n        font=None,\n        anchor=None,\n        spacing=4,\n        align=\"left\",\n        direction=None,\n        features=None,\n        language=None,\n        stroke_width=0,\n        embedded_color=False,\n        *,\n        font_size=None,\n    ) -> tuple[int, int, int, int]:\n        \"\"\"Get the bounding box of a given string, in pixels.\"\"\"\n        if embedded_color and self.mode not in (\"RGB\", \"RGBA\"):\n            msg = \"Embedded color supported only in RGB and RGBA modes\"\n            raise ValueError(msg)\n\n        if font is None:\n            font = self._getfont(font_size)\n\n        if self._multiline_check(text):\n            return self.multiline_textbbox(\n                xy,\n                text,\n                font,\n                anchor,\n                spacing,\n                align,\n                direction,\n                features,\n                language,\n                stroke_width,\n                embedded_color,\n            )\n\n        mode = \"RGBA\" if embedded_color else self.fontmode\n        bbox = font.getbbox(\n            text, mode, direction, features, language, stroke_width, anchor\n        )\n        return bbox[0] + xy[0], bbox[1] + xy[1], bbox[2] + xy[0], bbox[3] + xy[1]\n\n    def multiline_textbbox(\n        self,\n        xy,\n        text,\n        font=None,\n        anchor=None,\n        spacing=4,\n        align=\"left\",\n        direction=None,\n        features=None,\n        language=None,\n        stroke_width=0,\n        embedded_color=False,\n        *,\n        font_size=None,\n    ) -> tuple[int, int, int, int]:\n        if direction == \"ttb\":\n            msg = \"ttb direction is unsupported for multiline text\"\n            raise ValueError(msg)\n\n        if anchor is None:\n            anchor = \"la\"\n        elif len(anchor) != 2:\n            msg = \"anchor must be a 2 character string\"\n            raise ValueError(msg)\n        elif anchor[1] in \"tb\":\n            msg = \"anchor not supported for multiline text\"\n            raise ValueError(msg)\n\n        if font is None:\n            font = self._getfont(font_size)\n\n        widths = []\n        max_width = 0\n        lines = self._multiline_split(text)\n        line_spacing = self._multiline_spacing(font, spacing, stroke_width)\n        for line in lines:\n            line_width = self.textlength(\n                line,\n                font,\n                direction=direction,\n                features=features,\n                language=language,\n                embedded_color=embedded_color,\n            )\n            widths.append(line_width)\n            max_width = max(max_width, line_width)\n\n        top = xy[1]\n        if anchor[1] == \"m\":\n            top -= (len(lines) - 1) * line_spacing / 2.0\n        elif anchor[1] == \"d\":\n            top -= (len(lines) - 1) * line_spacing\n\n        bbox: tuple[int, int, int, int] | None = None\n\n        for idx, line in enumerate(lines):\n            left = xy[0]\n            width_difference = max_width - widths[idx]\n\n            # first align left by anchor\n            if anchor[0] == \"m\":\n                left -= width_difference / 2.0\n            elif anchor[0] == \"r\":\n                left -= width_difference\n\n            # then align by align parameter\n            if align == \"left\":\n                pass\n            elif align == \"center\":\n                left += width_difference / 2.0\n            elif align == \"right\":\n                left += width_difference\n            else:\n                msg = 'align must be \"left\", \"center\" or \"right\"'\n                raise ValueError(msg)\n\n            bbox_line = self.textbbox(\n                (left, top),\n                line,\n                font,\n                anchor,\n                direction=direction,\n                features=features,\n                language=language,\n                stroke_width=stroke_width,\n                embedded_color=embedded_color,\n            )\n            if bbox is None:\n                bbox = bbox_line\n            else:\n                bbox = (\n                    min(bbox[0], bbox_line[0]),\n                    min(bbox[1], bbox_line[1]),\n                    max(bbox[2], bbox_line[2]),\n                    max(bbox[3], bbox_line[3]),\n                )\n\n            top += line_spacing\n\n        if bbox is None:\n            return xy[0], xy[1], xy[0], xy[1]\n        return bbox\n\n\ndef Draw(im, mode: str | None = None) -> ImageDraw:\n    \"\"\"\n    A simple 2D drawing interface for PIL images.\n\n    :param im: The image to draw in.\n    :param mode: Optional mode to use for color values.  For RGB\n       images, this argument can be RGB or RGBA (to blend the\n       drawing into the image).  For all other modes, this argument\n       must be the same as the image mode.  If omitted, the mode\n       defaults to the mode of the image.\n    \"\"\"\n    try:\n        return im.getdraw(mode)\n    except AttributeError:\n        return ImageDraw(im, mode)\n\n\n# experimental access to the outline API\ntry:\n    Outline = Image.core.outline\nexcept AttributeError:\n    Outline = None\n\n\ndef getdraw(im=None, hints=None):\n    \"\"\"\n    (Experimental) A more advanced 2D drawing interface for PIL images,\n    based on the WCK interface.\n\n    :param im: The image to draw in.\n    :param hints: An optional list of hints.\n    :returns: A (drawing context, drawing resource factory) tuple.\n    \"\"\"\n    # FIXME: this needs more work!\n    # FIXME: come up with a better 'hints' scheme.\n    handler = None\n    if not hints or \"nicest\" in hints:\n        try:\n            from . import _imagingagg as handler\n        except ImportError:\n            pass\n    if handler is None:\n        from . import ImageDraw2 as handler\n    if im:\n        im = handler.Draw(im)\n    return im, handler\n\n\ndef floodfill(image: Image.Image, xy, value, border=None, thresh=0) -> None:\n    \"\"\"\n    (experimental) Fills a bounded region with a given color.\n\n    :param image: Target image.\n    :param xy: Seed position (a 2-item coordinate tuple). See\n        :ref:`coordinate-system`.\n    :param value: Fill color.\n    :param border: Optional border value.  If given, the region consists of\n        pixels with a color different from the border color.  If not given,\n        the region consists of pixels having the same color as the seed\n        pixel.\n    :param thresh: Optional threshold value which specifies a maximum\n        tolerable difference of a pixel value from the 'background' in\n        order for it to be replaced. Useful for filling regions of\n        non-homogeneous, but similar, colors.\n    \"\"\"\n    # based on an implementation by Eric S. Raymond\n    # amended by yo1995 @20180806\n    pixel = image.load()\n    x, y = xy\n    try:\n        background = pixel[x, y]\n        if _color_diff(value, background) <= thresh:\n            return  # seed point already has fill color\n        pixel[x, y] = value\n    except (ValueError, IndexError):\n        return  # seed point outside image\n    edge = {(x, y)}\n    # use a set to keep record of current and previous edge pixels\n    # to reduce memory consumption\n    full_edge = set()\n    while edge:\n        new_edge = set()\n        for x, y in edge:  # 4 adjacent method\n            for s, t in ((x + 1, y), (x - 1, y), (x, y + 1), (x, y - 1)):\n                # If already processed, or if a coordinate is negative, skip\n                if (s, t) in full_edge or s < 0 or t < 0:\n                    continue\n                try:\n                    p = pixel[s, t]\n                except (ValueError, IndexError):\n                    pass\n                else:\n                    full_edge.add((s, t))\n                    if border is None:\n                        fill = _color_diff(p, background) <= thresh\n                    else:\n                        fill = p not in (value, border)\n                    if fill:\n                        pixel[s, t] = value\n                        new_edge.add((s, t))\n        full_edge = edge  # discard pixels processed\n        edge = new_edge\n\n\ndef _compute_regular_polygon_vertices(\n    bounding_circle, n_sides, rotation\n) -> list[tuple[float, float]]:\n    \"\"\"\n    Generate a list of vertices for a 2D regular polygon.\n\n    :param bounding_circle: The bounding circle is a tuple defined\n        by a point and radius. The polygon is inscribed in this circle.\n        (e.g. ``bounding_circle=(x, y, r)`` or ``((x, y), r)``)\n    :param n_sides: Number of sides\n        (e.g. ``n_sides=3`` for a triangle, ``6`` for a hexagon)\n    :param rotation: Apply an arbitrary rotation to the polygon\n        (e.g. ``rotation=90``, applies a 90 degree rotation)\n    :return: List of regular polygon vertices\n        (e.g. ``[(25, 50), (50, 50), (50, 25), (25, 25)]``)\n\n    How are the vertices computed?\n    1. Compute the following variables\n        - theta: Angle between the apothem & the nearest polygon vertex\n        - side_length: Length of each polygon edge\n        - centroid: Center of bounding circle (1st, 2nd elements of bounding_circle)\n        - polygon_radius: Polygon radius (last element of bounding_circle)\n        - angles: Location of each polygon vertex in polar grid\n            (e.g. A square with 0 degree rotation => [225.0, 315.0, 45.0, 135.0])\n\n    2. For each angle in angles, get the polygon vertex at that angle\n        The vertex is computed using the equation below.\n            X= xcos(φ) + ysin(φ)\n            Y= −xsin(φ) + ycos(φ)\n\n        Note:\n            φ = angle in degrees\n            x = 0\n            y = polygon_radius\n\n        The formula above assumes rotation around the origin.\n        In our case, we are rotating around the centroid.\n        To account for this, we use the formula below\n            X = xcos(φ) + ysin(φ) + centroid_x\n            Y = −xsin(φ) + ycos(φ) + centroid_y\n    \"\"\"\n    # 1. Error Handling\n    # 1.1 Check `n_sides` has an appropriate value\n    if not isinstance(n_sides, int):\n        msg = \"n_sides should be an int\"\n        raise TypeError(msg)\n    if n_sides < 3:\n        msg = \"n_sides should be an int > 2\"\n        raise ValueError(msg)\n\n    # 1.2 Check `bounding_circle` has an appropriate value\n    if not isinstance(bounding_circle, (list, tuple)):\n        msg = \"bounding_circle should be a sequence\"\n        raise TypeError(msg)\n\n    if len(bounding_circle) == 3:\n        *centroid, polygon_radius = bounding_circle\n    elif len(bounding_circle) == 2:\n        centroid, polygon_radius = bounding_circle\n    else:\n        msg = (\n            \"bounding_circle should contain 2D coordinates \"\n            \"and a radius (e.g. (x, y, r) or ((x, y), r) )\"\n        )\n        raise ValueError(msg)\n\n    if not all(isinstance(i, (int, float)) for i in (*centroid, polygon_radius)):\n        msg = \"bounding_circle should only contain numeric data\"\n        raise ValueError(msg)\n\n    if not len(centroid) == 2:\n        msg = \"bounding_circle centre should contain 2D coordinates (e.g. (x, y))\"\n        raise ValueError(msg)\n\n    if polygon_radius <= 0:\n        msg = \"bounding_circle radius should be > 0\"\n        raise ValueError(msg)\n\n    # 1.3 Check `rotation` has an appropriate value\n    if not isinstance(rotation, (int, float)):\n        msg = \"rotation should be an int or float\"\n        raise ValueError(msg)\n\n    # 2. Define Helper Functions\n    def _apply_rotation(point: list[float], degrees: float) -> tuple[int, int]:\n        return (\n            round(\n                point[0] * math.cos(math.radians(360 - degrees))\n                - point[1] * math.sin(math.radians(360 - degrees))\n                + centroid[0],\n                2,\n            ),\n            round(\n                point[1] * math.cos(math.radians(360 - degrees))\n                + point[0] * math.sin(math.radians(360 - degrees))\n                + centroid[1],\n                2,\n            ),\n        )\n\n    def _compute_polygon_vertex(angle: float) -> tuple[int, int]:\n        start_point = [polygon_radius, 0]\n        return _apply_rotation(start_point, angle)\n\n    def _get_angles(n_sides: int, rotation: float) -> list[float]:\n        angles = []\n        degrees = 360 / n_sides\n        # Start with the bottom left polygon vertex\n        current_angle = (270 - 0.5 * degrees) + rotation\n        for _ in range(0, n_sides):\n            angles.append(current_angle)\n            current_angle += degrees\n            if current_angle > 360:\n                current_angle -= 360\n        return angles\n\n    # 3. Variable Declarations\n    angles = _get_angles(n_sides, rotation)\n\n    # 4. Compute Vertices\n    return [_compute_polygon_vertex(angle) for angle in angles]\n\n\ndef _color_diff(color1, color2: float | tuple[int, ...]) -> float:\n    \"\"\"\n    Uses 1-norm distance to calculate difference between two values.\n    \"\"\"\n    if isinstance(color2, tuple):\n        return sum(abs(color1[i] - color2[i]) for i in range(0, len(color2)))\n    else:\n        return abs(color1 - color2)\n",
            "new_file_content": "#\n# The Python Imaging Library\n# $Id$\n#\n# drawing interface operations\n#\n# History:\n# 1996-04-13 fl   Created (experimental)\n# 1996-08-07 fl   Filled polygons, ellipses.\n# 1996-08-13 fl   Added text support\n# 1998-06-28 fl   Handle I and F images\n# 1998-12-29 fl   Added arc; use arc primitive to draw ellipses\n# 1999-01-10 fl   Added shape stuff (experimental)\n# 1999-02-06 fl   Added bitmap support\n# 1999-02-11 fl   Changed all primitives to take options\n# 1999-02-20 fl   Fixed backwards compatibility\n# 2000-10-12 fl   Copy on write, when necessary\n# 2001-02-18 fl   Use default ink for bitmap/text also in fill mode\n# 2002-10-24 fl   Added support for CSS-style color strings\n# 2002-12-10 fl   Added experimental support for RGBA-on-RGB drawing\n# 2002-12-11 fl   Refactored low-level drawing API (work in progress)\n# 2004-08-26 fl   Made Draw() a factory function, added getdraw() support\n# 2004-09-04 fl   Added width support to line primitive\n# 2004-09-10 fl   Added font mode handling\n# 2006-06-19 fl   Added font bearing support (getmask2)\n#\n# Copyright (c) 1997-2006 by Secret Labs AB\n# Copyright (c) 1996-2006 by Fredrik Lundh\n#\n# See the README file for information on usage and redistribution.\n#\nfrom __future__ import annotations\n\nimport math\nimport numbers\nimport struct\nfrom typing import TYPE_CHECKING, Sequence, cast\n\nfrom . import Image, ImageColor\nfrom ._typing import Coords\n\n\"\"\"\nA simple 2D drawing interface for PIL images.\n<p>\nApplication code should use the <b>Draw</b> factory, instead of\ndirectly.\n\"\"\"\n\n\nclass ImageDraw:\n    font = None\n\n    def __init__(self, im: Image.Image, mode: str | None = None) -> None:\n        \"\"\"\n        Create a drawing instance.\n\n        :param im: The image to draw in.\n        :param mode: Optional mode to use for color values.  For RGB\n           images, this argument can be RGB or RGBA (to blend the\n           drawing into the image).  For all other modes, this argument\n           must be the same as the image mode.  If omitted, the mode\n           defaults to the mode of the image.\n        \"\"\"\n        im.load()\n        if im.readonly:\n            im._copy()  # make it writeable\n        blend = 0\n        if mode is None:\n            mode = im.mode\n        if mode != im.mode:\n            if mode == \"RGBA\" and im.mode == \"RGB\":\n                blend = 1\n            else:\n                msg = \"mode mismatch\"\n                raise ValueError(msg)\n        if mode == \"P\":\n            self.palette = im.palette\n        else:\n            self.palette = None\n        self._image = im\n        self.im = im.im\n        self.draw = Image.core.draw(self.im, blend)\n        self.mode = mode\n        if mode in (\"I\", \"F\"):\n            self.ink = self.draw.draw_ink(1)\n        else:\n            self.ink = self.draw.draw_ink(-1)\n        if mode in (\"1\", \"P\", \"I\", \"F\"):\n            # FIXME: fix Fill2 to properly support matte for I+F images\n            self.fontmode = \"1\"\n        else:\n            self.fontmode = \"L\"  # aliasing is okay for other modes\n        self.fill = False\n\n    if TYPE_CHECKING:\n        from . import ImageFont\n\n    def getfont(self) -> ImageFont.FreeTypeFont | ImageFont.ImageFont:\n        \"\"\"\n        Get the current default font.\n\n        To set the default font for this ImageDraw instance::\n\n            from PIL import ImageDraw, ImageFont\n            draw.font = ImageFont.truetype(\"Tests/fonts/FreeMono.ttf\")\n\n        To set the default font for all future ImageDraw instances::\n\n            from PIL import ImageDraw, ImageFont\n            ImageDraw.ImageDraw.font = ImageFont.truetype(\"Tests/fonts/FreeMono.ttf\")\n\n        If the current default font is ``None``,\n        it is initialized with ``ImageFont.load_default()``.\n\n        :returns: An image font.\"\"\"\n        if not self.font:\n            # FIXME: should add a font repository\n            from . import ImageFont\n\n            self.font = ImageFont.load_default()\n        return self.font\n\n    def _getfont(self, font_size: float | None):\n        if font_size is not None:\n            from . import ImageFont\n\n            font = ImageFont.load_default(font_size)\n        else:\n            font = self.getfont()\n        return font\n\n    def _getink(self, ink, fill=None) -> tuple[int | None, int | None]:\n        if ink is None and fill is None:\n            if self.fill:\n                fill = self.ink\n            else:\n                ink = self.ink\n        else:\n            if ink is not None:\n                if isinstance(ink, str):\n                    ink = ImageColor.getcolor(ink, self.mode)\n                if self.palette and not isinstance(ink, numbers.Number):\n                    ink = self.palette.getcolor(ink, self._image)\n                ink = self.draw.draw_ink(ink)\n            if fill is not None:\n                if isinstance(fill, str):\n                    fill = ImageColor.getcolor(fill, self.mode)\n                if self.palette and not isinstance(fill, numbers.Number):\n                    fill = self.palette.getcolor(fill, self._image)\n                fill = self.draw.draw_ink(fill)\n        return ink, fill\n\n    def arc(self, xy: Coords, start, end, fill=None, width=1) -> None:\n        \"\"\"Draw an arc.\"\"\"\n        ink, fill = self._getink(fill)\n        if ink is not None:\n            self.draw.draw_arc(xy, start, end, ink, width)\n\n    def bitmap(self, xy: Sequence[int], bitmap, fill=None) -> None:\n        \"\"\"Draw a bitmap.\"\"\"\n        bitmap.load()\n        ink, fill = self._getink(fill)\n        if ink is None:\n            ink = fill\n        if ink is not None:\n            self.draw.draw_bitmap(xy, bitmap.im, ink)\n\n    def chord(self, xy: Coords, start, end, fill=None, outline=None, width=1) -> None:\n        \"\"\"Draw a chord.\"\"\"\n        ink, fill = self._getink(outline, fill)\n        if fill is not None:\n            self.draw.draw_chord(xy, start, end, fill, 1)\n        if ink is not None and ink != fill and width != 0:\n            self.draw.draw_chord(xy, start, end, ink, 0, width)\n\n    def ellipse(self, xy: Coords, fill=None, outline=None, width=1) -> None:\n        \"\"\"Draw an ellipse.\"\"\"\n        ink, fill = self._getink(outline, fill)\n        if fill is not None:\n            self.draw.draw_ellipse(xy, fill, 1)\n        if ink is not None and ink != fill and width != 0:\n            self.draw.draw_ellipse(xy, ink, 0, width)\n\n    def circle(\n        self, xy: Sequence[float], radius: float, fill=None, outline=None, width=1\n    ) -> None:\n        \"\"\"Draw a circle given center coordinates and a radius.\"\"\"\n        ellipse_xy = (xy[0] - radius, xy[1] - radius, xy[0] + radius, xy[1] + radius)\n        self.ellipse(ellipse_xy, fill, outline, width)\n\n    def line(self, xy: Coords, fill=None, width=0, joint=None) -> None:\n        \"\"\"Draw a line, or a connected sequence of line segments.\"\"\"\n        ink = self._getink(fill)[0]\n        if ink is not None:\n            self.draw.draw_lines(xy, ink, width)\n            if joint == \"curve\" and width > 4:\n                points: Sequence[Sequence[float]]\n                if isinstance(xy[0], (list, tuple)):\n                    points = cast(Sequence[Sequence[float]], xy)\n                else:\n                    points = [\n                        cast(Sequence[float], tuple(xy[i : i + 2]))\n                        for i in range(0, len(xy), 2)\n                    ]\n                for i in range(1, len(points) - 1):\n                    point = points[i]\n                    angles = [\n                        math.degrees(math.atan2(end[0] - start[0], start[1] - end[1]))\n                        % 360\n                        for start, end in (\n                            (points[i - 1], point),\n                            (point, points[i + 1]),\n                        )\n                    ]\n                    if angles[0] == angles[1]:\n                        # This is a straight line, so no joint is required\n                        continue\n\n                    def coord_at_angle(coord, angle):\n                        x, y = coord\n                        angle -= 90\n                        distance = width / 2 - 1\n                        return tuple(\n                            p + (math.floor(p_d) if p_d > 0 else math.ceil(p_d))\n                            for p, p_d in (\n                                (x, distance * math.cos(math.radians(angle))),\n                                (y, distance * math.sin(math.radians(angle))),\n                            )\n                        )\n\n                    flipped = (\n                        angles[1] > angles[0] and angles[1] - 180 > angles[0]\n                    ) or (angles[1] < angles[0] and angles[1] + 180 > angles[0])\n                    coords = [\n                        (point[0] - width / 2 + 1, point[1] - width / 2 + 1),\n                        (point[0] + width / 2 - 1, point[1] + width / 2 - 1),\n                    ]\n                    if flipped:\n                        start, end = (angles[1] + 90, angles[0] + 90)\n                    else:\n                        start, end = (angles[0] - 90, angles[1] - 90)\n                    self.pieslice(coords, start - 90, end - 90, fill)\n\n                    if width > 8:\n                        # Cover potential gaps between the line and the joint\n                        if flipped:\n                            gap_coords = [\n                                coord_at_angle(point, angles[0] + 90),\n                                point,\n                                coord_at_angle(point, angles[1] + 90),\n                            ]\n                        else:\n                            gap_coords = [\n                                coord_at_angle(point, angles[0] - 90),\n                                point,\n                                coord_at_angle(point, angles[1] - 90),\n                            ]\n                        self.line(gap_coords, fill, width=3)\n\n    def shape(self, shape, fill=None, outline=None) -> None:\n        \"\"\"(Experimental) Draw a shape.\"\"\"\n        shape.close()\n        ink, fill = self._getink(outline, fill)\n        if fill is not None:\n            self.draw.draw_outline(shape, fill, 1)\n        if ink is not None and ink != fill:\n            self.draw.draw_outline(shape, ink, 0)\n\n    def pieslice(\n        self, xy: Coords, start, end, fill=None, outline=None, width=1\n    ) -> None:\n        \"\"\"Draw a pieslice.\"\"\"\n        ink, fill = self._getink(outline, fill)\n        if fill is not None:\n            self.draw.draw_pieslice(xy, start, end, fill, 1)\n        if ink is not None and ink != fill and width != 0:\n            self.draw.draw_pieslice(xy, start, end, ink, 0, width)\n\n    def point(self, xy: Coords, fill=None) -> None:\n        \"\"\"Draw one or more individual pixels.\"\"\"\n        ink, fill = self._getink(fill)\n        if ink is not None:\n            self.draw.draw_points(xy, ink)\n\n    def polygon(self, xy: Coords, fill=None, outline=None, width=1) -> None:\n        \"\"\"Draw a polygon.\"\"\"\n        ink, fill = self._getink(outline, fill)\n        if fill is not None:\n            self.draw.draw_polygon(xy, fill, 1)\n        if ink is not None and ink != fill and width != 0:\n            if width == 1:\n                self.draw.draw_polygon(xy, ink, 0, width)\n            elif self.im is not None:\n                # To avoid expanding the polygon outwards,\n                # use the fill as a mask\n                mask = Image.new(\"1\", self.im.size)\n                mask_ink = self._getink(1)[0]\n\n                fill_im = mask.copy()\n                draw = Draw(fill_im)\n                draw.draw.draw_polygon(xy, mask_ink, 1)\n\n                ink_im = mask.copy()\n                draw = Draw(ink_im)\n                width = width * 2 - 1\n                draw.draw.draw_polygon(xy, mask_ink, 0, width)\n\n                mask.paste(ink_im, mask=fill_im)\n\n                im = Image.new(self.mode, self.im.size)\n                draw = Draw(im)\n                draw.draw.draw_polygon(xy, ink, 0, width)\n                self.im.paste(im.im, (0, 0) + im.size, mask.im)\n\n    def regular_polygon(\n        self, bounding_circle, n_sides, rotation=0, fill=None, outline=None, width=1\n    ) -> None:\n        \"\"\"Draw a regular polygon.\"\"\"\n        xy = _compute_regular_polygon_vertices(bounding_circle, n_sides, rotation)\n        self.polygon(xy, fill, outline, width)\n\n    def rectangle(self, xy: Coords, fill=None, outline=None, width=1) -> None:\n        \"\"\"Draw a rectangle.\"\"\"\n        ink, fill = self._getink(outline, fill)\n        if fill is not None:\n            self.draw.draw_rectangle(xy, fill, 1)\n        if ink is not None and ink != fill and width != 0:\n            self.draw.draw_rectangle(xy, ink, 0, width)\n\n    def rounded_rectangle(\n        self, xy: Coords, radius=0, fill=None, outline=None, width=1, *, corners=None\n    ) -> None:\n        \"\"\"Draw a rounded rectangle.\"\"\"\n        if isinstance(xy[0], (list, tuple)):\n            (x0, y0), (x1, y1) = cast(Sequence[Sequence[float]], xy)\n        else:\n            x0, y0, x1, y1 = cast(Sequence[float], xy)\n        if x1 < x0:\n            msg = \"x1 must be greater than or equal to x0\"\n            raise ValueError(msg)\n        if y1 < y0:\n            msg = \"y1 must be greater than or equal to y0\"\n            raise ValueError(msg)\n        if corners is None:\n            corners = (True, True, True, True)\n\n        d = radius * 2\n\n        x0 = round(x0)\n        y0 = round(y0)\n        x1 = round(x1)\n        y1 = round(y1)\n        full_x, full_y = False, False\n        if all(corners):\n            full_x = d >= x1 - x0 - 1\n            if full_x:\n                # The two left and two right corners are joined\n                d = x1 - x0\n            full_y = d >= y1 - y0 - 1\n            if full_y:\n                # The two top and two bottom corners are joined\n                d = y1 - y0\n            if full_x and full_y:\n                # If all corners are joined, that is a circle\n                return self.ellipse(xy, fill, outline, width)\n\n        if d == 0 or not any(corners):\n            # If the corners have no curve,\n            # or there are no corners,\n            # that is a rectangle\n            return self.rectangle(xy, fill, outline, width)\n\n        r = d // 2\n        ink, fill = self._getink(outline, fill)\n\n        def draw_corners(pieslice) -> None:\n            parts: tuple[tuple[tuple[float, float, float, float], int, int], ...]\n            if full_x:\n                # Draw top and bottom halves\n                parts = (\n                    ((x0, y0, x0 + d, y0 + d), 180, 360),\n                    ((x0, y1 - d, x0 + d, y1), 0, 180),\n                )\n            elif full_y:\n                # Draw left and right halves\n                parts = (\n                    ((x0, y0, x0 + d, y0 + d), 90, 270),\n                    ((x1 - d, y0, x1, y0 + d), 270, 90),\n                )\n            else:\n                # Draw four separate corners\n                parts = tuple(\n                    part\n                    for i, part in enumerate(\n                        (\n                            ((x0, y0, x0 + d, y0 + d), 180, 270),\n                            ((x1 - d, y0, x1, y0 + d), 270, 360),\n                            ((x1 - d, y1 - d, x1, y1), 0, 90),\n                            ((x0, y1 - d, x0 + d, y1), 90, 180),\n                        )\n                    )\n                    if corners[i]\n                )\n            for part in parts:\n                if pieslice:\n                    self.draw.draw_pieslice(*(part + (fill, 1)))\n                else:\n                    self.draw.draw_arc(*(part + (ink, width)))\n\n        if fill is not None:\n            draw_corners(True)\n\n            if full_x:\n                self.draw.draw_rectangle((x0, y0 + r + 1, x1, y1 - r - 1), fill, 1)\n            else:\n                self.draw.draw_rectangle((x0 + r + 1, y0, x1 - r - 1, y1), fill, 1)\n            if not full_x and not full_y:\n                left = [x0, y0, x0 + r, y1]\n                if corners[0]:\n                    left[1] += r + 1\n                if corners[3]:\n                    left[3] -= r + 1\n                self.draw.draw_rectangle(left, fill, 1)\n\n                right = [x1 - r, y0, x1, y1]\n                if corners[1]:\n                    right[1] += r + 1\n                if corners[2]:\n                    right[3] -= r + 1\n                self.draw.draw_rectangle(right, fill, 1)\n        if ink is not None and ink != fill and width != 0:\n            draw_corners(False)\n\n            if not full_x:\n                top = [x0, y0, x1, y0 + width - 1]\n                if corners[0]:\n                    top[0] += r + 1\n                if corners[1]:\n                    top[2] -= r + 1\n                self.draw.draw_rectangle(top, ink, 1)\n\n                bottom = [x0, y1 - width + 1, x1, y1]\n                if corners[3]:\n                    bottom[0] += r + 1\n                if corners[2]:\n                    bottom[2] -= r + 1\n                self.draw.draw_rectangle(bottom, ink, 1)\n            if not full_y:\n                left = [x0, y0, x0 + width - 1, y1]\n                if corners[0]:\n                    left[1] += r + 1\n                if corners[3]:\n                    left[3] -= r + 1\n                self.draw.draw_rectangle(left, ink, 1)\n\n                right = [x1 - width + 1, y0, x1, y1]\n                if corners[1]:\n                    right[1] += r + 1\n                if corners[2]:\n                    right[3] -= r + 1\n                self.draw.draw_rectangle(right, ink, 1)\n\n    def _multiline_check(self, text) -> bool:\n        split_character = \"\\n\" if isinstance(text, str) else b\"\\n\"\n\n        return split_character in text\n\n    def _multiline_split(self, text) -> list[str | bytes]:\n        split_character = \"\\n\" if isinstance(text, str) else b\"\\n\"\n\n        return text.split(split_character)\n\n    def _multiline_spacing(self, font, spacing, stroke_width):\n        return (\n            self.textbbox((0, 0), \"A\", font, stroke_width=stroke_width)[3]\n            + stroke_width\n            + spacing\n        )\n\n    def text(\n        self,\n        xy,\n        text,\n        fill=None,\n        font=None,\n        anchor=None,\n        spacing=4,\n        align=\"left\",\n        direction=None,\n        features=None,\n        language=None,\n        stroke_width=0,\n        stroke_fill=None,\n        embedded_color=False,\n        *args,\n        **kwargs,\n    ) -> None:\n        \"\"\"Draw text.\"\"\"\n        if embedded_color and self.mode not in (\"RGB\", \"RGBA\"):\n            msg = \"Embedded color supported only in RGB and RGBA modes\"\n            raise ValueError(msg)\n\n        if font is None:\n            font = self._getfont(kwargs.get(\"font_size\"))\n\n        if self._multiline_check(text):\n            return self.multiline_text(\n                xy,\n                text,\n                fill,\n                font,\n                anchor,\n                spacing,\n                align,\n                direction,\n                features,\n                language,\n                stroke_width,\n                stroke_fill,\n                embedded_color,\n            )\n\n        def getink(fill):\n            ink, fill = self._getink(fill)\n            if ink is None:\n                return fill\n            return ink\n\n        def draw_text(ink, stroke_width=0, stroke_offset=None) -> None:\n            mode = self.fontmode\n            if stroke_width == 0 and embedded_color:\n                mode = \"RGBA\"\n            coord = []\n            start = []\n            for i in range(2):\n                coord.append(int(xy[i]))\n                start.append(math.modf(xy[i])[0])\n            try:\n                mask, offset = font.getmask2(\n                    text,\n                    mode,\n                    direction=direction,\n                    features=features,\n                    language=language,\n                    stroke_width=stroke_width,\n                    anchor=anchor,\n                    ink=ink,\n                    start=start,\n                    *args,\n                    **kwargs,\n                )\n                coord = [coord[0] + offset[0], coord[1] + offset[1]]\n            except AttributeError:\n                try:\n                    mask = font.getmask(\n                        text,\n                        mode,\n                        direction,\n                        features,\n                        language,\n                        stroke_width,\n                        anchor,\n                        ink,\n                        start=start,\n                        *args,\n                        **kwargs,\n                    )\n                except TypeError:\n                    mask = font.getmask(text)\n            if stroke_offset:\n                coord = [coord[0] + stroke_offset[0], coord[1] + stroke_offset[1]]\n            if mode == \"RGBA\":\n                # font.getmask2(mode=\"RGBA\") returns color in RGB bands and mask in A\n                # extract mask and set text alpha\n                color, mask = mask, mask.getband(3)\n                ink_alpha = struct.pack(\"i\", ink)[3]\n                color.fillband(3, ink_alpha)\n                x, y = coord\n                if self.im is not None:\n                    self.im.paste(\n                        color, (x, y, x + mask.size[0], y + mask.size[1]), mask\n                    )\n            else:\n                self.draw.draw_bitmap(coord, mask, ink)\n\n        ink = getink(fill)\n        if ink is not None:\n            stroke_ink = None\n            if stroke_width:\n                stroke_ink = getink(stroke_fill) if stroke_fill is not None else ink\n\n            if stroke_ink is not None:\n                # Draw stroked text\n                draw_text(stroke_ink, stroke_width)\n\n                # Draw normal text\n                draw_text(ink, 0)\n            else:\n                # Only draw normal text\n                draw_text(ink)\n\n    def multiline_text(\n        self,\n        xy,\n        text,\n        fill=None,\n        font=None,\n        anchor=None,\n        spacing=4,\n        align=\"left\",\n        direction=None,\n        features=None,\n        language=None,\n        stroke_width=0,\n        stroke_fill=None,\n        embedded_color=False,\n        *,\n        font_size=None,\n    ) -> None:\n        if direction == \"ttb\":\n            msg = \"ttb direction is unsupported for multiline text\"\n            raise ValueError(msg)\n\n        if anchor is None:\n            anchor = \"la\"\n        elif len(anchor) != 2:\n            msg = \"anchor must be a 2 character string\"\n            raise ValueError(msg)\n        elif anchor[1] in \"tb\":\n            msg = \"anchor not supported for multiline text\"\n            raise ValueError(msg)\n\n        if font is None:\n            font = self._getfont(font_size)\n\n        widths = []\n        max_width = 0\n        lines = self._multiline_split(text)\n        line_spacing = self._multiline_spacing(font, spacing, stroke_width)\n        for line in lines:\n            line_width = self.textlength(\n                line, font, direction=direction, features=features, language=language\n            )\n            widths.append(line_width)\n            max_width = max(max_width, line_width)\n\n        top = xy[1]\n        if anchor[1] == \"m\":\n            top -= (len(lines) - 1) * line_spacing / 2.0\n        elif anchor[1] == \"d\":\n            top -= (len(lines) - 1) * line_spacing\n\n        for idx, line in enumerate(lines):\n            left = xy[0]\n            width_difference = max_width - widths[idx]\n\n            # first align left by anchor\n            if anchor[0] == \"m\":\n                left -= width_difference / 2.0\n            elif anchor[0] == \"r\":\n                left -= width_difference\n\n            # then align by align parameter\n            if align == \"left\":\n                pass\n            elif align == \"center\":\n                left += width_difference / 2.0\n            elif align == \"right\":\n                left += width_difference\n            else:\n                msg = 'align must be \"left\", \"center\" or \"right\"'\n                raise ValueError(msg)\n\n            self.text(\n                (left, top),\n                line,\n                fill,\n                font,\n                anchor,\n                direction=direction,\n                features=features,\n                language=language,\n                stroke_width=stroke_width,\n                stroke_fill=stroke_fill,\n                embedded_color=embedded_color,\n            )\n            top += line_spacing\n\n    def textlength(\n        self,\n        text,\n        font=None,\n        direction=None,\n        features=None,\n        language=None,\n        embedded_color=False,\n        *,\n        font_size=None,\n    ):\n        \"\"\"Get the length of a given string, in pixels with 1/64 precision.\"\"\"\n        if self._multiline_check(text):\n            msg = \"can't measure length of multiline text\"\n            raise ValueError(msg)\n        if embedded_color and self.mode not in (\"RGB\", \"RGBA\"):\n            msg = \"Embedded color supported only in RGB and RGBA modes\"\n            raise ValueError(msg)\n\n        if font is None:\n            font = self._getfont(font_size)\n        mode = \"RGBA\" if embedded_color else self.fontmode\n        return font.getlength(text, mode, direction, features, language)\n\n    def textbbox(\n        self,\n        xy,\n        text,\n        font=None,\n        anchor=None,\n        spacing=4,\n        align=\"left\",\n        direction=None,\n        features=None,\n        language=None,\n        stroke_width=0,\n        embedded_color=False,\n        *,\n        font_size=None,\n    ) -> tuple[int, int, int, int]:\n        \"\"\"Get the bounding box of a given string, in pixels.\"\"\"\n        if embedded_color and self.mode not in (\"RGB\", \"RGBA\"):\n            msg = \"Embedded color supported only in RGB and RGBA modes\"\n            raise ValueError(msg)\n\n        if font is None:\n            font = self._getfont(font_size)\n\n        if self._multiline_check(text):\n            return self.multiline_textbbox(\n                xy,\n                text,\n                font,\n                anchor,\n                spacing,\n                align,\n                direction,\n                features,\n                language,\n                stroke_width,\n                embedded_color,\n            )\n\n        mode = \"RGBA\" if embedded_color else self.fontmode\n        bbox = font.getbbox(\n            text, mode, direction, features, language, stroke_width, anchor\n        )\n        return bbox[0] + xy[0], bbox[1] + xy[1], bbox[2] + xy[0], bbox[3] + xy[1]\n\n    def multiline_textbbox(\n        self,\n        xy,\n        text,\n        font=None,\n        anchor=None,\n        spacing=4,\n        align=\"left\",\n        direction=None,\n        features=None,\n        language=None,\n        stroke_width=0,\n        embedded_color=False,\n        *,\n        font_size=None,\n    ) -> tuple[int, int, int, int]:\n        if direction == \"ttb\":\n            msg = \"ttb direction is unsupported for multiline text\"\n            raise ValueError(msg)\n\n        if anchor is None:\n            anchor = \"la\"\n        elif len(anchor) != 2:\n            msg = \"anchor must be a 2 character string\"\n            raise ValueError(msg)\n        elif anchor[1] in \"tb\":\n            msg = \"anchor not supported for multiline text\"\n            raise ValueError(msg)\n\n        if font is None:\n            font = self._getfont(font_size)\n\n        widths = []\n        max_width = 0\n        lines = self._multiline_split(text)\n        line_spacing = self._multiline_spacing(font, spacing, stroke_width)\n        for line in lines:\n            line_width = self.textlength(\n                line,\n                font,\n                direction=direction,\n                features=features,\n                language=language,\n                embedded_color=embedded_color,\n            )\n            widths.append(line_width)\n            max_width = max(max_width, line_width)\n\n        top = xy[1]\n        if anchor[1] == \"m\":\n            top -= (len(lines) - 1) * line_spacing / 2.0\n        elif anchor[1] == \"d\":\n            top -= (len(lines) - 1) * line_spacing\n\n        bbox: tuple[int, int, int, int] | None = None\n\n        for idx, line in enumerate(lines):\n            left = xy[0]\n            width_difference = max_width - widths[idx]\n\n            # first align left by anchor\n            if anchor[0] == \"m\":\n                left -= width_difference / 2.0\n            elif anchor[0] == \"r\":\n                left -= width_difference\n\n            # then align by align parameter\n            if align == \"left\":\n                pass\n            elif align == \"center\":\n                left += width_difference / 2.0\n            elif align == \"right\":\n                left += width_difference\n            else:\n                msg = 'align must be \"left\", \"center\" or \"right\"'\n                raise ValueError(msg)\n\n            bbox_line = self.textbbox(\n                (left, top),\n                line,\n                font,\n                anchor,\n                direction=direction,\n                features=features,\n                language=language,\n                stroke_width=stroke_width,\n                embedded_color=embedded_color,\n            )\n            if bbox is None:\n                bbox = bbox_line\n            else:\n                bbox = (\n                    min(bbox[0], bbox_line[0]),\n                    min(bbox[1], bbox_line[1]),\n                    max(bbox[2], bbox_line[2]),\n                    max(bbox[3], bbox_line[3]),\n                )\n\n            top += line_spacing\n\n        if bbox is None:\n            return xy[0], xy[1], xy[0], xy[1]\n        return bbox\n\n\ndef Draw(im, mode: str | None = None) -> ImageDraw:\n    \"\"\"\n    A simple 2D drawing interface for PIL images.\n\n    :param im: The image to draw in.\n    :param mode: Optional mode to use for color values.  For RGB\n       images, this argument can be RGB or RGBA (to blend the\n       drawing into the image).  For all other modes, this argument\n       must be the same as the image mode.  If omitted, the mode\n       defaults to the mode of the image.\n    \"\"\"\n    try:\n        return im.getdraw(mode)\n    except AttributeError:\n        return ImageDraw(im, mode)\n\n\n# experimental access to the outline API\ntry:\n    Outline = Image.core.outline\nexcept AttributeError:\n    Outline = None\n\n\ndef getdraw(im=None, hints=None):\n    \"\"\"\n    (Experimental) A more advanced 2D drawing interface for PIL images,\n    based on the WCK interface.\n\n    :param im: The image to draw in.\n    :param hints: An optional list of hints.\n    :returns: A (drawing context, drawing resource factory) tuple.\n    \"\"\"\n    # FIXME: this needs more work!\n    # FIXME: come up with a better 'hints' scheme.\n    handler = None\n    if not hints or \"nicest\" in hints:\n        try:\n            from . import _imagingagg as handler\n        except ImportError:\n            pass\n    if handler is None:\n        from . import ImageDraw2 as handler\n    if im:\n        im = handler.Draw(im)\n    return im, handler\n\n\ndef floodfill(image: Image.Image, xy, value, border=None, thresh=0) -> None:\n    \"\"\"\n    (experimental) Fills a bounded region with a given color.\n\n    :param image: Target image.\n    :param xy: Seed position (a 2-item coordinate tuple). See\n        :ref:`coordinate-system`.\n    :param value: Fill color.\n    :param border: Optional border value.  If given, the region consists of\n        pixels with a color different from the border color.  If not given,\n        the region consists of pixels having the same color as the seed\n        pixel.\n    :param thresh: Optional threshold value which specifies a maximum\n        tolerable difference of a pixel value from the 'background' in\n        order for it to be replaced. Useful for filling regions of\n        non-homogeneous, but similar, colors.\n    \"\"\"\n    # based on an implementation by Eric S. Raymond\n    # amended by yo1995 @20180806\n    pixel = image.load()\n    x, y = xy\n    try:\n        background = pixel[x, y]\n        if _color_diff(value, background) <= thresh:\n            return  # seed point already has fill color\n        pixel[x, y] = value\n    except (ValueError, IndexError):\n        return  # seed point outside image\n    edge = {(x, y)}\n    # use a set to keep record of current and previous edge pixels\n    # to reduce memory consumption\n    full_edge = set()\n    while edge:\n        new_edge = set()\n        for x, y in edge:  # 4 adjacent method\n            for s, t in ((x + 1, y), (x - 1, y), (x, y + 1), (x, y - 1)):\n                # If already processed, or if a coordinate is negative, skip\n                if (s, t) in full_edge or s < 0 or t < 0:\n                    continue\n                try:\n                    p = pixel[s, t]\n                except (ValueError, IndexError):\n                    pass\n                else:\n                    full_edge.add((s, t))\n                    if border is None:\n                        fill = _color_diff(p, background) <= thresh\n                    else:\n                        fill = p not in (value, border)\n                    if fill:\n                        pixel[s, t] = value\n                        new_edge.add((s, t))\n        full_edge = edge  # discard pixels processed\n        edge = new_edge\n\n\ndef _compute_regular_polygon_vertices(\n    bounding_circle, n_sides, rotation\n) -> list[tuple[float, float]]:\n    \"\"\"\n    Generate a list of vertices for a 2D regular polygon.\n\n    :param bounding_circle: The bounding circle is a tuple defined\n        by a point and radius. The polygon is inscribed in this circle.\n        (e.g. ``bounding_circle=(x, y, r)`` or ``((x, y), r)``)\n    :param n_sides: Number of sides\n        (e.g. ``n_sides=3`` for a triangle, ``6`` for a hexagon)\n    :param rotation: Apply an arbitrary rotation to the polygon\n        (e.g. ``rotation=90``, applies a 90 degree rotation)\n    :return: List of regular polygon vertices\n        (e.g. ``[(25, 50), (50, 50), (50, 25), (25, 25)]``)\n\n    How are the vertices computed?\n    1. Compute the following variables\n        - theta: Angle between the apothem & the nearest polygon vertex\n        - side_length: Length of each polygon edge\n        - centroid: Center of bounding circle (1st, 2nd elements of bounding_circle)\n        - polygon_radius: Polygon radius (last element of bounding_circle)\n        - angles: Location of each polygon vertex in polar grid\n            (e.g. A square with 0 degree rotation => [225.0, 315.0, 45.0, 135.0])\n\n    2. For each angle in angles, get the polygon vertex at that angle\n        The vertex is computed using the equation below.\n            X= xcos(φ) + ysin(φ)\n            Y= −xsin(φ) + ycos(φ)\n\n        Note:\n            φ = angle in degrees\n            x = 0\n            y = polygon_radius\n\n        The formula above assumes rotation around the origin.\n        In our case, we are rotating around the centroid.\n        To account for this, we use the formula below\n            X = xcos(φ) + ysin(φ) + centroid_x\n            Y = −xsin(φ) + ycos(φ) + centroid_y\n    \"\"\"\n    # 1. Error Handling\n    # 1.1 Check `n_sides` has an appropriate value\n    if not isinstance(n_sides, int):\n        msg = \"n_sides should be an int\"\n        raise TypeError(msg)\n    if n_sides < 3:\n        msg = \"n_sides should be an int > 2\"\n        raise ValueError(msg)\n\n    # 1.2 Check `bounding_circle` has an appropriate value\n    if not isinstance(bounding_circle, (list, tuple)):\n        msg = \"bounding_circle should be a sequence\"\n        raise TypeError(msg)\n\n    if len(bounding_circle) == 3:\n        *centroid, polygon_radius = bounding_circle\n    elif len(bounding_circle) == 2:\n        centroid, polygon_radius = bounding_circle\n    else:\n        msg = (\n            \"bounding_circle should contain 2D coordinates \"\n            \"and a radius (e.g. (x, y, r) or ((x, y), r) )\"\n        )\n        raise ValueError(msg)\n\n    if not all(isinstance(i, (int, float)) for i in (*centroid, polygon_radius)):\n        msg = \"bounding_circle should only contain numeric data\"\n        raise ValueError(msg)\n\n    if not len(centroid) == 2:\n        msg = \"bounding_circle centre should contain 2D coordinates (e.g. (x, y))\"\n        raise ValueError(msg)\n\n    if polygon_radius <= 0:\n        msg = \"bounding_circle radius should be > 0\"\n        raise ValueError(msg)\n\n    # 1.3 Check `rotation` has an appropriate value\n    if not isinstance(rotation, (int, float)):\n        msg = \"rotation should be an int or float\"\n        raise ValueError(msg)\n\n    # 2. Define Helper Functions\n    def _apply_rotation(point: list[float], degrees: float) -> tuple[int, int]:\n        return (\n            round(\n                point[0] * math.cos(math.radians(360 - degrees))\n                - point[1] * math.sin(math.radians(360 - degrees))\n                + centroid[0],\n                2,\n            ),\n            round(\n                point[1] * math.cos(math.radians(360 - degrees))\n                + point[0] * math.sin(math.radians(360 - degrees))\n                + centroid[1],\n                2,\n            ),\n        )\n\n    def _compute_polygon_vertex(angle: float) -> tuple[int, int]:\n        start_point = [polygon_radius, 0]\n        return _apply_rotation(start_point, angle)\n\n    def _get_angles(n_sides: int, rotation: float) -> list[float]:\n        angles = []\n        degrees = 360 / n_sides\n        # Start with the bottom left polygon vertex\n        current_angle = (270 - 0.5 * degrees) + rotation\n        for _ in range(0, n_sides):\n            angles.append(current_angle)\n            current_angle += degrees\n            if current_angle > 360:\n                current_angle -= 360\n        return angles\n\n    # 3. Variable Declarations\n    angles = _get_angles(n_sides, rotation)\n\n    # 4. Compute Vertices\n    return [_compute_polygon_vertex(angle) for angle in angles]\n\n\ndef _color_diff(color1, color2: float | tuple[int, ...]) -> float:\n    \"\"\"\n    Uses 1-norm distance to calculate difference between two values.\n    \"\"\"\n    if isinstance(color2, tuple):\n        return sum(abs(color1[i] - color2[i]) for i in range(0, len(color2)))\n    else:\n        return abs(color1 - color2)\n",
            "header": {
                "file": {
                    "path": "src/PIL/ImageDraw.py"
                },
                "misc_line": null
            },
            "index_line": {
                "old_commit_hash": "42f2ee8c7",
                "new_commit_hash": "17c176430",
                "mode": "100644"
            },
            "is_binary_file": false,
            "binary_line": null,
            "minus_file": {
                "path": "a/src/PIL/ImageDraw.py"
            },
            "plus_file": {
                "path": "b/src/PIL/ImageDraw.py"
            },
            "hunks": [
                {
                    "descriptor": {
                        "old_range": {
                            "start": 181,
                            "length": 6
                        },
                        "new_range": {
                            "start": 181,
                            "length": 13
                        },
                        "section": "class ImageDraw:"
                    },
                    "line_group": {
                        "all_lines": [
                            {
                                "content": "        if ink is not None and ink != fill and width != 0:",
                                "type": "context"
                            },
                            {
                                "content": "            self.draw.draw_ellipse(xy, ink, 0, width)",
                                "type": "context"
                            },
                            {
                                "content": "",
                                "type": "context"
                            },
                            {
                                "content": "    def circle(",
                                "type": "added"
                            },
                            {
                                "content": "        self, xy: Sequence[float], radius: float, fill=None, outline=None, width=1",
                                "type": "added"
                            },
                            {
                                "content": "    ) -> None:",
                                "type": "added"
                            },
                            {
                                "content": "        \"\"\"Draw a circle given center coordinates and a radius.\"\"\"",
                                "type": "added"
                            },
                            {
                                "content": "        ellipse_xy = (xy[0] - radius, xy[1] - radius, xy[0] + radius, xy[1] + radius)",
                                "type": "added"
                            },
                            {
                                "content": "        self.ellipse(ellipse_xy, fill, outline, width)",
                                "type": "added"
                            },
                            {
                                "content": "",
                                "type": "added"
                            },
                            {
                                "content": "    def line(self, xy: Coords, fill=None, width=0, joint=None) -> None:",
                                "type": "context"
                            },
                            {
                                "content": "        \"\"\"Draw a line, or a connected sequence of line segments.\"\"\"",
                                "type": "context"
                            },
                            {
                                "content": "        ink = self._getink(fill)[0]",
                                "type": "context"
                            },
                            {
                                "content": "",
                                "type": "context"
                            }
                        ]
                    },
                    "modified_entities": [
                        {
                            "file_name": "src/PIL/ImageDraw.py",
                            "type": "class",
                            "name": "ImageDraw",
                            "content": "class ImageDraw:\n    font = None\n\n    def __init__(self, im: Image.Image, mode: str | None = None) -> None:\n        \"\"\"\n        Create a drawing instance.\n\n        :param im: The image to draw in.\n        :param mode: Optional mode to use for color values.  For RGB\n           images, this argument can be RGB or RGBA (to blend the\n           drawing into the image).  For all other modes, this argument\n           must be the same as the image mode.  If omitted, the mode\n           defaults to the mode of the image.\n        \"\"\"\n        im.load()\n        if im.readonly:\n            im._copy()  # make it writeable\n        blend = 0\n        if mode is None:\n            mode = im.mode\n        if mode != im.mode:\n            if mode == \"RGBA\" and im.mode == \"RGB\":\n                blend = 1\n            else:\n                msg = \"mode mismatch\"\n                raise ValueError(msg)\n        if mode == \"P\":\n            self.palette = im.palette\n        else:\n            self.palette = None\n        self._image = im\n        self.im = im.im\n        self.draw = Image.core.draw(self.im, blend)\n        self.mode = mode\n        if mode in (\"I\", \"F\"):\n            self.ink = self.draw.draw_ink(1)\n        else:\n            self.ink = self.draw.draw_ink(-1)\n        if mode in (\"1\", \"P\", \"I\", \"F\"):\n            # FIXME: fix Fill2 to properly support matte for I+F images\n            self.fontmode = \"1\"\n        else:\n            self.fontmode = \"L\"  # aliasing is okay for other modes\n        self.fill = False\n\n    if TYPE_CHECKING:\n        from . import ImageFont\n\n    def getfont(self) -> ImageFont.FreeTypeFont | ImageFont.ImageFont:\n        \"\"\"\n        Get the current default font.\n\n        To set the default font for this ImageDraw instance::\n\n            from PIL import ImageDraw, ImageFont\n            draw.font = ImageFont.truetype(\"Tests/fonts/FreeMono.ttf\")\n\n        To set the default font for all future ImageDraw instances::\n\n            from PIL import ImageDraw, ImageFont\n            ImageDraw.ImageDraw.font = ImageFont.truetype(\"Tests/fonts/FreeMono.ttf\")\n\n        If the current default font is ``None``,\n        it is initialized with ``ImageFont.load_default()``.\n\n        :returns: An image font.\"\"\"\n        if not self.font:\n            # FIXME: should add a font repository\n            from . import ImageFont\n\n            self.font = ImageFont.load_default()\n        return self.font\n\n    def _getfont(self, font_size: float | None):\n        if font_size is not None:\n            from . import ImageFont\n\n            font = ImageFont.load_default(font_size)\n        else:\n            font = self.getfont()\n        return font\n\n    def _getink(self, ink, fill=None) -> tuple[int | None, int | None]:\n        if ink is None and fill is None:\n            if self.fill:\n                fill = self.ink\n            else:\n                ink = self.ink\n        else:\n            if ink is not None:\n                if isinstance(ink, str):\n                    ink = ImageColor.getcolor(ink, self.mode)\n                if self.palette and not isinstance(ink, numbers.Number):\n                    ink = self.palette.getcolor(ink, self._image)\n                ink = self.draw.draw_ink(ink)\n            if fill is not None:\n                if isinstance(fill, str):\n                    fill = ImageColor.getcolor(fill, self.mode)\n                if self.palette and not isinstance(fill, numbers.Number):\n                    fill = self.palette.getcolor(fill, self._image)\n                fill = self.draw.draw_ink(fill)\n        return ink, fill\n\n    def arc(self, xy: Coords, start, end, fill=None, width=1) -> None:\n        \"\"\"Draw an arc.\"\"\"\n        ink, fill = self._getink(fill)\n        if ink is not None:\n            self.draw.draw_arc(xy, start, end, ink, width)\n\n    def bitmap(self, xy: Sequence[int], bitmap, fill=None) -> None:\n        \"\"\"Draw a bitmap.\"\"\"\n        bitmap.load()\n        ink, fill = self._getink(fill)\n        if ink is None:\n            ink = fill\n        if ink is not None:\n            self.draw.draw_bitmap(xy, bitmap.im, ink)\n\n    def chord(self, xy: Coords, start, end, fill=None, outline=None, width=1) -> None:\n        \"\"\"Draw a chord.\"\"\"\n        ink, fill = self._getink(outline, fill)\n        if fill is not None:\n            self.draw.draw_chord(xy, start, end, fill, 1)\n        if ink is not None and ink != fill and width != 0:\n            self.draw.draw_chord(xy, start, end, ink, 0, width)\n\n    def ellipse(self, xy: Coords, fill=None, outline=None, width=1) -> None:\n        \"\"\"Draw an ellipse.\"\"\"\n        ink, fill = self._getink(outline, fill)\n        if fill is not None:\n            self.draw.draw_ellipse(xy, fill, 1)\n        if ink is not None and ink != fill and width != 0:\n            self.draw.draw_ellipse(xy, ink, 0, width)\n\n    def circle(\n        self, xy: Sequence[float], radius: float, fill=None, outline=None, width=1\n    ) -> None:\n        \"\"\"Draw a circle given center coordinates and a radius.\"\"\"\n        ellipse_xy = (xy[0] - radius, xy[1] - radius, xy[0] + radius, xy[1] + radius)\n        self.ellipse(ellipse_xy, fill, outline, width)\n\n    def line(self, xy: Coords, fill=None, width=0, joint=None) -> None:\n        \"\"\"Draw a line, or a connected sequence of line segments.\"\"\"\n        ink = self._getink(fill)[0]\n        if ink is not None:\n            self.draw.draw_lines(xy, ink, width)\n            if joint == \"curve\" and width > 4:\n                points: Sequence[Sequence[float]]\n                if isinstance(xy[0], (list, tuple)):\n                    points = cast(Sequence[Sequence[float]], xy)\n                else:\n                    points = [\n                        cast(Sequence[float], tuple(xy[i : i + 2]))\n                        for i in range(0, len(xy), 2)\n                    ]\n                for i in range(1, len(points) - 1):\n                    point = points[i]\n                    angles = [\n                        math.degrees(math.atan2(end[0] - start[0], start[1] - end[1]))\n                        % 360\n                        for start, end in (\n                            (points[i - 1], point),\n                            (point, points[i + 1]),\n                        )\n                    ]\n                    if angles[0] == angles[1]:\n                        # This is a straight line, so no joint is required\n                        continue\n\n                    def coord_at_angle(coord, angle):\n                        x, y = coord\n                        angle -= 90\n                        distance = width / 2 - 1\n                        return tuple(\n                            p + (math.floor(p_d) if p_d > 0 else math.ceil(p_d))\n                            for p, p_d in (\n                                (x, distance * math.cos(math.radians(angle))),\n                                (y, distance * math.sin(math.radians(angle))),\n                            )\n                        )\n\n                    flipped = (\n                        angles[1] > angles[0] and angles[1] - 180 > angles[0]\n                    ) or (angles[1] < angles[0] and angles[1] + 180 > angles[0])\n                    coords = [\n                        (point[0] - width / 2 + 1, point[1] - width / 2 + 1),\n                        (point[0] + width / 2 - 1, point[1] + width / 2 - 1),\n                    ]\n                    if flipped:\n                        start, end = (angles[1] + 90, angles[0] + 90)\n                    else:\n                        start, end = (angles[0] - 90, angles[1] - 90)\n                    self.pieslice(coords, start - 90, end - 90, fill)\n\n                    if width > 8:\n                        # Cover potential gaps between the line and the joint\n                        if flipped:\n                            gap_coords = [\n                                coord_at_angle(point, angles[0] + 90),\n                                point,\n                                coord_at_angle(point, angles[1] + 90),\n                            ]\n                        else:\n                            gap_coords = [\n                                coord_at_angle(point, angles[0] - 90),\n                                point,\n                                coord_at_angle(point, angles[1] - 90),\n                            ]\n                        self.line(gap_coords, fill, width=3)\n\n    def shape(self, shape, fill=None, outline=None) -> None:\n        \"\"\"(Experimental) Draw a shape.\"\"\"\n        shape.close()\n        ink, fill = self._getink(outline, fill)\n        if fill is not None:\n            self.draw.draw_outline(shape, fill, 1)\n        if ink is not None and ink != fill:\n            self.draw.draw_outline(shape, ink, 0)\n\n    def pieslice(\n        self, xy: Coords, start, end, fill=None, outline=None, width=1\n    ) -> None:\n        \"\"\"Draw a pieslice.\"\"\"\n        ink, fill = self._getink(outline, fill)\n        if fill is not None:\n            self.draw.draw_pieslice(xy, start, end, fill, 1)\n        if ink is not None and ink != fill and width != 0:\n            self.draw.draw_pieslice(xy, start, end, ink, 0, width)\n\n    def point(self, xy: Coords, fill=None) -> None:\n        \"\"\"Draw one or more individual pixels.\"\"\"\n        ink, fill = self._getink(fill)\n        if ink is not None:\n            self.draw.draw_points(xy, ink)\n\n    def polygon(self, xy: Coords, fill=None, outline=None, width=1) -> None:\n        \"\"\"Draw a polygon.\"\"\"\n        ink, fill = self._getink(outline, fill)\n        if fill is not None:\n            self.draw.draw_polygon(xy, fill, 1)\n        if ink is not None and ink != fill and width != 0:\n            if width == 1:\n                self.draw.draw_polygon(xy, ink, 0, width)\n            elif self.im is not None:\n                # To avoid expanding the polygon outwards,\n                # use the fill as a mask\n                mask = Image.new(\"1\", self.im.size)\n                mask_ink = self._getink(1)[0]\n\n                fill_im = mask.copy()\n                draw = Draw(fill_im)\n                draw.draw.draw_polygon(xy, mask_ink, 1)\n\n                ink_im = mask.copy()\n                draw = Draw(ink_im)\n                width = width * 2 - 1\n                draw.draw.draw_polygon(xy, mask_ink, 0, width)\n\n                mask.paste(ink_im, mask=fill_im)\n\n                im = Image.new(self.mode, self.im.size)\n                draw = Draw(im)\n                draw.draw.draw_polygon(xy, ink, 0, width)\n                self.im.paste(im.im, (0, 0) + im.size, mask.im)\n\n    def regular_polygon(\n        self, bounding_circle, n_sides, rotation=0, fill=None, outline=None, width=1\n    ) -> None:\n        \"\"\"Draw a regular polygon.\"\"\"\n        xy = _compute_regular_polygon_vertices(bounding_circle, n_sides, rotation)\n        self.polygon(xy, fill, outline, width)\n\n    def rectangle(self, xy: Coords, fill=None, outline=None, width=1) -> None:\n        \"\"\"Draw a rectangle.\"\"\"\n        ink, fill = self._getink(outline, fill)\n        if fill is not None:\n            self.draw.draw_rectangle(xy, fill, 1)\n        if ink is not None and ink != fill and width != 0:\n            self.draw.draw_rectangle(xy, ink, 0, width)\n\n    def rounded_rectangle(\n        self, xy: Coords, radius=0, fill=None, outline=None, width=1, *, corners=None\n    ) -> None:\n        \"\"\"Draw a rounded rectangle.\"\"\"\n        if isinstance(xy[0], (list, tuple)):\n            (x0, y0), (x1, y1) = cast(Sequence[Sequence[float]], xy)\n        else:\n            x0, y0, x1, y1 = cast(Sequence[float], xy)\n        if x1 < x0:\n            msg = \"x1 must be greater than or equal to x0\"\n            raise ValueError(msg)\n        if y1 < y0:\n            msg = \"y1 must be greater than or equal to y0\"\n            raise ValueError(msg)\n        if corners is None:\n            corners = (True, True, True, True)\n\n        d = radius * 2\n\n        x0 = round(x0)\n        y0 = round(y0)\n        x1 = round(x1)\n        y1 = round(y1)\n        full_x, full_y = False, False\n        if all(corners):\n            full_x = d >= x1 - x0 - 1\n            if full_x:\n                # The two left and two right corners are joined\n                d = x1 - x0\n            full_y = d >= y1 - y0 - 1\n            if full_y:\n                # The two top and two bottom corners are joined\n                d = y1 - y0\n            if full_x and full_y:\n                # If all corners are joined, that is a circle\n                return self.ellipse(xy, fill, outline, width)\n\n        if d == 0 or not any(corners):\n            # If the corners have no curve,\n            # or there are no corners,\n            # that is a rectangle\n            return self.rectangle(xy, fill, outline, width)\n\n        r = d // 2\n        ink, fill = self._getink(outline, fill)\n\n        def draw_corners(pieslice) -> None:\n            parts: tuple[tuple[tuple[float, float, float, float], int, int], ...]\n            if full_x:\n                # Draw top and bottom halves\n                parts = (\n                    ((x0, y0, x0 + d, y0 + d), 180, 360),\n                    ((x0, y1 - d, x0 + d, y1), 0, 180),\n                )\n            elif full_y:\n                # Draw left and right halves\n                parts = (\n                    ((x0, y0, x0 + d, y0 + d), 90, 270),\n                    ((x1 - d, y0, x1, y0 + d), 270, 90),\n                )\n            else:\n                # Draw four separate corners\n                parts = tuple(\n                    part\n                    for i, part in enumerate(\n                        (\n                            ((x0, y0, x0 + d, y0 + d), 180, 270),\n                            ((x1 - d, y0, x1, y0 + d), 270, 360),\n                            ((x1 - d, y1 - d, x1, y1), 0, 90),\n                            ((x0, y1 - d, x0 + d, y1), 90, 180),\n                        )\n                    )\n                    if corners[i]\n                )\n            for part in parts:\n                if pieslice:\n                    self.draw.draw_pieslice(*(part + (fill, 1)))\n                else:\n                    self.draw.draw_arc(*(part + (ink, width)))\n\n        if fill is not None:\n            draw_corners(True)\n\n            if full_x:\n                self.draw.draw_rectangle((x0, y0 + r + 1, x1, y1 - r - 1), fill, 1)\n            else:\n                self.draw.draw_rectangle((x0 + r + 1, y0, x1 - r - 1, y1), fill, 1)\n            if not full_x and not full_y:\n                left = [x0, y0, x0 + r, y1]\n                if corners[0]:\n                    left[1] += r + 1\n                if corners[3]:\n                    left[3] -= r + 1\n                self.draw.draw_rectangle(left, fill, 1)\n\n                right = [x1 - r, y0, x1, y1]\n                if corners[1]:\n                    right[1] += r + 1\n                if corners[2]:\n                    right[3] -= r + 1\n                self.draw.draw_rectangle(right, fill, 1)\n        if ink is not None and ink != fill and width != 0:\n            draw_corners(False)\n\n            if not full_x:\n                top = [x0, y0, x1, y0 + width - 1]\n                if corners[0]:\n                    top[0] += r + 1\n                if corners[1]:\n                    top[2] -= r + 1\n                self.draw.draw_rectangle(top, ink, 1)\n\n                bottom = [x0, y1 - width + 1, x1, y1]\n                if corners[3]:\n                    bottom[0] += r + 1\n                if corners[2]:\n                    bottom[2] -= r + 1\n                self.draw.draw_rectangle(bottom, ink, 1)\n            if not full_y:\n                left = [x0, y0, x0 + width - 1, y1]\n                if corners[0]:\n                    left[1] += r + 1\n                if corners[3]:\n                    left[3] -= r + 1\n                self.draw.draw_rectangle(left, ink, 1)\n\n                right = [x1 - width + 1, y0, x1, y1]\n                if corners[1]:\n                    right[1] += r + 1\n                if corners[2]:\n                    right[3] -= r + 1\n                self.draw.draw_rectangle(right, ink, 1)\n\n    def _multiline_check(self, text) -> bool:\n        split_character = \"\\n\" if isinstance(text, str) else b\"\\n\"\n\n        return split_character in text\n\n    def _multiline_split(self, text) -> list[str | bytes]:\n        split_character = \"\\n\" if isinstance(text, str) else b\"\\n\"\n\n        return text.split(split_character)\n\n    def _multiline_spacing(self, font, spacing, stroke_width):\n        return (\n            self.textbbox((0, 0), \"A\", font, stroke_width=stroke_width)[3]\n            + stroke_width\n            + spacing\n        )\n\n    def text(\n        self,\n        xy,\n        text,\n        fill=None,\n        font=None,\n        anchor=None,\n        spacing=4,\n        align=\"left\",\n        direction=None,\n        features=None,\n        language=None,\n        stroke_width=0,\n        stroke_fill=None,\n        embedded_color=False,\n        *args,\n        **kwargs,\n    ) -> None:\n        \"\"\"Draw text.\"\"\"\n        if embedded_color and self.mode not in (\"RGB\", \"RGBA\"):\n            msg = \"Embedded color supported only in RGB and RGBA modes\"\n            raise ValueError(msg)\n\n        if font is None:\n            font = self._getfont(kwargs.get(\"font_size\"))\n\n        if self._multiline_check(text):\n            return self.multiline_text(\n                xy,\n                text,\n                fill,\n                font,\n                anchor,\n                spacing,\n                align,\n                direction,\n                features,\n                language,\n                stroke_width,\n                stroke_fill,\n                embedded_color,\n            )\n\n        def getink(fill):\n            ink, fill = self._getink(fill)\n            if ink is None:\n                return fill\n            return ink\n\n        def draw_text(ink, stroke_width=0, stroke_offset=None) -> None:\n            mode = self.fontmode\n            if stroke_width == 0 and embedded_color:\n                mode = \"RGBA\"\n            coord = []\n            start = []\n            for i in range(2):\n                coord.append(int(xy[i]))\n                start.append(math.modf(xy[i])[0])\n            try:\n                mask, offset = font.getmask2(\n                    text,\n                    mode,\n                    direction=direction,\n                    features=features,\n                    language=language,\n                    stroke_width=stroke_width,\n                    anchor=anchor,\n                    ink=ink,\n                    start=start,\n                    *args,\n                    **kwargs,\n                )\n                coord = [coord[0] + offset[0], coord[1] + offset[1]]\n            except AttributeError:\n                try:\n                    mask = font.getmask(\n                        text,\n                        mode,\n                        direction,\n                        features,\n                        language,\n                        stroke_width,\n                        anchor,\n                        ink,\n                        start=start,\n                        *args,\n                        **kwargs,\n                    )\n                except TypeError:\n                    mask = font.getmask(text)\n            if stroke_offset:\n                coord = [coord[0] + stroke_offset[0], coord[1] + stroke_offset[1]]\n            if mode == \"RGBA\":\n                # font.getmask2(mode=\"RGBA\") returns color in RGB bands and mask in A\n                # extract mask and set text alpha\n                color, mask = mask, mask.getband(3)\n                ink_alpha = struct.pack(\"i\", ink)[3]\n                color.fillband(3, ink_alpha)\n                x, y = coord\n                if self.im is not None:\n                    self.im.paste(\n                        color, (x, y, x + mask.size[0], y + mask.size[1]), mask\n                    )\n            else:\n                self.draw.draw_bitmap(coord, mask, ink)\n\n        ink = getink(fill)\n        if ink is not None:\n            stroke_ink = None\n            if stroke_width:\n                stroke_ink = getink(stroke_fill) if stroke_fill is not None else ink\n\n            if stroke_ink is not None:\n                # Draw stroked text\n                draw_text(stroke_ink, stroke_width)\n\n                # Draw normal text\n                draw_text(ink, 0)\n            else:\n                # Only draw normal text\n                draw_text(ink)\n\n    def multiline_text(\n        self,\n        xy,\n        text,\n        fill=None,\n        font=None,\n        anchor=None,\n        spacing=4,\n        align=\"left\",\n        direction=None,\n        features=None,\n        language=None,\n        stroke_width=0,\n        stroke_fill=None,\n        embedded_color=False,\n        *,\n        font_size=None,\n    ) -> None:\n        if direction == \"ttb\":\n            msg = \"ttb direction is unsupported for multiline text\"\n            raise ValueError(msg)\n\n        if anchor is None:\n            anchor = \"la\"\n        elif len(anchor) != 2:\n            msg = \"anchor must be a 2 character string\"\n            raise ValueError(msg)\n        elif anchor[1] in \"tb\":\n            msg = \"anchor not supported for multiline text\"\n            raise ValueError(msg)\n\n        if font is None:\n            font = self._getfont(font_size)\n\n        widths = []\n        max_width = 0\n        lines = self._multiline_split(text)\n        line_spacing = self._multiline_spacing(font, spacing, stroke_width)\n        for line in lines:\n            line_width = self.textlength(\n                line, font, direction=direction, features=features, language=language\n            )\n            widths.append(line_width)\n            max_width = max(max_width, line_width)\n\n        top = xy[1]\n        if anchor[1] == \"m\":\n            top -= (len(lines) - 1) * line_spacing / 2.0\n        elif anchor[1] == \"d\":\n            top -= (len(lines) - 1) * line_spacing\n\n        for idx, line in enumerate(lines):\n            left = xy[0]\n            width_difference = max_width - widths[idx]\n\n            # first align left by anchor\n            if anchor[0] == \"m\":\n                left -= width_difference / 2.0\n            elif anchor[0] == \"r\":\n                left -= width_difference\n\n            # then align by align parameter\n            if align == \"left\":\n                pass\n            elif align == \"center\":\n                left += width_difference / 2.0\n            elif align == \"right\":\n                left += width_difference\n            else:\n                msg = 'align must be \"left\", \"center\" or \"right\"'\n                raise ValueError(msg)\n\n            self.text(\n                (left, top),\n                line,\n                fill,\n                font,\n                anchor,\n                direction=direction,\n                features=features,\n                language=language,\n                stroke_width=stroke_width,\n                stroke_fill=stroke_fill,\n                embedded_color=embedded_color,\n            )\n            top += line_spacing\n\n    def textlength(\n        self,\n        text,\n        font=None,\n        direction=None,\n        features=None,\n        language=None,\n        embedded_color=False,\n        *,\n        font_size=None,\n    ):\n        \"\"\"Get the length of a given string, in pixels with 1/64 precision.\"\"\"\n        if self._multiline_check(text):\n            msg = \"can't measure length of multiline text\"\n            raise ValueError(msg)\n        if embedded_color and self.mode not in (\"RGB\", \"RGBA\"):\n            msg = \"Embedded color supported only in RGB and RGBA modes\"\n            raise ValueError(msg)\n\n        if font is None:\n            font = self._getfont(font_size)\n        mode = \"RGBA\" if embedded_color else self.fontmode\n        return font.getlength(text, mode, direction, features, language)\n\n    def textbbox(\n        self,\n        xy,\n        text,\n        font=None,\n        anchor=None,\n        spacing=4,\n        align=\"left\",\n        direction=None,\n        features=None,\n        language=None,\n        stroke_width=0,\n        embedded_color=False,\n        *,\n        font_size=None,\n    ) -> tuple[int, int, int, int]:\n        \"\"\"Get the bounding box of a given string, in pixels.\"\"\"\n        if embedded_color and self.mode not in (\"RGB\", \"RGBA\"):\n            msg = \"Embedded color supported only in RGB and RGBA modes\"\n            raise ValueError(msg)\n\n        if font is None:\n            font = self._getfont(font_size)\n\n        if self._multiline_check(text):\n            return self.multiline_textbbox(\n                xy,\n                text,\n                font,\n                anchor,\n                spacing,\n                align,\n                direction,\n                features,\n                language,\n                stroke_width,\n                embedded_color,\n            )\n\n        mode = \"RGBA\" if embedded_color else self.fontmode\n        bbox = font.getbbox(\n            text, mode, direction, features, language, stroke_width, anchor\n        )\n        return bbox[0] + xy[0], bbox[1] + xy[1], bbox[2] + xy[0], bbox[3] + xy[1]\n\n    def multiline_textbbox(\n        self,\n        xy,\n        text,\n        font=None,\n        anchor=None,\n        spacing=4,\n        align=\"left\",\n        direction=None,\n        features=None,\n        language=None,\n        stroke_width=0,\n        embedded_color=False,\n        *,\n        font_size=None,\n    ) -> tuple[int, int, int, int]:\n        if direction == \"ttb\":\n            msg = \"ttb direction is unsupported for multiline text\"\n            raise ValueError(msg)\n\n        if anchor is None:\n            anchor = \"la\"\n        elif len(anchor) != 2:\n            msg = \"anchor must be a 2 character string\"\n            raise ValueError(msg)\n        elif anchor[1] in \"tb\":\n            msg = \"anchor not supported for multiline text\"\n            raise ValueError(msg)\n\n        if font is None:\n            font = self._getfont(font_size)\n\n        widths = []\n        max_width = 0\n        lines = self._multiline_split(text)\n        line_spacing = self._multiline_spacing(font, spacing, stroke_width)\n        for line in lines:\n            line_width = self.textlength(\n                line,\n                font,\n                direction=direction,\n                features=features,\n                language=language,\n                embedded_color=embedded_color,\n            )\n            widths.append(line_width)\n            max_width = max(max_width, line_width)\n\n        top = xy[1]\n        if anchor[1] == \"m\":\n            top -= (len(lines) - 1) * line_spacing / 2.0\n        elif anchor[1] == \"d\":\n            top -= (len(lines) - 1) * line_spacing\n\n        bbox: tuple[int, int, int, int] | None = None\n\n        for idx, line in enumerate(lines):\n            left = xy[0]\n            width_difference = max_width - widths[idx]\n\n            # first align left by anchor\n            if anchor[0] == \"m\":\n                left -= width_difference / 2.0\n            elif anchor[0] == \"r\":\n                left -= width_difference\n\n            # then align by align parameter\n            if align == \"left\":\n                pass\n            elif align == \"center\":\n                left += width_difference / 2.0\n            elif align == \"right\":\n                left += width_difference\n            else:\n                msg = 'align must be \"left\", \"center\" or \"right\"'\n                raise ValueError(msg)\n\n            bbox_line = self.textbbox(\n                (left, top),\n                line,\n                font,\n                anchor,\n                direction=direction,\n                features=features,\n                language=language,\n                stroke_width=stroke_width,\n                embedded_color=embedded_color,\n            )\n            if bbox is None:\n                bbox = bbox_line\n            else:\n                bbox = (\n                    min(bbox[0], bbox_line[0]),\n                    min(bbox[1], bbox_line[1]),\n                    max(bbox[2], bbox_line[2]),\n                    max(bbox[3], bbox_line[3]),\n                )\n\n            top += line_spacing\n\n        if bbox is None:\n            return xy[0], xy[1], xy[0], xy[1]\n        return bbox",
                            "ast_type_str": "ClassDef",
                            "start_lineno": 50,
                            "end_lineno": 860,
                            "parent": null
                        }
                    ],
                    "added_entities": [
                        {
                            "file_name": "src/PIL/ImageDraw.py",
                            "type": "method",
                            "name": "ImageDraw.circle",
                            "content": "class ImageDraw:\n    font = None\n\n    def __init__(self, im: Image.Image, mode: str | None = None) -> None:\n        \"\"\"\n        Create a drawing instance.\n\n        :param im: The image to draw in.\n        :param mode: Optional mode to use for color values.  For RGB\n           images, this argument can be RGB or RGBA (to blend the\n           drawing into the image).  For all other modes, this argument\n           must be the same as the image mode.  If omitted, the mode\n           defaults to the mode of the image.\n        \"\"\"\n        im.load()\n        if im.readonly:\n            im._copy()  # make it writeable\n        blend = 0\n        if mode is None:\n            mode = im.mode\n        if mode != im.mode:\n            if mode == \"RGBA\" and im.mode == \"RGB\":\n                blend = 1\n            else:\n                msg = \"mode mismatch\"\n                raise ValueError(msg)\n        if mode == \"P\":\n            self.palette = im.palette\n        else:\n            self.palette = None\n        self._image = im\n        self.im = im.im\n        self.draw = Image.core.draw(self.im, blend)\n        self.mode = mode\n        if mode in (\"I\", \"F\"):\n            self.ink = self.draw.draw_ink(1)\n        else:\n            self.ink = self.draw.draw_ink(-1)\n        if mode in (\"1\", \"P\", \"I\", \"F\"):\n            # FIXME: fix Fill2 to properly support matte for I+F images\n            self.fontmode = \"1\"\n        else:\n            self.fontmode = \"L\"  # aliasing is okay for other modes\n        self.fill = False\n\n    if TYPE_CHECKING:\n        from . import ImageFont\n\n    def getfont(self) -> ImageFont.FreeTypeFont | ImageFont.ImageFont:\n        \"\"\"\n        Get the current default font.\n\n        To set the default font for this ImageDraw instance::\n\n            from PIL import ImageDraw, ImageFont\n            draw.font = ImageFont.truetype(\"Tests/fonts/FreeMono.ttf\")\n\n        To set the default font for all future ImageDraw instances::\n\n            from PIL import ImageDraw, ImageFont\n            ImageDraw.ImageDraw.font = ImageFont.truetype(\"Tests/fonts/FreeMono.ttf\")\n\n        If the current default font is ``None``,\n        it is initialized with ``ImageFont.load_default()``.\n\n        :returns: An image font.\"\"\"\n        if not self.font:\n            # FIXME: should add a font repository\n            from . import ImageFont\n\n            self.font = ImageFont.load_default()\n        return self.font\n\n    def _getfont(self, font_size: float | None):\n        if font_size is not None:\n            from . import ImageFont\n\n            font = ImageFont.load_default(font_size)\n        else:\n            font = self.getfont()\n        return font\n\n    def _getink(self, ink, fill=None) -> tuple[int | None, int | None]:\n        if ink is None and fill is None:\n            if self.fill:\n                fill = self.ink\n            else:\n                ink = self.ink\n        else:\n            if ink is not None:\n                if isinstance(ink, str):\n                    ink = ImageColor.getcolor(ink, self.mode)\n                if self.palette and not isinstance(ink, numbers.Number):\n                    ink = self.palette.getcolor(ink, self._image)\n                ink = self.draw.draw_ink(ink)\n            if fill is not None:\n                if isinstance(fill, str):\n                    fill = ImageColor.getcolor(fill, self.mode)\n                if self.palette and not isinstance(fill, numbers.Number):\n                    fill = self.palette.getcolor(fill, self._image)\n                fill = self.draw.draw_ink(fill)\n        return ink, fill\n\n    def arc(self, xy: Coords, start, end, fill=None, width=1) -> None:\n        \"\"\"Draw an arc.\"\"\"\n        ink, fill = self._getink(fill)\n        if ink is not None:\n            self.draw.draw_arc(xy, start, end, ink, width)\n\n    def bitmap(self, xy: Sequence[int], bitmap, fill=None) -> None:\n        \"\"\"Draw a bitmap.\"\"\"\n        bitmap.load()\n        ink, fill = self._getink(fill)\n        if ink is None:\n            ink = fill\n        if ink is not None:\n            self.draw.draw_bitmap(xy, bitmap.im, ink)\n\n    def chord(self, xy: Coords, start, end, fill=None, outline=None, width=1) -> None:\n        \"\"\"Draw a chord.\"\"\"\n        ink, fill = self._getink(outline, fill)\n        if fill is not None:\n            self.draw.draw_chord(xy, start, end, fill, 1)\n        if ink is not None and ink != fill and width != 0:\n            self.draw.draw_chord(xy, start, end, ink, 0, width)\n\n    def ellipse(self, xy: Coords, fill=None, outline=None, width=1) -> None:\n        \"\"\"Draw an ellipse.\"\"\"\n        ink, fill = self._getink(outline, fill)\n        if fill is not None:\n            self.draw.draw_ellipse(xy, fill, 1)\n        if ink is not None and ink != fill and width != 0:\n            self.draw.draw_ellipse(xy, ink, 0, width)\n\n    def circle(\n        self, xy: Sequence[float], radius: float, fill=None, outline=None, width=1\n    ) -> None:\n        \"\"\"Draw a circle given center coordinates and a radius.\"\"\"\n        ellipse_xy = (xy[0] - radius, xy[1] - radius, xy[0] + radius, xy[1] + radius)\n        self.ellipse(ellipse_xy, fill, outline, width)\n\n    def line(self, xy: Coords, fill=None, width=0, joint=None) -> None:\n        \"\"\"Draw a line, or a connected sequence of line segments.\"\"\"\n        ink = self._getink(fill)[0]\n        if ink is not None:\n            self.draw.draw_lines(xy, ink, width)\n            if joint == \"curve\" and width > 4:\n                points: Sequence[Sequence[float]]\n                if isinstance(xy[0], (list, tuple)):\n                    points = cast(Sequence[Sequence[float]], xy)\n                else:\n                    points = [\n                        cast(Sequence[float], tuple(xy[i : i + 2]))\n                        for i in range(0, len(xy), 2)\n                    ]\n                for i in range(1, len(points) - 1):\n                    point = points[i]\n                    angles = [\n                        math.degrees(math.atan2(end[0] - start[0], start[1] - end[1]))\n                        % 360\n                        for start, end in (\n                            (points[i - 1], point),\n                            (point, points[i + 1]),\n                        )\n                    ]\n                    if angles[0] == angles[1]:\n                        # This is a straight line, so no joint is required\n                        continue\n\n                    def coord_at_angle(coord, angle):\n                        x, y = coord\n                        angle -= 90\n                        distance = width / 2 - 1\n                        return tuple(\n                            p + (math.floor(p_d) if p_d > 0 else math.ceil(p_d))\n                            for p, p_d in (\n                                (x, distance * math.cos(math.radians(angle))),\n                                (y, distance * math.sin(math.radians(angle))),\n                            )\n                        )\n\n                    flipped = (\n                        angles[1] > angles[0] and angles[1] - 180 > angles[0]\n                    ) or (angles[1] < angles[0] and angles[1] + 180 > angles[0])\n                    coords = [\n                        (point[0] - width / 2 + 1, point[1] - width / 2 + 1),\n                        (point[0] + width / 2 - 1, point[1] + width / 2 - 1),\n                    ]\n                    if flipped:\n                        start, end = (angles[1] + 90, angles[0] + 90)\n                    else:\n                        start, end = (angles[0] - 90, angles[1] - 90)\n                    self.pieslice(coords, start - 90, end - 90, fill)\n\n                    if width > 8:\n                        # Cover potential gaps between the line and the joint\n                        if flipped:\n                            gap_coords = [\n                                coord_at_angle(point, angles[0] + 90),\n                                point,\n                                coord_at_angle(point, angles[1] + 90),\n                            ]\n                        else:\n                            gap_coords = [\n                                coord_at_angle(point, angles[0] - 90),\n                                point,\n                                coord_at_angle(point, angles[1] - 90),\n                            ]\n                        self.line(gap_coords, fill, width=3)\n\n    def shape(self, shape, fill=None, outline=None) -> None:\n        \"\"\"(Experimental) Draw a shape.\"\"\"\n        shape.close()\n        ink, fill = self._getink(outline, fill)\n        if fill is not None:\n            self.draw.draw_outline(shape, fill, 1)\n        if ink is not None and ink != fill:\n            self.draw.draw_outline(shape, ink, 0)\n\n    def pieslice(\n        self, xy: Coords, start, end, fill=None, outline=None, width=1\n    ) -> None:\n        \"\"\"Draw a pieslice.\"\"\"\n        ink, fill = self._getink(outline, fill)\n        if fill is not None:\n            self.draw.draw_pieslice(xy, start, end, fill, 1)\n        if ink is not None and ink != fill and width != 0:\n            self.draw.draw_pieslice(xy, start, end, ink, 0, width)\n\n    def point(self, xy: Coords, fill=None) -> None:\n        \"\"\"Draw one or more individual pixels.\"\"\"\n        ink, fill = self._getink(fill)\n        if ink is not None:\n            self.draw.draw_points(xy, ink)\n\n    def polygon(self, xy: Coords, fill=None, outline=None, width=1) -> None:\n        \"\"\"Draw a polygon.\"\"\"\n        ink, fill = self._getink(outline, fill)\n        if fill is not None:\n            self.draw.draw_polygon(xy, fill, 1)\n        if ink is not None and ink != fill and width != 0:\n            if width == 1:\n                self.draw.draw_polygon(xy, ink, 0, width)\n            elif self.im is not None:\n                # To avoid expanding the polygon outwards,\n                # use the fill as a mask\n                mask = Image.new(\"1\", self.im.size)\n                mask_ink = self._getink(1)[0]\n\n                fill_im = mask.copy()\n                draw = Draw(fill_im)\n                draw.draw.draw_polygon(xy, mask_ink, 1)\n\n                ink_im = mask.copy()\n                draw = Draw(ink_im)\n                width = width * 2 - 1\n                draw.draw.draw_polygon(xy, mask_ink, 0, width)\n\n                mask.paste(ink_im, mask=fill_im)\n\n                im = Image.new(self.mode, self.im.size)\n                draw = Draw(im)\n                draw.draw.draw_polygon(xy, ink, 0, width)\n                self.im.paste(im.im, (0, 0) + im.size, mask.im)\n\n    def regular_polygon(\n        self, bounding_circle, n_sides, rotation=0, fill=None, outline=None, width=1\n    ) -> None:\n        \"\"\"Draw a regular polygon.\"\"\"\n        xy = _compute_regular_polygon_vertices(bounding_circle, n_sides, rotation)\n        self.polygon(xy, fill, outline, width)\n\n    def rectangle(self, xy: Coords, fill=None, outline=None, width=1) -> None:\n        \"\"\"Draw a rectangle.\"\"\"\n        ink, fill = self._getink(outline, fill)\n        if fill is not None:\n            self.draw.draw_rectangle(xy, fill, 1)\n        if ink is not None and ink != fill and width != 0:\n            self.draw.draw_rectangle(xy, ink, 0, width)\n\n    def rounded_rectangle(\n        self, xy: Coords, radius=0, fill=None, outline=None, width=1, *, corners=None\n    ) -> None:\n        \"\"\"Draw a rounded rectangle.\"\"\"\n        if isinstance(xy[0], (list, tuple)):\n            (x0, y0), (x1, y1) = cast(Sequence[Sequence[float]], xy)\n        else:\n            x0, y0, x1, y1 = cast(Sequence[float], xy)\n        if x1 < x0:\n            msg = \"x1 must be greater than or equal to x0\"\n            raise ValueError(msg)\n        if y1 < y0:\n            msg = \"y1 must be greater than or equal to y0\"\n            raise ValueError(msg)\n        if corners is None:\n            corners = (True, True, True, True)\n\n        d = radius * 2\n\n        x0 = round(x0)\n        y0 = round(y0)\n        x1 = round(x1)\n        y1 = round(y1)\n        full_x, full_y = False, False\n        if all(corners):\n            full_x = d >= x1 - x0 - 1\n            if full_x:\n                # The two left and two right corners are joined\n                d = x1 - x0\n            full_y = d >= y1 - y0 - 1\n            if full_y:\n                # The two top and two bottom corners are joined\n                d = y1 - y0\n            if full_x and full_y:\n                # If all corners are joined, that is a circle\n                return self.ellipse(xy, fill, outline, width)\n\n        if d == 0 or not any(corners):\n            # If the corners have no curve,\n            # or there are no corners,\n            # that is a rectangle\n            return self.rectangle(xy, fill, outline, width)\n\n        r = d // 2\n        ink, fill = self._getink(outline, fill)\n\n        def draw_corners(pieslice) -> None:\n            parts: tuple[tuple[tuple[float, float, float, float], int, int], ...]\n            if full_x:\n                # Draw top and bottom halves\n                parts = (\n                    ((x0, y0, x0 + d, y0 + d), 180, 360),\n                    ((x0, y1 - d, x0 + d, y1), 0, 180),\n                )\n            elif full_y:\n                # Draw left and right halves\n                parts = (\n                    ((x0, y0, x0 + d, y0 + d), 90, 270),\n                    ((x1 - d, y0, x1, y0 + d), 270, 90),\n                )\n            else:\n                # Draw four separate corners\n                parts = tuple(\n                    part\n                    for i, part in enumerate(\n                        (\n                            ((x0, y0, x0 + d, y0 + d), 180, 270),\n                            ((x1 - d, y0, x1, y0 + d), 270, 360),\n                            ((x1 - d, y1 - d, x1, y1), 0, 90),\n                            ((x0, y1 - d, x0 + d, y1), 90, 180),\n                        )\n                    )\n                    if corners[i]\n                )\n            for part in parts:\n                if pieslice:\n                    self.draw.draw_pieslice(*(part + (fill, 1)))\n                else:\n                    self.draw.draw_arc(*(part + (ink, width)))\n\n        if fill is not None:\n            draw_corners(True)\n\n            if full_x:\n                self.draw.draw_rectangle((x0, y0 + r + 1, x1, y1 - r - 1), fill, 1)\n            else:\n                self.draw.draw_rectangle((x0 + r + 1, y0, x1 - r - 1, y1), fill, 1)\n            if not full_x and not full_y:\n                left = [x0, y0, x0 + r, y1]\n                if corners[0]:\n                    left[1] += r + 1\n                if corners[3]:\n                    left[3] -= r + 1\n                self.draw.draw_rectangle(left, fill, 1)\n\n                right = [x1 - r, y0, x1, y1]\n                if corners[1]:\n                    right[1] += r + 1\n                if corners[2]:\n                    right[3] -= r + 1\n                self.draw.draw_rectangle(right, fill, 1)\n        if ink is not None and ink != fill and width != 0:\n            draw_corners(False)\n\n            if not full_x:\n                top = [x0, y0, x1, y0 + width - 1]\n                if corners[0]:\n                    top[0] += r + 1\n                if corners[1]:\n                    top[2] -= r + 1\n                self.draw.draw_rectangle(top, ink, 1)\n\n                bottom = [x0, y1 - width + 1, x1, y1]\n                if corners[3]:\n                    bottom[0] += r + 1\n                if corners[2]:\n                    bottom[2] -= r + 1\n                self.draw.draw_rectangle(bottom, ink, 1)\n            if not full_y:\n                left = [x0, y0, x0 + width - 1, y1]\n                if corners[0]:\n                    left[1] += r + 1\n                if corners[3]:\n                    left[3] -= r + 1\n                self.draw.draw_rectangle(left, ink, 1)\n\n                right = [x1 - width + 1, y0, x1, y1]\n                if corners[1]:\n                    right[1] += r + 1\n                if corners[2]:\n                    right[3] -= r + 1\n                self.draw.draw_rectangle(right, ink, 1)\n\n    def _multiline_check(self, text) -> bool:\n        split_character = \"\\n\" if isinstance(text, str) else b\"\\n\"\n\n        return split_character in text\n\n    def _multiline_split(self, text) -> list[str | bytes]:\n        split_character = \"\\n\" if isinstance(text, str) else b\"\\n\"\n\n        return text.split(split_character)\n\n    def _multiline_spacing(self, font, spacing, stroke_width):\n        return (\n            self.textbbox((0, 0), \"A\", font, stroke_width=stroke_width)[3]\n            + stroke_width\n            + spacing\n        )\n\n    def text(\n        self,\n        xy,\n        text,\n        fill=None,\n        font=None,\n        anchor=None,\n        spacing=4,\n        align=\"left\",\n        direction=None,\n        features=None,\n        language=None,\n        stroke_width=0,\n        stroke_fill=None,\n        embedded_color=False,\n        *args,\n        **kwargs,\n    ) -> None:\n        \"\"\"Draw text.\"\"\"\n        if embedded_color and self.mode not in (\"RGB\", \"RGBA\"):\n            msg = \"Embedded color supported only in RGB and RGBA modes\"\n            raise ValueError(msg)\n\n        if font is None:\n            font = self._getfont(kwargs.get(\"font_size\"))\n\n        if self._multiline_check(text):\n            return self.multiline_text(\n                xy,\n                text,\n                fill,\n                font,\n                anchor,\n                spacing,\n                align,\n                direction,\n                features,\n                language,\n                stroke_width,\n                stroke_fill,\n                embedded_color,\n            )\n\n        def getink(fill):\n            ink, fill = self._getink(fill)\n            if ink is None:\n                return fill\n            return ink\n\n        def draw_text(ink, stroke_width=0, stroke_offset=None) -> None:\n            mode = self.fontmode\n            if stroke_width == 0 and embedded_color:\n                mode = \"RGBA\"\n            coord = []\n            start = []\n            for i in range(2):\n                coord.append(int(xy[i]))\n                start.append(math.modf(xy[i])[0])\n            try:\n                mask, offset = font.getmask2(\n                    text,\n                    mode,\n                    direction=direction,\n                    features=features,\n                    language=language,\n                    stroke_width=stroke_width,\n                    anchor=anchor,\n                    ink=ink,\n                    start=start,\n                    *args,\n                    **kwargs,\n                )\n                coord = [coord[0] + offset[0], coord[1] + offset[1]]\n            except AttributeError:\n                try:\n                    mask = font.getmask(\n                        text,\n                        mode,\n                        direction,\n                        features,\n                        language,\n                        stroke_width,\n                        anchor,\n                        ink,\n                        start=start,\n                        *args,\n                        **kwargs,\n                    )\n                except TypeError:\n                    mask = font.getmask(text)\n            if stroke_offset:\n                coord = [coord[0] + stroke_offset[0], coord[1] + stroke_offset[1]]\n            if mode == \"RGBA\":\n                # font.getmask2(mode=\"RGBA\") returns color in RGB bands and mask in A\n                # extract mask and set text alpha\n                color, mask = mask, mask.getband(3)\n                ink_alpha = struct.pack(\"i\", ink)[3]\n                color.fillband(3, ink_alpha)\n                x, y = coord\n                if self.im is not None:\n                    self.im.paste(\n                        color, (x, y, x + mask.size[0], y + mask.size[1]), mask\n                    )\n            else:\n                self.draw.draw_bitmap(coord, mask, ink)\n\n        ink = getink(fill)\n        if ink is not None:\n            stroke_ink = None\n            if stroke_width:\n                stroke_ink = getink(stroke_fill) if stroke_fill is not None else ink\n\n            if stroke_ink is not None:\n                # Draw stroked text\n                draw_text(stroke_ink, stroke_width)\n\n                # Draw normal text\n                draw_text(ink, 0)\n            else:\n                # Only draw normal text\n                draw_text(ink)\n\n    def multiline_text(\n        self,\n        xy,\n        text,\n        fill=None,\n        font=None,\n        anchor=None,\n        spacing=4,\n        align=\"left\",\n        direction=None,\n        features=None,\n        language=None,\n        stroke_width=0,\n        stroke_fill=None,\n        embedded_color=False,\n        *,\n        font_size=None,\n    ) -> None:\n        if direction == \"ttb\":\n            msg = \"ttb direction is unsupported for multiline text\"\n            raise ValueError(msg)\n\n        if anchor is None:\n            anchor = \"la\"\n        elif len(anchor) != 2:\n            msg = \"anchor must be a 2 character string\"\n            raise ValueError(msg)\n        elif anchor[1] in \"tb\":\n            msg = \"anchor not supported for multiline text\"\n            raise ValueError(msg)\n\n        if font is None:\n            font = self._getfont(font_size)\n\n        widths = []\n        max_width = 0\n        lines = self._multiline_split(text)\n        line_spacing = self._multiline_spacing(font, spacing, stroke_width)\n        for line in lines:\n            line_width = self.textlength(\n                line, font, direction=direction, features=features, language=language\n            )\n            widths.append(line_width)\n            max_width = max(max_width, line_width)\n\n        top = xy[1]\n        if anchor[1] == \"m\":\n            top -= (len(lines) - 1) * line_spacing / 2.0\n        elif anchor[1] == \"d\":\n            top -= (len(lines) - 1) * line_spacing\n\n        for idx, line in enumerate(lines):\n            left = xy[0]\n            width_difference = max_width - widths[idx]\n\n            # first align left by anchor\n            if anchor[0] == \"m\":\n                left -= width_difference / 2.0\n            elif anchor[0] == \"r\":\n                left -= width_difference\n\n            # then align by align parameter\n            if align == \"left\":\n                pass\n            elif align == \"center\":\n                left += width_difference / 2.0\n            elif align == \"right\":\n                left += width_difference\n            else:\n                msg = 'align must be \"left\", \"center\" or \"right\"'\n                raise ValueError(msg)\n\n            self.text(\n                (left, top),\n                line,\n                fill,\n                font,\n                anchor,\n                direction=direction,\n                features=features,\n                language=language,\n                stroke_width=stroke_width,\n                stroke_fill=stroke_fill,\n                embedded_color=embedded_color,\n            )\n            top += line_spacing\n\n    def textlength(\n        self,\n        text,\n        font=None,\n        direction=None,\n        features=None,\n        language=None,\n        embedded_color=False,\n        *,\n        font_size=None,\n    ):\n        \"\"\"Get the length of a given string, in pixels with 1/64 precision.\"\"\"\n        if self._multiline_check(text):\n            msg = \"can't measure length of multiline text\"\n            raise ValueError(msg)\n        if embedded_color and self.mode not in (\"RGB\", \"RGBA\"):\n            msg = \"Embedded color supported only in RGB and RGBA modes\"\n            raise ValueError(msg)\n\n        if font is None:\n            font = self._getfont(font_size)\n        mode = \"RGBA\" if embedded_color else self.fontmode\n        return font.getlength(text, mode, direction, features, language)\n\n    def textbbox(\n        self,\n        xy,\n        text,\n        font=None,\n        anchor=None,\n        spacing=4,\n        align=\"left\",\n        direction=None,\n        features=None,\n        language=None,\n        stroke_width=0,\n        embedded_color=False,\n        *,\n        font_size=None,\n    ) -> tuple[int, int, int, int]:\n        \"\"\"Get the bounding box of a given string, in pixels.\"\"\"\n        if embedded_color and self.mode not in (\"RGB\", \"RGBA\"):\n            msg = \"Embedded color supported only in RGB and RGBA modes\"\n            raise ValueError(msg)\n\n        if font is None:\n            font = self._getfont(font_size)\n\n        if self._multiline_check(text):\n            return self.multiline_textbbox(\n                xy,\n                text,\n                font,\n                anchor,\n                spacing,\n                align,\n                direction,\n                features,\n                language,\n                stroke_width,\n                embedded_color,\n            )\n\n        mode = \"RGBA\" if embedded_color else self.fontmode\n        bbox = font.getbbox(\n            text, mode, direction, features, language, stroke_width, anchor\n        )\n        return bbox[0] + xy[0], bbox[1] + xy[1], bbox[2] + xy[0], bbox[3] + xy[1]\n\n    def multiline_textbbox(\n        self,\n        xy,\n        text,\n        font=None,\n        anchor=None,\n        spacing=4,\n        align=\"left\",\n        direction=None,\n        features=None,\n        language=None,\n        stroke_width=0,\n        embedded_color=False,\n        *,\n        font_size=None,\n    ) -> tuple[int, int, int, int]:\n        if direction == \"ttb\":\n            msg = \"ttb direction is unsupported for multiline text\"\n            raise ValueError(msg)\n\n        if anchor is None:\n            anchor = \"la\"\n        elif len(anchor) != 2:\n            msg = \"anchor must be a 2 character string\"\n            raise ValueError(msg)\n        elif anchor[1] in \"tb\":\n            msg = \"anchor not supported for multiline text\"\n            raise ValueError(msg)\n\n        if font is None:\n            font = self._getfont(font_size)\n\n        widths = []\n        max_width = 0\n        lines = self._multiline_split(text)\n        line_spacing = self._multiline_spacing(font, spacing, stroke_width)\n        for line in lines:\n            line_width = self.textlength(\n                line,\n                font,\n                direction=direction,\n                features=features,\n                language=language,\n                embedded_color=embedded_color,\n            )\n            widths.append(line_width)\n            max_width = max(max_width, line_width)\n\n        top = xy[1]\n        if anchor[1] == \"m\":\n            top -= (len(lines) - 1) * line_spacing / 2.0\n        elif anchor[1] == \"d\":\n            top -= (len(lines) - 1) * line_spacing\n\n        bbox: tuple[int, int, int, int] | None = None\n\n        for idx, line in enumerate(lines):\n            left = xy[0]\n            width_difference = max_width - widths[idx]\n\n            # first align left by anchor\n            if anchor[0] == \"m\":\n                left -= width_difference / 2.0\n            elif anchor[0] == \"r\":\n                left -= width_difference\n\n            # then align by align parameter\n            if align == \"left\":\n                pass\n            elif align == \"center\":\n                left += width_difference / 2.0\n            elif align == \"right\":\n                left += width_difference\n            else:\n                msg = 'align must be \"left\", \"center\" or \"right\"'\n                raise ValueError(msg)\n\n            bbox_line = self.textbbox(\n                (left, top),\n                line,\n                font,\n                anchor,\n                direction=direction,\n                features=features,\n                language=language,\n                stroke_width=stroke_width,\n                embedded_color=embedded_color,\n            )\n            if bbox is None:\n                bbox = bbox_line\n            else:\n                bbox = (\n                    min(bbox[0], bbox_line[0]),\n                    min(bbox[1], bbox_line[1]),\n                    max(bbox[2], bbox_line[2]),\n                    max(bbox[3], bbox_line[3]),\n                )\n\n            top += line_spacing\n\n        if bbox is None:\n            return xy[0], xy[1], xy[0], xy[1]\n        return bbox",
                            "ast_type_str": "ClassDef",
                            "start_lineno": 184,
                            "end_lineno": 189,
                            "parent": {
                                "file_name": "src/PIL/ImageDraw.py",
                                "type": "class",
                                "name": "ImageDraw",
                                "content": "class ImageDraw:\n    font = None\n\n    def __init__(self, im: Image.Image, mode: str | None = None) -> None:\n        \"\"\"\n        Create a drawing instance.\n\n        :param im: The image to draw in.\n        :param mode: Optional mode to use for color values.  For RGB\n           images, this argument can be RGB or RGBA (to blend the\n           drawing into the image).  For all other modes, this argument\n           must be the same as the image mode.  If omitted, the mode\n           defaults to the mode of the image.\n        \"\"\"\n        im.load()\n        if im.readonly:\n            im._copy()  # make it writeable\n        blend = 0\n        if mode is None:\n            mode = im.mode\n        if mode != im.mode:\n            if mode == \"RGBA\" and im.mode == \"RGB\":\n                blend = 1\n            else:\n                msg = \"mode mismatch\"\n                raise ValueError(msg)\n        if mode == \"P\":\n            self.palette = im.palette\n        else:\n            self.palette = None\n        self._image = im\n        self.im = im.im\n        self.draw = Image.core.draw(self.im, blend)\n        self.mode = mode\n        if mode in (\"I\", \"F\"):\n            self.ink = self.draw.draw_ink(1)\n        else:\n            self.ink = self.draw.draw_ink(-1)\n        if mode in (\"1\", \"P\", \"I\", \"F\"):\n            # FIXME: fix Fill2 to properly support matte for I+F images\n            self.fontmode = \"1\"\n        else:\n            self.fontmode = \"L\"  # aliasing is okay for other modes\n        self.fill = False\n\n    if TYPE_CHECKING:\n        from . import ImageFont\n\n    def getfont(self) -> ImageFont.FreeTypeFont | ImageFont.ImageFont:\n        \"\"\"\n        Get the current default font.\n\n        To set the default font for this ImageDraw instance::\n\n            from PIL import ImageDraw, ImageFont\n            draw.font = ImageFont.truetype(\"Tests/fonts/FreeMono.ttf\")\n\n        To set the default font for all future ImageDraw instances::\n\n            from PIL import ImageDraw, ImageFont\n            ImageDraw.ImageDraw.font = ImageFont.truetype(\"Tests/fonts/FreeMono.ttf\")\n\n        If the current default font is ``None``,\n        it is initialized with ``ImageFont.load_default()``.\n\n        :returns: An image font.\"\"\"\n        if not self.font:\n            # FIXME: should add a font repository\n            from . import ImageFont\n\n            self.font = ImageFont.load_default()\n        return self.font\n\n    def _getfont(self, font_size: float | None):\n        if font_size is not None:\n            from . import ImageFont\n\n            font = ImageFont.load_default(font_size)\n        else:\n            font = self.getfont()\n        return font\n\n    def _getink(self, ink, fill=None) -> tuple[int | None, int | None]:\n        if ink is None and fill is None:\n            if self.fill:\n                fill = self.ink\n            else:\n                ink = self.ink\n        else:\n            if ink is not None:\n                if isinstance(ink, str):\n                    ink = ImageColor.getcolor(ink, self.mode)\n                if self.palette and not isinstance(ink, numbers.Number):\n                    ink = self.palette.getcolor(ink, self._image)\n                ink = self.draw.draw_ink(ink)\n            if fill is not None:\n                if isinstance(fill, str):\n                    fill = ImageColor.getcolor(fill, self.mode)\n                if self.palette and not isinstance(fill, numbers.Number):\n                    fill = self.palette.getcolor(fill, self._image)\n                fill = self.draw.draw_ink(fill)\n        return ink, fill\n\n    def arc(self, xy: Coords, start, end, fill=None, width=1) -> None:\n        \"\"\"Draw an arc.\"\"\"\n        ink, fill = self._getink(fill)\n        if ink is not None:\n            self.draw.draw_arc(xy, start, end, ink, width)\n\n    def bitmap(self, xy: Sequence[int], bitmap, fill=None) -> None:\n        \"\"\"Draw a bitmap.\"\"\"\n        bitmap.load()\n        ink, fill = self._getink(fill)\n        if ink is None:\n            ink = fill\n        if ink is not None:\n            self.draw.draw_bitmap(xy, bitmap.im, ink)\n\n    def chord(self, xy: Coords, start, end, fill=None, outline=None, width=1) -> None:\n        \"\"\"Draw a chord.\"\"\"\n        ink, fill = self._getink(outline, fill)\n        if fill is not None:\n            self.draw.draw_chord(xy, start, end, fill, 1)\n        if ink is not None and ink != fill and width != 0:\n            self.draw.draw_chord(xy, start, end, ink, 0, width)\n\n    def ellipse(self, xy: Coords, fill=None, outline=None, width=1) -> None:\n        \"\"\"Draw an ellipse.\"\"\"\n        ink, fill = self._getink(outline, fill)\n        if fill is not None:\n            self.draw.draw_ellipse(xy, fill, 1)\n        if ink is not None and ink != fill and width != 0:\n            self.draw.draw_ellipse(xy, ink, 0, width)\n\n    def circle(\n        self, xy: Sequence[float], radius: float, fill=None, outline=None, width=1\n    ) -> None:\n        \"\"\"Draw a circle given center coordinates and a radius.\"\"\"\n        ellipse_xy = (xy[0] - radius, xy[1] - radius, xy[0] + radius, xy[1] + radius)\n        self.ellipse(ellipse_xy, fill, outline, width)\n\n    def line(self, xy: Coords, fill=None, width=0, joint=None) -> None:\n        \"\"\"Draw a line, or a connected sequence of line segments.\"\"\"\n        ink = self._getink(fill)[0]\n        if ink is not None:\n            self.draw.draw_lines(xy, ink, width)\n            if joint == \"curve\" and width > 4:\n                points: Sequence[Sequence[float]]\n                if isinstance(xy[0], (list, tuple)):\n                    points = cast(Sequence[Sequence[float]], xy)\n                else:\n                    points = [\n                        cast(Sequence[float], tuple(xy[i : i + 2]))\n                        for i in range(0, len(xy), 2)\n                    ]\n                for i in range(1, len(points) - 1):\n                    point = points[i]\n                    angles = [\n                        math.degrees(math.atan2(end[0] - start[0], start[1] - end[1]))\n                        % 360\n                        for start, end in (\n                            (points[i - 1], point),\n                            (point, points[i + 1]),\n                        )\n                    ]\n                    if angles[0] == angles[1]:\n                        # This is a straight line, so no joint is required\n                        continue\n\n                    def coord_at_angle(coord, angle):\n                        x, y = coord\n                        angle -= 90\n                        distance = width / 2 - 1\n                        return tuple(\n                            p + (math.floor(p_d) if p_d > 0 else math.ceil(p_d))\n                            for p, p_d in (\n                                (x, distance * math.cos(math.radians(angle))),\n                                (y, distance * math.sin(math.radians(angle))),\n                            )\n                        )\n\n                    flipped = (\n                        angles[1] > angles[0] and angles[1] - 180 > angles[0]\n                    ) or (angles[1] < angles[0] and angles[1] + 180 > angles[0])\n                    coords = [\n                        (point[0] - width / 2 + 1, point[1] - width / 2 + 1),\n                        (point[0] + width / 2 - 1, point[1] + width / 2 - 1),\n                    ]\n                    if flipped:\n                        start, end = (angles[1] + 90, angles[0] + 90)\n                    else:\n                        start, end = (angles[0] - 90, angles[1] - 90)\n                    self.pieslice(coords, start - 90, end - 90, fill)\n\n                    if width > 8:\n                        # Cover potential gaps between the line and the joint\n                        if flipped:\n                            gap_coords = [\n                                coord_at_angle(point, angles[0] + 90),\n                                point,\n                                coord_at_angle(point, angles[1] + 90),\n                            ]\n                        else:\n                            gap_coords = [\n                                coord_at_angle(point, angles[0] - 90),\n                                point,\n                                coord_at_angle(point, angles[1] - 90),\n                            ]\n                        self.line(gap_coords, fill, width=3)\n\n    def shape(self, shape, fill=None, outline=None) -> None:\n        \"\"\"(Experimental) Draw a shape.\"\"\"\n        shape.close()\n        ink, fill = self._getink(outline, fill)\n        if fill is not None:\n            self.draw.draw_outline(shape, fill, 1)\n        if ink is not None and ink != fill:\n            self.draw.draw_outline(shape, ink, 0)\n\n    def pieslice(\n        self, xy: Coords, start, end, fill=None, outline=None, width=1\n    ) -> None:\n        \"\"\"Draw a pieslice.\"\"\"\n        ink, fill = self._getink(outline, fill)\n        if fill is not None:\n            self.draw.draw_pieslice(xy, start, end, fill, 1)\n        if ink is not None and ink != fill and width != 0:\n            self.draw.draw_pieslice(xy, start, end, ink, 0, width)\n\n    def point(self, xy: Coords, fill=None) -> None:\n        \"\"\"Draw one or more individual pixels.\"\"\"\n        ink, fill = self._getink(fill)\n        if ink is not None:\n            self.draw.draw_points(xy, ink)\n\n    def polygon(self, xy: Coords, fill=None, outline=None, width=1) -> None:\n        \"\"\"Draw a polygon.\"\"\"\n        ink, fill = self._getink(outline, fill)\n        if fill is not None:\n            self.draw.draw_polygon(xy, fill, 1)\n        if ink is not None and ink != fill and width != 0:\n            if width == 1:\n                self.draw.draw_polygon(xy, ink, 0, width)\n            elif self.im is not None:\n                # To avoid expanding the polygon outwards,\n                # use the fill as a mask\n                mask = Image.new(\"1\", self.im.size)\n                mask_ink = self._getink(1)[0]\n\n                fill_im = mask.copy()\n                draw = Draw(fill_im)\n                draw.draw.draw_polygon(xy, mask_ink, 1)\n\n                ink_im = mask.copy()\n                draw = Draw(ink_im)\n                width = width * 2 - 1\n                draw.draw.draw_polygon(xy, mask_ink, 0, width)\n\n                mask.paste(ink_im, mask=fill_im)\n\n                im = Image.new(self.mode, self.im.size)\n                draw = Draw(im)\n                draw.draw.draw_polygon(xy, ink, 0, width)\n                self.im.paste(im.im, (0, 0) + im.size, mask.im)\n\n    def regular_polygon(\n        self, bounding_circle, n_sides, rotation=0, fill=None, outline=None, width=1\n    ) -> None:\n        \"\"\"Draw a regular polygon.\"\"\"\n        xy = _compute_regular_polygon_vertices(bounding_circle, n_sides, rotation)\n        self.polygon(xy, fill, outline, width)\n\n    def rectangle(self, xy: Coords, fill=None, outline=None, width=1) -> None:\n        \"\"\"Draw a rectangle.\"\"\"\n        ink, fill = self._getink(outline, fill)\n        if fill is not None:\n            self.draw.draw_rectangle(xy, fill, 1)\n        if ink is not None and ink != fill and width != 0:\n            self.draw.draw_rectangle(xy, ink, 0, width)\n\n    def rounded_rectangle(\n        self, xy: Coords, radius=0, fill=None, outline=None, width=1, *, corners=None\n    ) -> None:\n        \"\"\"Draw a rounded rectangle.\"\"\"\n        if isinstance(xy[0], (list, tuple)):\n            (x0, y0), (x1, y1) = cast(Sequence[Sequence[float]], xy)\n        else:\n            x0, y0, x1, y1 = cast(Sequence[float], xy)\n        if x1 < x0:\n            msg = \"x1 must be greater than or equal to x0\"\n            raise ValueError(msg)\n        if y1 < y0:\n            msg = \"y1 must be greater than or equal to y0\"\n            raise ValueError(msg)\n        if corners is None:\n            corners = (True, True, True, True)\n\n        d = radius * 2\n\n        x0 = round(x0)\n        y0 = round(y0)\n        x1 = round(x1)\n        y1 = round(y1)\n        full_x, full_y = False, False\n        if all(corners):\n            full_x = d >= x1 - x0 - 1\n            if full_x:\n                # The two left and two right corners are joined\n                d = x1 - x0\n            full_y = d >= y1 - y0 - 1\n            if full_y:\n                # The two top and two bottom corners are joined\n                d = y1 - y0\n            if full_x and full_y:\n                # If all corners are joined, that is a circle\n                return self.ellipse(xy, fill, outline, width)\n\n        if d == 0 or not any(corners):\n            # If the corners have no curve,\n            # or there are no corners,\n            # that is a rectangle\n            return self.rectangle(xy, fill, outline, width)\n\n        r = d // 2\n        ink, fill = self._getink(outline, fill)\n\n        def draw_corners(pieslice) -> None:\n            parts: tuple[tuple[tuple[float, float, float, float], int, int], ...]\n            if full_x:\n                # Draw top and bottom halves\n                parts = (\n                    ((x0, y0, x0 + d, y0 + d), 180, 360),\n                    ((x0, y1 - d, x0 + d, y1), 0, 180),\n                )\n            elif full_y:\n                # Draw left and right halves\n                parts = (\n                    ((x0, y0, x0 + d, y0 + d), 90, 270),\n                    ((x1 - d, y0, x1, y0 + d), 270, 90),\n                )\n            else:\n                # Draw four separate corners\n                parts = tuple(\n                    part\n                    for i, part in enumerate(\n                        (\n                            ((x0, y0, x0 + d, y0 + d), 180, 270),\n                            ((x1 - d, y0, x1, y0 + d), 270, 360),\n                            ((x1 - d, y1 - d, x1, y1), 0, 90),\n                            ((x0, y1 - d, x0 + d, y1), 90, 180),\n                        )\n                    )\n                    if corners[i]\n                )\n            for part in parts:\n                if pieslice:\n                    self.draw.draw_pieslice(*(part + (fill, 1)))\n                else:\n                    self.draw.draw_arc(*(part + (ink, width)))\n\n        if fill is not None:\n            draw_corners(True)\n\n            if full_x:\n                self.draw.draw_rectangle((x0, y0 + r + 1, x1, y1 - r - 1), fill, 1)\n            else:\n                self.draw.draw_rectangle((x0 + r + 1, y0, x1 - r - 1, y1), fill, 1)\n            if not full_x and not full_y:\n                left = [x0, y0, x0 + r, y1]\n                if corners[0]:\n                    left[1] += r + 1\n                if corners[3]:\n                    left[3] -= r + 1\n                self.draw.draw_rectangle(left, fill, 1)\n\n                right = [x1 - r, y0, x1, y1]\n                if corners[1]:\n                    right[1] += r + 1\n                if corners[2]:\n                    right[3] -= r + 1\n                self.draw.draw_rectangle(right, fill, 1)\n        if ink is not None and ink != fill and width != 0:\n            draw_corners(False)\n\n            if not full_x:\n                top = [x0, y0, x1, y0 + width - 1]\n                if corners[0]:\n                    top[0] += r + 1\n                if corners[1]:\n                    top[2] -= r + 1\n                self.draw.draw_rectangle(top, ink, 1)\n\n                bottom = [x0, y1 - width + 1, x1, y1]\n                if corners[3]:\n                    bottom[0] += r + 1\n                if corners[2]:\n                    bottom[2] -= r + 1\n                self.draw.draw_rectangle(bottom, ink, 1)\n            if not full_y:\n                left = [x0, y0, x0 + width - 1, y1]\n                if corners[0]:\n                    left[1] += r + 1\n                if corners[3]:\n                    left[3] -= r + 1\n                self.draw.draw_rectangle(left, ink, 1)\n\n                right = [x1 - width + 1, y0, x1, y1]\n                if corners[1]:\n                    right[1] += r + 1\n                if corners[2]:\n                    right[3] -= r + 1\n                self.draw.draw_rectangle(right, ink, 1)\n\n    def _multiline_check(self, text) -> bool:\n        split_character = \"\\n\" if isinstance(text, str) else b\"\\n\"\n\n        return split_character in text\n\n    def _multiline_split(self, text) -> list[str | bytes]:\n        split_character = \"\\n\" if isinstance(text, str) else b\"\\n\"\n\n        return text.split(split_character)\n\n    def _multiline_spacing(self, font, spacing, stroke_width):\n        return (\n            self.textbbox((0, 0), \"A\", font, stroke_width=stroke_width)[3]\n            + stroke_width\n            + spacing\n        )\n\n    def text(\n        self,\n        xy,\n        text,\n        fill=None,\n        font=None,\n        anchor=None,\n        spacing=4,\n        align=\"left\",\n        direction=None,\n        features=None,\n        language=None,\n        stroke_width=0,\n        stroke_fill=None,\n        embedded_color=False,\n        *args,\n        **kwargs,\n    ) -> None:\n        \"\"\"Draw text.\"\"\"\n        if embedded_color and self.mode not in (\"RGB\", \"RGBA\"):\n            msg = \"Embedded color supported only in RGB and RGBA modes\"\n            raise ValueError(msg)\n\n        if font is None:\n            font = self._getfont(kwargs.get(\"font_size\"))\n\n        if self._multiline_check(text):\n            return self.multiline_text(\n                xy,\n                text,\n                fill,\n                font,\n                anchor,\n                spacing,\n                align,\n                direction,\n                features,\n                language,\n                stroke_width,\n                stroke_fill,\n                embedded_color,\n            )\n\n        def getink(fill):\n            ink, fill = self._getink(fill)\n            if ink is None:\n                return fill\n            return ink\n\n        def draw_text(ink, stroke_width=0, stroke_offset=None) -> None:\n            mode = self.fontmode\n            if stroke_width == 0 and embedded_color:\n                mode = \"RGBA\"\n            coord = []\n            start = []\n            for i in range(2):\n                coord.append(int(xy[i]))\n                start.append(math.modf(xy[i])[0])\n            try:\n                mask, offset = font.getmask2(\n                    text,\n                    mode,\n                    direction=direction,\n                    features=features,\n                    language=language,\n                    stroke_width=stroke_width,\n                    anchor=anchor,\n                    ink=ink,\n                    start=start,\n                    *args,\n                    **kwargs,\n                )\n                coord = [coord[0] + offset[0], coord[1] + offset[1]]\n            except AttributeError:\n                try:\n                    mask = font.getmask(\n                        text,\n                        mode,\n                        direction,\n                        features,\n                        language,\n                        stroke_width,\n                        anchor,\n                        ink,\n                        start=start,\n                        *args,\n                        **kwargs,\n                    )\n                except TypeError:\n                    mask = font.getmask(text)\n            if stroke_offset:\n                coord = [coord[0] + stroke_offset[0], coord[1] + stroke_offset[1]]\n            if mode == \"RGBA\":\n                # font.getmask2(mode=\"RGBA\") returns color in RGB bands and mask in A\n                # extract mask and set text alpha\n                color, mask = mask, mask.getband(3)\n                ink_alpha = struct.pack(\"i\", ink)[3]\n                color.fillband(3, ink_alpha)\n                x, y = coord\n                if self.im is not None:\n                    self.im.paste(\n                        color, (x, y, x + mask.size[0], y + mask.size[1]), mask\n                    )\n            else:\n                self.draw.draw_bitmap(coord, mask, ink)\n\n        ink = getink(fill)\n        if ink is not None:\n            stroke_ink = None\n            if stroke_width:\n                stroke_ink = getink(stroke_fill) if stroke_fill is not None else ink\n\n            if stroke_ink is not None:\n                # Draw stroked text\n                draw_text(stroke_ink, stroke_width)\n\n                # Draw normal text\n                draw_text(ink, 0)\n            else:\n                # Only draw normal text\n                draw_text(ink)\n\n    def multiline_text(\n        self,\n        xy,\n        text,\n        fill=None,\n        font=None,\n        anchor=None,\n        spacing=4,\n        align=\"left\",\n        direction=None,\n        features=None,\n        language=None,\n        stroke_width=0,\n        stroke_fill=None,\n        embedded_color=False,\n        *,\n        font_size=None,\n    ) -> None:\n        if direction == \"ttb\":\n            msg = \"ttb direction is unsupported for multiline text\"\n            raise ValueError(msg)\n\n        if anchor is None:\n            anchor = \"la\"\n        elif len(anchor) != 2:\n            msg = \"anchor must be a 2 character string\"\n            raise ValueError(msg)\n        elif anchor[1] in \"tb\":\n            msg = \"anchor not supported for multiline text\"\n            raise ValueError(msg)\n\n        if font is None:\n            font = self._getfont(font_size)\n\n        widths = []\n        max_width = 0\n        lines = self._multiline_split(text)\n        line_spacing = self._multiline_spacing(font, spacing, stroke_width)\n        for line in lines:\n            line_width = self.textlength(\n                line, font, direction=direction, features=features, language=language\n            )\n            widths.append(line_width)\n            max_width = max(max_width, line_width)\n\n        top = xy[1]\n        if anchor[1] == \"m\":\n            top -= (len(lines) - 1) * line_spacing / 2.0\n        elif anchor[1] == \"d\":\n            top -= (len(lines) - 1) * line_spacing\n\n        for idx, line in enumerate(lines):\n            left = xy[0]\n            width_difference = max_width - widths[idx]\n\n            # first align left by anchor\n            if anchor[0] == \"m\":\n                left -= width_difference / 2.0\n            elif anchor[0] == \"r\":\n                left -= width_difference\n\n            # then align by align parameter\n            if align == \"left\":\n                pass\n            elif align == \"center\":\n                left += width_difference / 2.0\n            elif align == \"right\":\n                left += width_difference\n            else:\n                msg = 'align must be \"left\", \"center\" or \"right\"'\n                raise ValueError(msg)\n\n            self.text(\n                (left, top),\n                line,\n                fill,\n                font,\n                anchor,\n                direction=direction,\n                features=features,\n                language=language,\n                stroke_width=stroke_width,\n                stroke_fill=stroke_fill,\n                embedded_color=embedded_color,\n            )\n            top += line_spacing\n\n    def textlength(\n        self,\n        text,\n        font=None,\n        direction=None,\n        features=None,\n        language=None,\n        embedded_color=False,\n        *,\n        font_size=None,\n    ):\n        \"\"\"Get the length of a given string, in pixels with 1/64 precision.\"\"\"\n        if self._multiline_check(text):\n            msg = \"can't measure length of multiline text\"\n            raise ValueError(msg)\n        if embedded_color and self.mode not in (\"RGB\", \"RGBA\"):\n            msg = \"Embedded color supported only in RGB and RGBA modes\"\n            raise ValueError(msg)\n\n        if font is None:\n            font = self._getfont(font_size)\n        mode = \"RGBA\" if embedded_color else self.fontmode\n        return font.getlength(text, mode, direction, features, language)\n\n    def textbbox(\n        self,\n        xy,\n        text,\n        font=None,\n        anchor=None,\n        spacing=4,\n        align=\"left\",\n        direction=None,\n        features=None,\n        language=None,\n        stroke_width=0,\n        embedded_color=False,\n        *,\n        font_size=None,\n    ) -> tuple[int, int, int, int]:\n        \"\"\"Get the bounding box of a given string, in pixels.\"\"\"\n        if embedded_color and self.mode not in (\"RGB\", \"RGBA\"):\n            msg = \"Embedded color supported only in RGB and RGBA modes\"\n            raise ValueError(msg)\n\n        if font is None:\n            font = self._getfont(font_size)\n\n        if self._multiline_check(text):\n            return self.multiline_textbbox(\n                xy,\n                text,\n                font,\n                anchor,\n                spacing,\n                align,\n                direction,\n                features,\n                language,\n                stroke_width,\n                embedded_color,\n            )\n\n        mode = \"RGBA\" if embedded_color else self.fontmode\n        bbox = font.getbbox(\n            text, mode, direction, features, language, stroke_width, anchor\n        )\n        return bbox[0] + xy[0], bbox[1] + xy[1], bbox[2] + xy[0], bbox[3] + xy[1]\n\n    def multiline_textbbox(\n        self,\n        xy,\n        text,\n        font=None,\n        anchor=None,\n        spacing=4,\n        align=\"left\",\n        direction=None,\n        features=None,\n        language=None,\n        stroke_width=0,\n        embedded_color=False,\n        *,\n        font_size=None,\n    ) -> tuple[int, int, int, int]:\n        if direction == \"ttb\":\n            msg = \"ttb direction is unsupported for multiline text\"\n            raise ValueError(msg)\n\n        if anchor is None:\n            anchor = \"la\"\n        elif len(anchor) != 2:\n            msg = \"anchor must be a 2 character string\"\n            raise ValueError(msg)\n        elif anchor[1] in \"tb\":\n            msg = \"anchor not supported for multiline text\"\n            raise ValueError(msg)\n\n        if font is None:\n            font = self._getfont(font_size)\n\n        widths = []\n        max_width = 0\n        lines = self._multiline_split(text)\n        line_spacing = self._multiline_spacing(font, spacing, stroke_width)\n        for line in lines:\n            line_width = self.textlength(\n                line,\n                font,\n                direction=direction,\n                features=features,\n                language=language,\n                embedded_color=embedded_color,\n            )\n            widths.append(line_width)\n            max_width = max(max_width, line_width)\n\n        top = xy[1]\n        if anchor[1] == \"m\":\n            top -= (len(lines) - 1) * line_spacing / 2.0\n        elif anchor[1] == \"d\":\n            top -= (len(lines) - 1) * line_spacing\n\n        bbox: tuple[int, int, int, int] | None = None\n\n        for idx, line in enumerate(lines):\n            left = xy[0]\n            width_difference = max_width - widths[idx]\n\n            # first align left by anchor\n            if anchor[0] == \"m\":\n                left -= width_difference / 2.0\n            elif anchor[0] == \"r\":\n                left -= width_difference\n\n            # then align by align parameter\n            if align == \"left\":\n                pass\n            elif align == \"center\":\n                left += width_difference / 2.0\n            elif align == \"right\":\n                left += width_difference\n            else:\n                msg = 'align must be \"left\", \"center\" or \"right\"'\n                raise ValueError(msg)\n\n            bbox_line = self.textbbox(\n                (left, top),\n                line,\n                font,\n                anchor,\n                direction=direction,\n                features=features,\n                language=language,\n                stroke_width=stroke_width,\n                embedded_color=embedded_color,\n            )\n            if bbox is None:\n                bbox = bbox_line\n            else:\n                bbox = (\n                    min(bbox[0], bbox_line[0]),\n                    min(bbox[1], bbox_line[1]),\n                    max(bbox[2], bbox_line[2]),\n                    max(bbox[3], bbox_line[3]),\n                )\n\n            top += line_spacing\n\n        if bbox is None:\n            return xy[0], xy[1], xy[0], xy[1]\n        return bbox",
                                "ast_type_str": "ClassDef",
                                "start_lineno": 50,
                                "end_lineno": 860,
                                "parent": null
                            }
                        }
                    ],
                    "deleted_entities": []
                }
            ]
        }
    ],
    "old_commit_hash": "fdf103c5ad2cb3fe710e16f2fa3019262d088818^",
    "new_commit_hash": "fdf103c5ad2cb3fe710e16f2fa3019262d088818",
    "commit_message": "Merge pull request #8085 from void4/imagedraw_circle",
    "commit_date": "2024-05-28T12:35:31+03:00",
    "metadata": {}
}
{
    "repo_name": "pillow",
    "new_commit_hash": "fdf103c5ad2cb3fe710e16f2fa3019262d088818",
    "test_file_codes": [
        "from __future__ import annotations\n\nimport contextlib\nimport os.path\nfrom typing import Sequence\n\nimport pytest\n\nfrom PIL import Image, ImageColor, ImageDraw, ImageFont, features\nfrom PIL._typing import Coords\n\nfrom .helper import (\n    assert_image_equal,\n    assert_image_equal_tofile,\n    assert_image_similar_tofile,\n    hopper,\n    skip_unless_feature,\n)\n\nBLACK = (0, 0, 0)\nWHITE = (255, 255, 255)\nGRAY = (190, 190, 190)\nDEFAULT_MODE = \"RGB\"\nIMAGES_PATH = os.path.join(\"Tests\", \"images\", \"imagedraw\")\n\n# Image size\nW, H = 100, 100\n\n# Bounding box points\nX0 = int(W / 4)\nX1 = int(X0 * 3)\nY0 = int(H / 4)\nY1 = int(X0 * 3)\n\n# Bounding boxes\nBBOX = (((X0, Y0), (X1, Y1)), [(X0, Y0), (X1, Y1)], (X0, Y0, X1, Y1), [X0, Y0, X1, Y1])\n\n# Coordinate sequences\nPOINTS = (\n    ((10, 10), (20, 40), (30, 30)),\n    [(10, 10), (20, 40), (30, 30)],\n    (10, 10, 20, 40, 30, 30),\n    [10, 10, 20, 40, 30, 30],\n)\n\nKITE_POINTS = (\n    ((10, 50), (70, 10), (90, 50), (70, 90), (10, 50)),\n    [(10, 50), (70, 10), (90, 50), (70, 90), (10, 50)],\n)\n\n\ndef test_sanity() -> None:\n    im = hopper(\"RGB\").copy()\n\n    draw = ImageDraw.ImageDraw(im)\n    draw = ImageDraw.Draw(im)\n\n    draw.ellipse(list(range(4)))\n    draw.line(list(range(10)))\n    draw.polygon(list(range(100)))\n    draw.rectangle(list(range(4)))\n\n\ndef test_valueerror() -> None:\n    with Image.open(\"Tests/images/chi.gif\") as im:\n        draw = ImageDraw.Draw(im)\n        draw.line((0, 0), fill=(0, 0, 0))\n\n\ndef test_mode_mismatch() -> None:\n    im = hopper(\"RGB\").copy()\n\n    with pytest.raises(ValueError):\n        ImageDraw.ImageDraw(im, mode=\"L\")\n\n\n@pytest.mark.parametrize(\"bbox\", BBOX)\n@pytest.mark.parametrize(\"start, end\", ((0, 180), (0.5, 180.4)))\ndef test_arc(bbox: Coords, start: float, end: float) -> None:\n    # Arrange\n    im = Image.new(\"RGB\", (W, H))\n    draw = ImageDraw.Draw(im)\n\n    # Act\n    draw.arc(bbox, start, end)\n\n    # Assert\n    assert_image_similar_tofile(im, \"Tests/images/imagedraw_arc.png\", 1)\n\n\n@pytest.mark.parametrize(\"bbox\", BBOX)\ndef test_arc_end_le_start(bbox: Coords) -> None:\n    # Arrange\n    im = Image.new(\"RGB\", (W, H))\n    draw = ImageDraw.Draw(im)\n    start = 270.5\n    end = 0\n\n    # Act\n    draw.arc(bbox, start=start, end=end)\n\n    # Assert\n    assert_image_equal_tofile(im, \"Tests/images/imagedraw_arc_end_le_start.png\")\n\n\n@pytest.mark.parametrize(\"bbox\", BBOX)\ndef test_arc_no_loops(bbox: Coords) -> None:\n    # No need to go in loops\n    # Arrange\n    im = Image.new(\"RGB\", (W, H))\n    draw = ImageDraw.Draw(im)\n    start = 5\n    end = 370\n\n    # Act\n    draw.arc(bbox, start=start, end=end)\n\n    # Assert\n    assert_image_similar_tofile(im, \"Tests/images/imagedraw_arc_no_loops.png\", 1)\n\n\n@pytest.mark.parametrize(\"bbox\", BBOX)\ndef test_arc_width(bbox: Coords) -> None:\n    # Arrange\n    im = Image.new(\"RGB\", (W, H))\n    draw = ImageDraw.Draw(im)\n\n    # Act\n    draw.arc(bbox, 10, 260, width=5)\n\n    # Assert\n    assert_image_similar_tofile(im, \"Tests/images/imagedraw_arc_width.png\", 1)\n\n\n@pytest.mark.parametrize(\"bbox\", BBOX)\ndef test_arc_width_pieslice_large(bbox: Coords) -> None:\n    # Tests an arc with a large enough width that it is a pieslice\n    # Arrange\n    im = Image.new(\"RGB\", (W, H))\n    draw = ImageDraw.Draw(im)\n\n    # Act\n    draw.arc(bbox, 10, 260, fill=\"yellow\", width=100)\n\n    # Assert\n    assert_image_similar_tofile(im, \"Tests/images/imagedraw_arc_width_pieslice.png\", 1)\n\n\n@pytest.mark.parametrize(\"bbox\", BBOX)\ndef test_arc_width_fill(bbox: Coords) -> None:\n    # Arrange\n    im = Image.new(\"RGB\", (W, H))\n    draw = ImageDraw.Draw(im)\n\n    # Act\n    draw.arc(bbox, 10, 260, fill=\"yellow\", width=5)\n\n    # Assert\n    assert_image_similar_tofile(im, \"Tests/images/imagedraw_arc_width_fill.png\", 1)\n\n\n@pytest.mark.parametrize(\"bbox\", BBOX)\ndef test_arc_width_non_whole_angle(bbox: Coords) -> None:\n    # Arrange\n    im = Image.new(\"RGB\", (W, H))\n    draw = ImageDraw.Draw(im)\n    expected = \"Tests/images/imagedraw_arc_width_non_whole_angle.png\"\n\n    # Act\n    draw.arc(bbox, 10, 259.5, width=5)\n\n    # Assert\n    assert_image_similar_tofile(im, expected, 1)\n\n\ndef test_arc_high() -> None:\n    # Arrange\n    im = Image.new(\"RGB\", (200, 200))\n    draw = ImageDraw.Draw(im)\n\n    # Act\n    draw.arc([10, 10, 89, 189], 20, 330, width=20, fill=\"white\")\n    draw.arc([110, 10, 189, 189], 20, 150, width=20, fill=\"white\")\n\n    # Assert\n    assert_image_equal_tofile(im, \"Tests/images/imagedraw_arc_high.png\")\n\n\ndef test_bitmap() -> None:\n    # Arrange\n    im = Image.new(\"RGB\", (W, H))\n    draw = ImageDraw.Draw(im)\n    with Image.open(\"Tests/images/pil123rgba.png\") as small:\n        small = small.resize((50, 50), Image.Resampling.NEAREST)\n\n        # Act\n        draw.bitmap((10, 10), small)\n\n    # Assert\n    assert_image_equal_tofile(im, \"Tests/images/imagedraw_bitmap.png\")\n\n\n@pytest.mark.parametrize(\"mode\", (\"RGB\", \"L\"))\n@pytest.mark.parametrize(\"bbox\", BBOX)\ndef test_chord(mode: str, bbox: Coords) -> None:\n    # Arrange\n    im = Image.new(mode, (W, H))\n    draw = ImageDraw.Draw(im)\n    expected = f\"Tests/images/imagedraw_chord_{mode}.png\"\n\n    # Act\n    draw.chord(bbox, 0, 180, fill=\"red\", outline=\"yellow\")\n\n    # Assert\n    assert_image_similar_tofile(im, expected, 1)\n\n\n@pytest.mark.parametrize(\"bbox\", BBOX)\ndef test_chord_width(bbox: Coords) -> None:\n    # Arrange\n    im = Image.new(\"RGB\", (W, H))\n    draw = ImageDraw.Draw(im)\n\n    # Act\n    draw.chord(bbox, 10, 260, outline=\"yellow\", width=5)\n\n    # Assert\n    assert_image_similar_tofile(im, \"Tests/images/imagedraw_chord_width.png\", 1)\n\n\n@pytest.mark.parametrize(\"bbox\", BBOX)\ndef test_chord_width_fill(bbox: Coords) -> None:\n    # Arrange\n    im = Image.new(\"RGB\", (W, H))\n    draw = ImageDraw.Draw(im)\n\n    # Act\n    draw.chord(bbox, 10, 260, fill=\"red\", outline=\"yellow\", width=5)\n\n    # Assert\n    assert_image_similar_tofile(im, \"Tests/images/imagedraw_chord_width_fill.png\", 1)\n\n\n@pytest.mark.parametrize(\"bbox\", BBOX)\ndef test_chord_zero_width(bbox: Coords) -> None:\n    # Arrange\n    im = Image.new(\"RGB\", (W, H))\n    draw = ImageDraw.Draw(im)\n\n    # Act\n    draw.chord(bbox, 10, 260, fill=\"red\", outline=\"yellow\", width=0)\n\n    # Assert\n    assert_image_equal_tofile(im, \"Tests/images/imagedraw_chord_zero_width.png\")\n\n\ndef test_chord_too_fat() -> None:\n    # Arrange\n    im = Image.new(\"RGB\", (100, 100))\n    draw = ImageDraw.Draw(im)\n\n    # Act\n    draw.chord([-150, -150, 99, 99], 15, 60, width=10, fill=\"white\", outline=\"red\")\n\n    # Assert\n    assert_image_equal_tofile(im, \"Tests/images/imagedraw_chord_too_fat.png\")\n\n\n@pytest.mark.parametrize(\"mode\", (\"RGB\", \"L\"))\n@pytest.mark.parametrize(\"xy\", ((W / 2, H / 2), [W / 2, H / 2]))\ndef test_circle(mode: str, xy: Sequence[float]) -> None:\n    # Arrange\n    im = Image.new(mode, (W, H))\n    draw = ImageDraw.Draw(im)\n    expected = f\"Tests/images/imagedraw_ellipse_{mode}.png\"\n\n    # Act\n    draw.circle(xy, 25, fill=\"green\", outline=\"blue\")\n\n    # Assert\n    assert_image_similar_tofile(im, expected, 1)\n\n\n@pytest.mark.parametrize(\"mode\", (\"RGB\", \"L\"))\n@pytest.mark.parametrize(\"bbox\", BBOX)\ndef test_ellipse(mode: str, bbox: Coords) -> None:\n    # Arrange\n    im = Image.new(mode, (W, H))\n    draw = ImageDraw.Draw(im)\n    expected = f\"Tests/images/imagedraw_ellipse_{mode}.png\"\n\n    # Act\n    draw.ellipse(bbox, fill=\"green\", outline=\"blue\")\n\n    # Assert\n    assert_image_similar_tofile(im, expected, 1)\n\n\n@pytest.mark.parametrize(\"bbox\", BBOX)\ndef test_ellipse_translucent(bbox: Coords) -> None:\n    # Arrange\n    im = Image.new(\"RGB\", (W, H))\n    draw = ImageDraw.Draw(im, \"RGBA\")\n\n    # Act\n    draw.ellipse(bbox, fill=(0, 255, 0, 127))\n\n    # Assert\n    expected = \"Tests/images/imagedraw_ellipse_translucent.png\"\n    assert_image_similar_tofile(im, expected, 1)\n\n\ndef test_ellipse_edge() -> None:\n    # Arrange\n    im = Image.new(\"RGB\", (W, H))\n    draw = ImageDraw.Draw(im)\n\n    # Act\n    draw.ellipse(((0, 0), (W - 1, H - 1)), fill=\"white\")\n\n    # Assert\n    assert_image_similar_tofile(im, \"Tests/images/imagedraw_ellipse_edge.png\", 1)\n\n\ndef test_ellipse_symmetric() -> None:\n    for width, bbox in (\n        (100, (24, 24, 75, 75)),\n        (101, (25, 25, 75, 75)),\n    ):\n        im = Image.new(\"RGB\", (width, 100))\n        draw = ImageDraw.Draw(im)\n        draw.ellipse(bbox, fill=\"green\", outline=\"blue\")\n        assert_image_equal(im, im.transpose(Image.Transpose.FLIP_LEFT_RIGHT))\n\n\n@pytest.mark.parametrize(\"bbox\", BBOX)\ndef test_ellipse_width(bbox: Coords) -> None:\n    # Arrange\n    im = Image.new(\"RGB\", (W, H))\n    draw = ImageDraw.Draw(im)\n\n    # Act\n    draw.ellipse(bbox, outline=\"blue\", width=5)\n\n    # Assert\n    assert_image_similar_tofile(im, \"Tests/images/imagedraw_ellipse_width.png\", 1)\n\n\ndef test_ellipse_width_large() -> None:\n    # Arrange\n    im = Image.new(\"RGB\", (500, 500))\n    draw = ImageDraw.Draw(im)\n\n    # Act\n    draw.ellipse((25, 25, 475, 475), outline=\"blue\", width=75)\n\n    # Assert\n    assert_image_similar_tofile(im, \"Tests/images/imagedraw_ellipse_width_large.png\", 1)\n\n\n@pytest.mark.parametrize(\"bbox\", BBOX)\ndef test_ellipse_width_fill(bbox: Coords) -> None:\n    # Arrange\n    im = Image.new(\"RGB\", (W, H))\n    draw = ImageDraw.Draw(im)\n\n    # Act\n    draw.ellipse(bbox, fill=\"green\", outline=\"blue\", width=5)\n\n    # Assert\n    assert_image_similar_tofile(im, \"Tests/images/imagedraw_ellipse_width_fill.png\", 1)\n\n\n@pytest.mark.parametrize(\"bbox\", BBOX)\ndef test_ellipse_zero_width(bbox: Coords) -> None:\n    # Arrange\n    im = Image.new(\"RGB\", (W, H))\n    draw = ImageDraw.Draw(im)\n\n    # Act\n    draw.ellipse(bbox, fill=\"green\", outline=\"blue\", width=0)\n\n    # Assert\n    assert_image_equal_tofile(im, \"Tests/images/imagedraw_ellipse_zero_width.png\")\n\n\ndef ellipse_various_sizes_helper(filled: bool) -> Image.Image:\n    ellipse_sizes = range(32)\n    image_size = sum(ellipse_sizes) + len(ellipse_sizes) + 1\n    im = Image.new(\"RGB\", (image_size, image_size))\n    draw = ImageDraw.Draw(im)\n\n    x = 1\n    for w in ellipse_sizes:\n        y = 1\n        for h in ellipse_sizes:\n            x1 = x + w\n            if w:\n                x1 -= 1\n            y1 = y + h\n            if h:\n                y1 -= 1\n            border = [x, y, x1, y1]\n            if filled:\n                draw.ellipse(border, fill=\"white\")\n            else:\n                draw.ellipse(border, outline=\"white\")\n            y += h + 1\n        x += w + 1\n\n    return im\n\n\ndef test_ellipse_various_sizes() -> None:\n    im = ellipse_various_sizes_helper(False)\n\n    assert_image_equal_tofile(im, \"Tests/images/imagedraw_ellipse_various_sizes.png\")\n\n\ndef test_ellipse_various_sizes_filled() -> None:\n    im = ellipse_various_sizes_helper(True)\n\n    assert_image_equal_tofile(\n        im, \"Tests/images/imagedraw_ellipse_various_sizes_filled.png\"\n    )\n\n\n@pytest.mark.parametrize(\"points\", POINTS)\ndef test_line(points: Coords) -> None:\n    # Arrange\n    im = Image.new(\"RGB\", (W, H))\n    draw = ImageDraw.Draw(im)\n\n    # Act\n    draw.line(points, fill=\"yellow\", width=2)\n\n    # Assert\n    assert_image_equal_tofile(im, \"Tests/images/imagedraw_line.png\")\n\n\ndef test_shape1() -> None:\n    # Arrange\n    im = Image.new(\"RGB\", (100, 100), \"white\")\n    draw = ImageDraw.Draw(im)\n    x0, y0 = 5, 5\n    x1, y1 = 5, 50\n    x2, y2 = 95, 50\n    x3, y3 = 95, 5\n\n    # Act\n    s = ImageDraw.Outline()\n    s.move(x0, y0)\n    s.curve(x1, y1, x2, y2, x3, y3)\n    s.line(x0, y0)\n\n    draw.shape(s, fill=1)\n\n    # Assert\n    assert_image_equal_tofile(im, \"Tests/images/imagedraw_shape1.png\")\n\n\ndef test_shape2() -> None:\n    # Arrange\n    im = Image.new(\"RGB\", (100, 100), \"white\")\n    draw = ImageDraw.Draw(im)\n    x0, y0 = 95, 95\n    x1, y1 = 95, 50\n    x2, y2 = 5, 50\n    x3, y3 = 5, 95\n\n    # Act\n    s = ImageDraw.Outline()\n    s.move(x0, y0)\n    s.curve(x1, y1, x2, y2, x3, y3)\n    s.line(x0, y0)\n\n    draw.shape(s, outline=\"blue\")\n\n    # Assert\n    assert_image_equal_tofile(im, \"Tests/images/imagedraw_shape2.png\")\n\n\ndef test_transform() -> None:\n    # Arrange\n    im = Image.new(\"RGB\", (100, 100), \"white\")\n    expected = im.copy()\n    draw = ImageDraw.Draw(im)\n\n    # Act\n    s = ImageDraw.Outline()\n    s.line(0, 0)\n    s.transform((0, 0, 0, 0, 0, 0))\n\n    draw.shape(s, fill=1)\n\n    # Assert\n    assert_image_equal(im, expected)\n\n\n@pytest.mark.parametrize(\"bbox\", BBOX)\n@pytest.mark.parametrize(\"start, end\", ((-92, 46), (-92.2, 46.2)))\ndef test_pieslice(bbox: Coords, start: float, end: float) -> None:\n    # Arrange\n    im = Image.new(\"RGB\", (W, H))\n    draw = ImageDraw.Draw(im)\n\n    # Act\n    draw.pieslice(bbox, start, end, fill=\"white\", outline=\"blue\")\n\n    # Assert\n    assert_image_similar_tofile(im, \"Tests/images/imagedraw_pieslice.png\", 1)\n\n\n@pytest.mark.parametrize(\"bbox\", BBOX)\ndef test_pieslice_width(bbox: Coords) -> None:\n    # Arrange\n    im = Image.new(\"RGB\", (W, H))\n    draw = ImageDraw.Draw(im)\n\n    # Act\n    draw.pieslice(bbox, 10, 260, outline=\"blue\", width=5)\n\n    # Assert\n    assert_image_similar_tofile(im, \"Tests/images/imagedraw_pieslice_width.png\", 1)\n\n\n@pytest.mark.parametrize(\"bbox\", BBOX)\ndef test_pieslice_width_fill(bbox: Coords) -> None:\n    # Arrange\n    im = Image.new(\"RGB\", (W, H))\n    draw = ImageDraw.Draw(im)\n    expected = \"Tests/images/imagedraw_pieslice_width_fill.png\"\n\n    # Act\n    draw.pieslice(bbox, 10, 260, fill=\"white\", outline=\"blue\", width=5)\n\n    # Assert\n    assert_image_similar_tofile(im, expected, 1)\n\n\n@pytest.mark.parametrize(\"bbox\", BBOX)\ndef test_pieslice_zero_width(bbox: Coords) -> None:\n    # Arrange\n    im = Image.new(\"RGB\", (W, H))\n    draw = ImageDraw.Draw(im)\n\n    # Act\n    draw.pieslice(bbox, 10, 260, fill=\"white\", outline=\"blue\", width=0)\n\n    # Assert\n    assert_image_equal_tofile(im, \"Tests/images/imagedraw_pieslice_zero_width.png\")\n\n\ndef test_pieslice_wide() -> None:\n    # Arrange\n    im = Image.new(\"RGB\", (200, 100))\n    draw = ImageDraw.Draw(im)\n\n    # Act\n    draw.pieslice([0, 0, 199, 99], 190, 170, width=10, fill=\"white\", outline=\"red\")\n\n    # Assert\n    assert_image_equal_tofile(im, \"Tests/images/imagedraw_pieslice_wide.png\")\n\n\ndef test_pieslice_no_spikes() -> None:\n    im = Image.new(\"RGB\", (161, 161), \"white\")\n    draw = ImageDraw.Draw(im)\n    cxs = (\n        [140] * 3\n        + list(range(140, 19, -20))\n        + [20] * 5\n        + list(range(20, 141, 20))\n        + [140] * 2\n    )\n    cys = (\n        list(range(80, 141, 20))\n        + [140] * 5\n        + list(range(140, 19, -20))\n        + [20] * 5\n        + list(range(20, 80, 20))\n    )\n\n    for cx, cy, angle in zip(cxs, cys, range(0, 360, 15)):\n        draw.pieslice(\n            [cx - 100, cy - 100, cx + 100, cy + 100], angle, angle + 1, fill=\"black\"\n        )\n        draw.point([cx, cy], fill=\"red\")\n\n    im_pre_erase = im.copy()\n    draw.rectangle([21, 21, 139, 139], fill=\"white\")\n\n    assert_image_equal(im, im_pre_erase)\n\n\n@pytest.mark.parametrize(\"points\", POINTS)\ndef test_point(points: Coords) -> None:\n    # Arrange\n    im = Image.new(\"RGB\", (W, H))\n    draw = ImageDraw.Draw(im)\n\n    # Act\n    draw.point(points, fill=\"yellow\")\n\n    # Assert\n    assert_image_equal_tofile(im, \"Tests/images/imagedraw_point.png\")\n\n\ndef test_point_I16() -> None:\n    # Arrange\n    im = Image.new(\"I;16\", (1, 1))\n    draw = ImageDraw.Draw(im)\n\n    # Act\n    draw.point((0, 0), fill=0x1234)\n\n    # Assert\n    assert im.getpixel((0, 0)) == 0x1234\n\n\n@pytest.mark.parametrize(\"points\", POINTS)\ndef test_polygon(points: Coords) -> None:\n    # Arrange\n    im = Image.new(\"RGB\", (W, H))\n    draw = ImageDraw.Draw(im)\n\n    # Act\n    draw.polygon(points, fill=\"red\", outline=\"blue\")\n\n    # Assert\n    assert_image_equal_tofile(im, \"Tests/images/imagedraw_polygon.png\")\n\n\n@pytest.mark.parametrize(\"mode\", (\"RGB\", \"L\"))\n@pytest.mark.parametrize(\"kite_points\", KITE_POINTS)\ndef test_polygon_kite(\n    mode: str, kite_points: tuple[tuple[int, int], ...] | list[tuple[int, int]]\n) -> None:\n    # Test drawing lines of different gradients (dx>dy, dy>dx) and\n    # vertical (dx==0) and horizontal (dy==0) lines\n    # Arrange\n    im = Image.new(mode, (W, H))\n    draw = ImageDraw.Draw(im)\n    expected = f\"Tests/images/imagedraw_polygon_kite_{mode}.png\"\n\n    # Act\n    draw.polygon(kite_points, fill=\"blue\", outline=\"yellow\")\n\n    # Assert\n    assert_image_equal_tofile(im, expected)\n\n\ndef test_polygon_1px_high() -> None:\n    # Test drawing a 1px high polygon\n    # Arrange\n    im = Image.new(\"RGB\", (3, 3))\n    draw = ImageDraw.Draw(im)\n    expected = \"Tests/images/imagedraw_polygon_1px_high.png\"\n\n    # Act\n    draw.polygon([(0, 1), (0, 1), (2, 1), (2, 1)], \"#f00\")\n\n    # Assert\n    assert_image_equal_tofile(im, expected)\n\n\ndef test_polygon_1px_high_translucent() -> None:\n    # Test drawing a translucent 1px high polygon\n    # Arrange\n    im = Image.new(\"RGB\", (4, 3))\n    draw = ImageDraw.Draw(im, \"RGBA\")\n    expected = \"Tests/images/imagedraw_polygon_1px_high_translucent.png\"\n\n    # Act\n    draw.polygon([(1, 1), (1, 1), (3, 1), (3, 1)], (255, 0, 0, 127))\n\n    # Assert\n    assert_image_equal_tofile(im, expected)\n\n\ndef test_polygon_translucent() -> None:\n    # Arrange\n    im = Image.new(\"RGB\", (W, H))\n    draw = ImageDraw.Draw(im, \"RGBA\")\n\n    # Act\n    draw.polygon([(20, 80), (80, 80), (80, 20)], fill=(0, 255, 0, 127))\n\n    # Assert\n    expected = \"Tests/images/imagedraw_polygon_translucent.png\"\n    assert_image_equal_tofile(im, expected)\n\n\n@pytest.mark.parametrize(\"bbox\", BBOX)\ndef test_rectangle(bbox: Coords) -> None:\n    # Arrange\n    im = Image.new(\"RGB\", (W, H))\n    draw = ImageDraw.Draw(im)\n\n    # Act\n    draw.rectangle(bbox, fill=\"black\", outline=\"green\")\n\n    # Assert\n    assert_image_equal_tofile(im, \"Tests/images/imagedraw_rectangle.png\")\n\n\ndef test_big_rectangle() -> None:\n    # Test drawing a rectangle bigger than the image\n    # Arrange\n    im = Image.new(\"RGB\", (W, H))\n    bbox = [(-1, -1), (W + 1, H + 1)]\n    draw = ImageDraw.Draw(im)\n\n    # Act\n    draw.rectangle(bbox, fill=\"orange\")\n\n    # Assert\n    assert_image_similar_tofile(im, \"Tests/images/imagedraw_big_rectangle.png\", 1)\n\n\n@pytest.mark.parametrize(\"bbox\", BBOX)\ndef test_rectangle_width(bbox: Coords) -> None:\n    # Arrange\n    im = Image.new(\"RGB\", (W, H))\n    draw = ImageDraw.Draw(im)\n    expected = \"Tests/images/imagedraw_rectangle_width.png\"\n\n    # Act\n    draw.rectangle(bbox, outline=\"green\", width=5)\n\n    # Assert\n    assert_image_equal_tofile(im, expected)\n\n\n@pytest.mark.parametrize(\"bbox\", BBOX)\ndef test_rectangle_width_fill(bbox: Coords) -> None:\n    # Arrange\n    im = Image.new(\"RGB\", (W, H))\n    draw = ImageDraw.Draw(im)\n    expected = \"Tests/images/imagedraw_rectangle_width_fill.png\"\n\n    # Act\n    draw.rectangle(bbox, fill=\"blue\", outline=\"green\", width=5)\n\n    # Assert\n    assert_image_equal_tofile(im, expected)\n\n\n@pytest.mark.parametrize(\"bbox\", BBOX)\ndef test_rectangle_zero_width(bbox: Coords) -> None:\n    # Arrange\n    im = Image.new(\"RGB\", (W, H))\n    draw = ImageDraw.Draw(im)\n\n    # Act\n    draw.rectangle(bbox, fill=\"blue\", outline=\"green\", width=0)\n\n    # Assert\n    assert_image_equal_tofile(im, \"Tests/images/imagedraw_rectangle_zero_width.png\")\n\n\n@pytest.mark.parametrize(\"bbox\", BBOX)\ndef test_rectangle_I16(bbox: Coords) -> None:\n    # Arrange\n    im = Image.new(\"I;16\", (W, H))\n    draw = ImageDraw.Draw(im)\n\n    # Act\n    draw.rectangle(bbox, outline=0xFFFF)\n\n    # Assert\n    assert_image_equal_tofile(im, \"Tests/images/imagedraw_rectangle_I.tiff\")\n\n\n@pytest.mark.parametrize(\"bbox\", BBOX)\ndef test_rectangle_translucent_outline(bbox: Coords) -> None:\n    # Arrange\n    im = Image.new(\"RGB\", (W, H))\n    draw = ImageDraw.Draw(im, \"RGBA\")\n\n    # Act\n    draw.rectangle(bbox, fill=\"black\", outline=(0, 255, 0, 127), width=5)\n\n    # Assert\n    assert_image_equal_tofile(\n        im, \"Tests/images/imagedraw_rectangle_translucent_outline.png\"\n    )\n\n\n@pytest.mark.parametrize(\n    \"xy\",\n    [(10, 20, 190, 180), ([10, 20], [190, 180]), ((10, 20), (190, 180))],\n)\ndef test_rounded_rectangle(\n    xy: (\n        tuple[int, int, int, int]\n        | tuple[list[int]]\n        | tuple[tuple[int, int], tuple[int, int]]\n    )\n) -> None:\n    # Arrange\n    im = Image.new(\"RGB\", (200, 200))\n    draw = ImageDraw.Draw(im)\n\n    # Act\n    draw.rounded_rectangle(xy, 30, fill=\"red\", outline=\"green\", width=5)\n\n    # Assert\n    assert_image_equal_tofile(im, \"Tests/images/imagedraw_rounded_rectangle.png\")\n\n\n@pytest.mark.parametrize(\"top_left\", (True, False))\n@pytest.mark.parametrize(\"top_right\", (True, False))\n@pytest.mark.parametrize(\"bottom_right\", (True, False))\n@pytest.mark.parametrize(\"bottom_left\", (True, False))\ndef test_rounded_rectangle_corners(\n    top_left: bool, top_right: bool, bottom_right: bool, bottom_left: bool\n) -> None:\n    corners = (top_left, top_right, bottom_right, bottom_left)\n\n    # Arrange\n    im = Image.new(\"RGB\", (200, 200))\n    draw = ImageDraw.Draw(im)\n\n    # Act\n    draw.rounded_rectangle(\n        (10, 20, 190, 180), 30, fill=\"red\", outline=\"green\", width=5, corners=corners\n    )\n\n    # Assert\n    suffix = \"\".join(\n        (\n            (\"y\" if top_left else \"n\"),\n            (\"y\" if top_right else \"n\"),\n            (\"y\" if bottom_right else \"n\"),\n            (\"y\" if bottom_left else \"n\"),\n        )\n    )\n    assert_image_equal_tofile(\n        im, \"Tests/images/imagedraw_rounded_rectangle_corners_\" + suffix + \".png\"\n    )\n\n\n@pytest.mark.parametrize(\n    \"xy, radius, type\",\n    [\n        ((10, 20, 190, 180), 30.5, \"given\"),\n        ((10, 10, 181, 190), 90, \"width\"),\n        ((10, 20, 190, 181), 85, \"height\"),\n    ],\n)\ndef test_rounded_rectangle_non_integer_radius(\n    xy: tuple[int, int, int, int], radius: float, type: str\n) -> None:\n    # Arrange\n    im = Image.new(\"RGB\", (200, 200))\n    draw = ImageDraw.Draw(im)\n\n    # Act\n    draw.rounded_rectangle(xy, radius, fill=\"red\", outline=\"green\", width=5)\n\n    # Assert\n    assert_image_equal_tofile(\n        im,\n        \"Tests/images/imagedraw_rounded_rectangle_non_integer_radius_\" + type + \".png\",\n    )\n\n\n@pytest.mark.parametrize(\"bbox\", BBOX)\ndef test_rounded_rectangle_zero_radius(bbox: Coords) -> None:\n    # Arrange\n    im = Image.new(\"RGB\", (W, H))\n    draw = ImageDraw.Draw(im)\n\n    # Act\n    draw.rounded_rectangle(bbox, 0, fill=\"blue\", outline=\"green\", width=5)\n\n    # Assert\n    assert_image_equal_tofile(im, \"Tests/images/imagedraw_rectangle_width_fill.png\")\n\n\n@pytest.mark.parametrize(\n    \"xy, suffix\",\n    [\n        ((20, 10, 80, 90), \"x\"),\n        ((20, 10, 81, 90), \"x_odd\"),\n        ((20, 10, 81.1, 90), \"x_odd\"),\n        ((10, 20, 90, 80), \"y\"),\n        ((10, 20, 90, 81), \"y_odd\"),\n        ((10, 20, 90, 81.1), \"y_odd\"),\n        ((20, 20, 80, 80), \"both\"),\n    ],\n)\ndef test_rounded_rectangle_translucent(\n    xy: tuple[int, int, int, int], suffix: str\n) -> None:\n    # Arrange\n    im = Image.new(\"RGB\", (W, H))\n    draw = ImageDraw.Draw(im, \"RGBA\")\n\n    # Act\n    draw.rounded_rectangle(\n        xy, 30, fill=(255, 0, 0, 127), outline=(0, 255, 0, 127), width=5\n    )\n\n    # Assert\n    assert_image_equal_tofile(\n        im, \"Tests/images/imagedraw_rounded_rectangle_\" + suffix + \".png\"\n    )\n\n\n@pytest.mark.parametrize(\"bbox\", BBOX)\ndef test_floodfill(bbox: Coords) -> None:\n    red = ImageColor.getrgb(\"red\")\n\n    for mode, value in [(\"L\", 1), (\"RGBA\", (255, 0, 0, 0)), (\"RGB\", red)]:\n        # Arrange\n        im = Image.new(mode, (W, H))\n        draw = ImageDraw.Draw(im)\n        draw.rectangle(bbox, outline=\"yellow\", fill=\"green\")\n        centre_point = (int(W / 2), int(H / 2))\n\n        # Act\n        ImageDraw.floodfill(im, centre_point, value)\n\n        # Assert\n        expected = \"Tests/images/imagedraw_floodfill_\" + mode + \".png\"\n        with Image.open(expected) as im_floodfill:\n            assert_image_equal(im, im_floodfill)\n\n    # Test that using the same colour does not change the image\n    ImageDraw.floodfill(im, centre_point, red)\n    assert_image_equal(im, im_floodfill)\n\n    # Test that filling outside the image does not change the image\n    ImageDraw.floodfill(im, (W, H), red)\n    assert_image_equal(im, im_floodfill)\n\n    # Test filling at the edge of an image\n    im = Image.new(\"RGB\", (1, 1))\n    ImageDraw.floodfill(im, (0, 0), red)\n    assert_image_equal(im, Image.new(\"RGB\", (1, 1), red))\n\n\n@pytest.mark.parametrize(\"bbox\", BBOX)\ndef test_floodfill_border(bbox: Coords) -> None:\n    # floodfill() is experimental\n\n    # Arrange\n    im = Image.new(\"RGB\", (W, H))\n    draw = ImageDraw.Draw(im)\n    draw.rectangle(bbox, outline=\"yellow\", fill=\"green\")\n    centre_point = (int(W / 2), int(H / 2))\n\n    # Act\n    ImageDraw.floodfill(\n        im,\n        centre_point,\n        ImageColor.getrgb(\"red\"),\n        border=ImageColor.getrgb(\"black\"),\n    )\n\n    # Assert\n    assert_image_equal_tofile(im, \"Tests/images/imagedraw_floodfill2.png\")\n\n\n@pytest.mark.parametrize(\"bbox\", BBOX)\ndef test_floodfill_thresh(bbox: Coords) -> None:\n    # floodfill() is experimental\n\n    # Arrange\n    im = Image.new(\"RGB\", (W, H))\n    draw = ImageDraw.Draw(im)\n    draw.rectangle(bbox, outline=\"darkgreen\", fill=\"green\")\n    centre_point = (int(W / 2), int(H / 2))\n\n    # Act\n    ImageDraw.floodfill(im, centre_point, ImageColor.getrgb(\"red\"), thresh=30)\n\n    # Assert\n    assert_image_equal_tofile(im, \"Tests/images/imagedraw_floodfill2.png\")\n\n\ndef test_floodfill_not_negative() -> None:\n    # floodfill() is experimental\n    # Test that floodfill does not extend into negative coordinates\n\n    # Arrange\n    im = Image.new(\"RGB\", (W, H))\n    draw = ImageDraw.Draw(im)\n    draw.line((W / 2, 0, W / 2, H / 2), fill=\"green\")\n    draw.line((0, H / 2, W / 2, H / 2), fill=\"green\")\n\n    # Act\n    ImageDraw.floodfill(im, (int(W / 4), int(H / 4)), ImageColor.getrgb(\"red\"))\n\n    # Assert\n    assert_image_equal_tofile(im, \"Tests/images/imagedraw_floodfill_not_negative.png\")\n\n\ndef create_base_image_draw(\n    size: tuple[int, int],\n    mode: str = DEFAULT_MODE,\n    background1: tuple[int, int, int] = WHITE,\n    background2: tuple[int, int, int] = GRAY,\n) -> tuple[Image.Image, ImageDraw.ImageDraw]:\n    img = Image.new(mode, size, background1)\n    for x in range(0, size[0]):\n        for y in range(0, size[1]):\n            if (x + y) % 2 == 0:\n                img.putpixel((x, y), background2)\n    return img, ImageDraw.Draw(img)\n\n\ndef test_square() -> None:\n    expected = os.path.join(IMAGES_PATH, \"square.png\")\n    img, draw = create_base_image_draw((10, 10))\n    draw.polygon([(2, 2), (2, 7), (7, 7), (7, 2)], BLACK)\n    assert_image_equal_tofile(img, expected, \"square as normal polygon failed\")\n    img, draw = create_base_image_draw((10, 10))\n    draw.polygon([(7, 7), (7, 2), (2, 2), (2, 7)], BLACK)\n    assert_image_equal_tofile(img, expected, \"square as inverted polygon failed\")\n    img, draw = create_base_image_draw((10, 10))\n    draw.rectangle((2, 2, 7, 7), BLACK)\n    assert_image_equal_tofile(img, expected, \"square as normal rectangle failed\")\n\n\ndef test_triangle_right() -> None:\n    img, draw = create_base_image_draw((20, 20))\n    draw.polygon([(3, 5), (17, 5), (10, 12)], BLACK)\n    assert_image_equal_tofile(\n        img, os.path.join(IMAGES_PATH, \"triangle_right.png\"), \"triangle right failed\"\n    )\n\n\n@pytest.mark.parametrize(\n    \"fill, suffix\",\n    ((BLACK, \"width\"), (None, \"width_no_fill\")),\n)\ndef test_triangle_right_width(fill: tuple[int, int, int] | None, suffix: str) -> None:\n    img, draw = create_base_image_draw((100, 100))\n    draw.polygon([(15, 25), (85, 25), (50, 60)], fill, WHITE, width=5)\n    assert_image_equal_tofile(\n        img, os.path.join(IMAGES_PATH, \"triangle_right_\" + suffix + \".png\")\n    )\n\n\ndef test_line_horizontal() -> None:\n    img, draw = create_base_image_draw((20, 20))\n    draw.line((5, 5, 14, 5), BLACK, 2)\n    assert_image_equal_tofile(\n        img,\n        os.path.join(IMAGES_PATH, \"line_horizontal_w2px_normal.png\"),\n        \"line straight horizontal normal 2px wide failed\",\n    )\n\n    img, draw = create_base_image_draw((20, 20))\n    draw.line((14, 5, 5, 5), BLACK, 2)\n    assert_image_equal_tofile(\n        img,\n        os.path.join(IMAGES_PATH, \"line_horizontal_w2px_inverted.png\"),\n        \"line straight horizontal inverted 2px wide failed\",\n    )\n\n    expected = os.path.join(IMAGES_PATH, \"line_horizontal_w3px.png\")\n    img, draw = create_base_image_draw((20, 20))\n    draw.line((5, 5, 14, 5), BLACK, 3)\n    assert_image_equal_tofile(\n        img, expected, \"line straight horizontal normal 3px wide failed\"\n    )\n    img, draw = create_base_image_draw((20, 20))\n    draw.line((14, 5, 5, 5), BLACK, 3)\n    assert_image_equal_tofile(\n        img, expected, \"line straight horizontal inverted 3px wide failed\"\n    )\n\n    img, draw = create_base_image_draw((200, 110))\n    draw.line((5, 55, 195, 55), BLACK, 101)\n    assert_image_equal_tofile(\n        img,\n        os.path.join(IMAGES_PATH, \"line_horizontal_w101px.png\"),\n        \"line straight horizontal 101px wide failed\",\n    )\n\n\ndef test_line_h_s1_w2() -> None:\n    pytest.skip(\"failing\")\n    img, draw = create_base_image_draw((20, 20))\n    draw.line((5, 5, 14, 6), BLACK, 2)\n    assert_image_equal_tofile(\n        img,\n        os.path.join(IMAGES_PATH, \"line_horizontal_slope1px_w2px.png\"),\n        \"line horizontal 1px slope 2px wide failed\",\n    )\n\n\ndef test_line_vertical() -> None:\n    img, draw = create_base_image_draw((20, 20))\n    draw.line((5, 5, 5, 14), BLACK, 2)\n    assert_image_equal_tofile(\n        img,\n        os.path.join(IMAGES_PATH, \"line_vertical_w2px_normal.png\"),\n        \"line straight vertical normal 2px wide failed\",\n    )\n\n    img, draw = create_base_image_draw((20, 20))\n    draw.line((5, 14, 5, 5), BLACK, 2)\n    assert_image_equal_tofile(\n        img,\n        os.path.join(IMAGES_PATH, \"line_vertical_w2px_inverted.png\"),\n        \"line straight vertical inverted 2px wide failed\",\n    )\n\n    expected = os.path.join(IMAGES_PATH, \"line_vertical_w3px.png\")\n    img, draw = create_base_image_draw((20, 20))\n    draw.line((5, 5, 5, 14), BLACK, 3)\n    assert_image_equal_tofile(\n        img, expected, \"line straight vertical normal 3px wide failed\"\n    )\n    img, draw = create_base_image_draw((20, 20))\n    draw.line((5, 14, 5, 5), BLACK, 3)\n    assert_image_equal_tofile(\n        img, expected, \"line straight vertical inverted 3px wide failed\"\n    )\n\n    img, draw = create_base_image_draw((110, 200))\n    draw.line((55, 5, 55, 195), BLACK, 101)\n    assert_image_equal_tofile(\n        img,\n        os.path.join(IMAGES_PATH, \"line_vertical_w101px.png\"),\n        \"line straight vertical 101px wide failed\",\n    )\n\n    img, draw = create_base_image_draw((20, 20))\n    draw.line((5, 5, 6, 14), BLACK, 2)\n    assert_image_equal_tofile(\n        img,\n        os.path.join(IMAGES_PATH, \"line_vertical_slope1px_w2px.png\"),\n        \"line vertical 1px slope 2px wide failed\",\n    )\n\n\ndef test_line_oblique_45() -> None:\n    expected = os.path.join(IMAGES_PATH, \"line_oblique_45_w3px_a.png\")\n    img, draw = create_base_image_draw((20, 20))\n    draw.line((5, 5, 14, 14), BLACK, 3)\n    assert_image_equal_tofile(img, expected, \"line oblique 45 normal 3px wide A failed\")\n    img, draw = create_base_image_draw((20, 20))\n    draw.line((14, 14, 5, 5), BLACK, 3)\n    assert_image_equal_tofile(\n        img, expected, \"line oblique 45 inverted 3px wide A failed\"\n    )\n\n    expected = os.path.join(IMAGES_PATH, \"line_oblique_45_w3px_b.png\")\n    img, draw = create_base_image_draw((20, 20))\n    draw.line((14, 5, 5, 14), BLACK, 3)\n    assert_image_equal_tofile(img, expected, \"line oblique 45 normal 3px wide B failed\")\n    img, draw = create_base_image_draw((20, 20))\n    draw.line((5, 14, 14, 5), BLACK, 3)\n    assert_image_equal_tofile(\n        img, expected, \"line oblique 45 inverted 3px wide B failed\"\n    )\n\n\ndef test_wide_line_dot() -> None:\n    # Test drawing a wide \"line\" from one point to another just draws a single point\n    # Arrange\n    im = Image.new(\"RGB\", (W, H))\n    draw = ImageDraw.Draw(im)\n\n    # Act\n    draw.line([(50, 50), (50, 50)], width=3)\n\n    # Assert\n    assert_image_similar_tofile(im, \"Tests/images/imagedraw_wide_line_dot.png\", 1)\n\n\ndef test_wide_line_larger_than_int() -> None:\n    # Arrange\n    im = Image.new(\"RGB\", (W, H))\n    draw = ImageDraw.Draw(im)\n    expected = \"Tests/images/imagedraw_wide_line_larger_than_int.png\"\n\n    # Act\n    draw.line([(0, 0), (32768, 32768)], width=3)\n\n    # Assert\n    assert_image_similar_tofile(im, expected, 1)\n\n\n@pytest.mark.parametrize(\n    \"xy\",\n    [\n        [\n            (400, 280),\n            (380, 280),\n            (450, 280),\n            (440, 120),\n            (350, 200),\n            (310, 280),\n            (300, 280),\n            (250, 280),\n            (250, 200),\n            (150, 200),\n            (150, 260),\n            (50, 200),\n            (150, 50),\n            (250, 100),\n        ],\n        (\n            400,\n            280,\n            380,\n            280,\n            450,\n            280,\n            440,\n            120,\n            350,\n            200,\n            310,\n            280,\n            300,\n            280,\n            250,\n            280,\n            250,\n            200,\n            150,\n            200,\n            150,\n            260,\n            50,\n            200,\n            150,\n            50,\n            250,\n            100,\n        ),\n        [\n            400,\n            280,\n            380,\n            280,\n            450,\n            280,\n            440,\n            120,\n            350,\n            200,\n            310,\n            280,\n            300,\n            280,\n            250,\n            280,\n            250,\n            200,\n            150,\n            200,\n            150,\n            260,\n            50,\n            200,\n            150,\n            50,\n            250,\n            100,\n        ],\n    ],\n)\ndef test_line_joint(xy: list[tuple[int, int]] | tuple[int, ...] | list[int]) -> None:\n    im = Image.new(\"RGB\", (500, 325))\n    draw = ImageDraw.Draw(im)\n\n    # Act\n    draw.line(xy, GRAY, 50, \"curve\")\n\n    # Assert\n    assert_image_similar_tofile(im, \"Tests/images/imagedraw_line_joint_curve.png\", 3)\n\n\ndef test_textsize_empty_string() -> None:\n    # https://github.com/python-pillow/Pillow/issues/2783\n    # Arrange\n    im = Image.new(\"RGB\", (W, H))\n    draw = ImageDraw.Draw(im)\n\n    # Act\n    # Should not cause 'SystemError: <built-in method getsize of\n    # ImagingFont object at 0x...> returned NULL without setting an error'\n    draw.textbbox((0, 0), \"\")\n    draw.textbbox((0, 0), \"\\n\")\n    draw.textbbox((0, 0), \"test\\n\")\n    draw.textlength(\"\")\n\n\n@skip_unless_feature(\"freetype2\")\ndef test_textbbox_stroke() -> None:\n    # Arrange\n    im = Image.new(\"RGB\", (W, H))\n    draw = ImageDraw.Draw(im)\n    font = ImageFont.truetype(\"Tests/fonts/FreeMono.ttf\", 20)\n\n    # Act / Assert\n    assert draw.textbbox((2, 2), \"A\", font, stroke_width=2) == (0, 4, 16, 20)\n    assert draw.textbbox((2, 2), \"A\", font, stroke_width=4) == (-2, 2, 18, 22)\n    assert draw.textbbox((2, 2), \"ABC\\nAaaa\", font, stroke_width=2) == (0, 4, 52, 44)\n    assert draw.textbbox((2, 2), \"ABC\\nAaaa\", font, stroke_width=4) == (-2, 2, 54, 50)\n\n\n@skip_unless_feature(\"freetype2\")\ndef test_stroke() -> None:\n    for suffix, stroke_fill in {\"same\": None, \"different\": \"#0f0\"}.items():\n        # Arrange\n        im = Image.new(\"RGB\", (120, 130))\n        draw = ImageDraw.Draw(im)\n        font = ImageFont.truetype(\"Tests/fonts/FreeMono.ttf\", 120)\n\n        # Act\n        draw.text((12, 12), \"A\", \"#f00\", font, stroke_width=2, stroke_fill=stroke_fill)\n\n        # Assert\n        assert_image_similar_tofile(\n            im, \"Tests/images/imagedraw_stroke_\" + suffix + \".png\", 3.1\n        )\n\n\n@skip_unless_feature(\"freetype2\")\ndef test_stroke_descender() -> None:\n    # Arrange\n    im = Image.new(\"RGB\", (120, 130))\n    draw = ImageDraw.Draw(im)\n    font = ImageFont.truetype(\"Tests/fonts/FreeMono.ttf\", 120)\n\n    # Act\n    draw.text((12, 2), \"y\", \"#f00\", font, stroke_width=2, stroke_fill=\"#0f0\")\n\n    # Assert\n    assert_image_similar_tofile(im, \"Tests/images/imagedraw_stroke_descender.png\", 6.76)\n\n\n@skip_unless_feature(\"freetype2\")\ndef test_split_word() -> None:\n    # Arrange\n    im = Image.new(\"RGB\", (230, 55))\n    expected = im.copy()\n    expected_draw = ImageDraw.Draw(expected)\n    font = ImageFont.truetype(\"Tests/fonts/FreeMono.ttf\", 48)\n    expected_draw.text((0, 0), \"paradise\", font=font)\n\n    draw = ImageDraw.Draw(im)\n\n    # Act\n    draw.text((0, 0), \"par\", font=font)\n\n    length = draw.textlength(\"par\", font=font)\n    draw.text((length, 0), \"adise\", font=font)\n\n    # Assert\n    assert_image_equal(im, expected)\n\n\n@skip_unless_feature(\"freetype2\")\ndef test_stroke_multiline() -> None:\n    # Arrange\n    im = Image.new(\"RGB\", (100, 250))\n    draw = ImageDraw.Draw(im)\n    font = ImageFont.truetype(\"Tests/fonts/FreeMono.ttf\", 120)\n\n    # Act\n    draw.multiline_text(\n        (12, 12), \"A\\nB\", \"#f00\", font, stroke_width=2, stroke_fill=\"#0f0\"\n    )\n\n    # Assert\n    assert_image_similar_tofile(im, \"Tests/images/imagedraw_stroke_multiline.png\", 3.3)\n\n\n@skip_unless_feature(\"freetype2\")\ndef test_setting_default_font() -> None:\n    # Arrange\n    im = Image.new(\"RGB\", (100, 250))\n    draw = ImageDraw.Draw(im)\n    font = ImageFont.truetype(\"Tests/fonts/FreeMono.ttf\", 120)\n\n    # Act\n    ImageDraw.ImageDraw.font = font\n\n    # Assert\n    try:\n        assert draw.getfont() == font\n    finally:\n        ImageDraw.ImageDraw.font = None\n        assert isinstance(draw.getfont(), ImageFont.load_default().__class__)\n\n\ndef test_default_font_size() -> None:\n    freetype_support = features.check_module(\"freetype2\")\n    text = \"Default font at a specific size.\"\n\n    im = Image.new(\"RGB\", (220, 25))\n    draw = ImageDraw.Draw(im)\n    with contextlib.nullcontext() if freetype_support else pytest.raises(ImportError):\n        draw.text((0, 0), text, font_size=16)\n        assert_image_equal_tofile(im, \"Tests/images/imagedraw_default_font_size.png\")\n\n    with contextlib.nullcontext() if freetype_support else pytest.raises(ImportError):\n        assert draw.textlength(text, font_size=16) == 216\n\n    with contextlib.nullcontext() if freetype_support else pytest.raises(ImportError):\n        assert draw.textbbox((0, 0), text, font_size=16) == (0, 3, 216, 19)\n\n    im = Image.new(\"RGB\", (220, 25))\n    draw = ImageDraw.Draw(im)\n    with contextlib.nullcontext() if freetype_support else pytest.raises(ImportError):\n        draw.multiline_text((0, 0), text, font_size=16)\n        assert_image_equal_tofile(im, \"Tests/images/imagedraw_default_font_size.png\")\n\n    with contextlib.nullcontext() if freetype_support else pytest.raises(ImportError):\n        assert draw.multiline_textbbox((0, 0), text, font_size=16) == (0, 3, 216, 19)\n\n\n@pytest.mark.parametrize(\"bbox\", BBOX)\ndef test_same_color_outline(bbox: Coords) -> None:\n    # Prepare shape\n    x0, y0 = 5, 5\n    x1, y1 = 5, 50\n    x2, y2 = 95, 50\n    x3, y3 = 95, 5\n\n    s = ImageDraw.Outline()\n    s.move(x0, y0)\n    s.curve(x1, y1, x2, y2, x3, y3)\n    s.line(x0, y0)\n\n    # Begin\n    for mode in [\"RGB\", \"L\"]:\n        fill = \"red\"\n        for outline in [None, \"red\", \"#f00\"]:\n            for operation, args in {\n                \"chord\": [bbox, 0, 180],\n                \"ellipse\": [bbox],\n                \"shape\": [s],\n                \"pieslice\": [bbox, -90, 45],\n                \"polygon\": [[(18, 30), (85, 30), (60, 72)]],\n                \"rectangle\": [bbox],\n            }.items():\n                # Arrange\n                im = Image.new(mode, (W, H))\n                draw = ImageDraw.Draw(im)\n\n                # Act\n                draw_method = getattr(draw, operation)\n                assert isinstance(args, list)\n                args += [fill, outline]\n                draw_method(*args)\n\n                # Assert\n                expected = f\"Tests/images/imagedraw_outline_{operation}_{mode}.png\"\n                assert_image_similar_tofile(im, expected, 1)\n\n\n@pytest.mark.parametrize(\n    \"n_sides, polygon_name, args\",\n    [\n        (4, \"square\", {}),\n        (8, \"regular_octagon\", {}),\n        (4, \"square_rotate_45\", {\"rotation\": 45}),\n        (3, \"triangle_width\", {\"width\": 5, \"outline\": \"yellow\"}),\n    ],\n)\ndef test_draw_regular_polygon(\n    n_sides: int, polygon_name: str, args: dict[str, int | str]\n) -> None:\n    im = Image.new(\"RGBA\", size=(W, H), color=(255, 0, 0, 0))\n    filename = f\"Tests/images/imagedraw_{polygon_name}.png\"\n    draw = ImageDraw.Draw(im)\n    bounding_circle = ((W // 2, H // 2), 25)\n    draw.regular_polygon(bounding_circle, n_sides, fill=\"red\", **args)\n    assert_image_equal_tofile(im, filename)\n\n\n@pytest.mark.parametrize(\n    \"n_sides, expected_vertices\",\n    [\n        (3, [(28.35, 62.5), (71.65, 62.5), (50.0, 25.0)]),\n        (4, [(32.32, 67.68), (67.68, 67.68), (67.68, 32.32), (32.32, 32.32)]),\n        (\n            5,\n            [\n                (35.31, 70.23),\n                (64.69, 70.23),\n                (73.78, 42.27),\n                (50.0, 25.0),\n                (26.22, 42.27),\n            ],\n        ),\n        (\n            6,\n            [\n                (37.5, 71.65),\n                (62.5, 71.65),\n                (75.0, 50.0),\n                (62.5, 28.35),\n                (37.5, 28.35),\n                (25.0, 50.0),\n            ],\n        ),\n    ],\n)\ndef test_compute_regular_polygon_vertices(\n    n_sides: int, expected_vertices: list[tuple[float, float]]\n) -> None:\n    bounding_circle = (W // 2, H // 2, 25)\n    vertices = ImageDraw._compute_regular_polygon_vertices(bounding_circle, n_sides, 0)\n    assert vertices == expected_vertices\n\n\n@pytest.mark.parametrize(\n    \"n_sides, bounding_circle, rotation, expected_error, error_message\",\n    [\n        (None, (50, 50, 25), 0, TypeError, \"n_sides should be an int\"),\n        (1, (50, 50, 25), 0, ValueError, \"n_sides should be an int > 2\"),\n        (3, 50, 0, TypeError, \"bounding_circle should be a sequence\"),\n        (\n            3,\n            (50, 50, 100, 100),\n            0,\n            ValueError,\n            \"bounding_circle should contain 2D coordinates \"\n            \"and a radius (e.g. (x, y, r) or ((x, y), r) )\",\n        ),\n        (\n            3,\n            (50, 50, \"25\"),\n            0,\n            ValueError,\n            \"bounding_circle should only contain numeric data\",\n        ),\n        (\n            3,\n            ((50, 50, 50), 25),\n            0,\n            ValueError,\n            \"bounding_circle centre should contain 2D coordinates (e.g. (x, y))\",\n        ),\n        (\n            3,\n            (50, 50, 0),\n            0,\n            ValueError,\n            \"bounding_circle radius should be > 0\",\n        ),\n        (\n            3,\n            (50, 50, 25),\n            \"0\",\n            ValueError,\n            \"rotation should be an int or float\",\n        ),\n    ],\n)\ndef test_compute_regular_polygon_vertices_input_error_handling(\n    n_sides, bounding_circle, rotation, expected_error, error_message\n) -> None:\n    with pytest.raises(expected_error) as e:\n        ImageDraw._compute_regular_polygon_vertices(bounding_circle, n_sides, rotation)\n    assert str(e.value) == error_message\n\n\ndef test_continuous_horizontal_edges_polygon() -> None:\n    xy = [\n        (2, 6),\n        (6, 6),\n        (12, 6),\n        (12, 12),\n        (8, 12),\n        (8, 8),\n        (4, 8),\n        (2, 8),\n    ]\n    img, draw = create_base_image_draw((16, 16))\n    draw.polygon(xy, BLACK)\n    expected = os.path.join(IMAGES_PATH, \"continuous_horizontal_edges_polygon.png\")\n    assert_image_equal_tofile(\n        img, expected, \"continuous horizontal edges polygon failed\"\n    )\n\n\ndef test_discontiguous_corners_polygon() -> None:\n    img, draw = create_base_image_draw((84, 68))\n    draw.polygon(((1, 21), (34, 4), (71, 1), (38, 18)), BLACK)\n    draw.polygon(((71, 44), (38, 27), (1, 24)), BLACK)\n    draw.polygon(\n        ((38, 66), (5, 49), (77, 49), (47, 66), (82, 63), (82, 47), (1, 47), (1, 63)),\n        BLACK,\n    )\n    expected = os.path.join(IMAGES_PATH, \"discontiguous_corners_polygon.png\")\n    assert_image_similar_tofile(img, expected, 1)\n\n\ndef test_polygon2() -> None:\n    im = Image.new(\"RGB\", (W, H))\n    draw = ImageDraw.Draw(im)\n    draw.polygon([(18, 30), (19, 31), (18, 30), (85, 30), (60, 72)], \"red\")\n    expected = \"Tests/images/imagedraw_outline_polygon_RGB.png\"\n    assert_image_similar_tofile(im, expected, 1)\n\n\n@pytest.mark.parametrize(\"xy\", ((1, 1, 0, 1), (1, 1, 1, 0)))\ndef test_incorrectly_ordered_coordinates(xy: tuple[int, int, int, int]) -> None:\n    im = Image.new(\"RGB\", (W, H))\n    draw = ImageDraw.Draw(im)\n    with pytest.raises(ValueError):\n        draw.arc(xy, 10, 260)\n    with pytest.raises(ValueError):\n        draw.chord(xy, 10, 260)\n    with pytest.raises(ValueError):\n        draw.ellipse(xy)\n    with pytest.raises(ValueError):\n        draw.pieslice(xy, 10, 260)\n    with pytest.raises(ValueError):\n        draw.rectangle(xy)\n    with pytest.raises(ValueError):\n        draw.rounded_rectangle(xy)\n"
    ],
    "test_file_names": [
        "test_1.py"
    ],
    "setup_res_code": 0,
    "setup_res_stdout": "Starting Pillow installation attempts...\nVerifying Pillow installation...\n✅ Pillow installation successful!\nSuccessfully installed Pillow\n",
    "setup_res_stderr": "warning: `VIRTUAL_ENV=/home/gcpuser/r2e-edits-internal/.venv` does not match the project environment path `.venv` and will be ignored\nUsing CPython 3.9.21\nCreating virtual environment at: .venv\nActivate with: source .venv/bin/activate\nResolved 12 packages in 17ms\nInstalled 12 packages in 197ms\n + coverage==7.6.10\n + exceptiongroup==1.2.2\n + iniconfig==2.0.0\n + packaging==24.2\n + pluggy==1.5.0\n + pyqt5==5.15.11\n + pyqt5-qt5==5.15.16\n + pyqt5-sip==12.16.1\n + pytest==8.3.4\n + pytest-cov==6.0.0\n + setuptools==75.8.0\n + tomli==2.2.1\nResolved 1 package in 878ms\nPrepared 1 package in 36.41s\nInstalled 1 package in 1ms\n + pillow==10.4.0.dev0 (from file:///home/gcpuser/buckets/local_repoeval_bucket/repos/pillow_fdf103c5ad2cb3fe710e16f2fa3019262d088818)\n",
    "new_commit_res_code": 0,
    "new_commit_res_stdout": "\u001b[1m============================= test session starts ==============================\u001b[0m\nplatform linux -- Python 3.9.21, pytest-8.3.4, pluggy-1.5.0\nrootdir: /home/gcpuser/buckets/local_repoeval_bucket/repos/pillow_fdf103c5ad2cb3fe710e16f2fa3019262d088818\nconfigfile: pyproject.toml\nplugins: cov-6.0.0\ncollected 253 items\n\nr2e_tests/test_1.py \u001b[32m.\u001b[0m\u001b[32m.\u001b[0m\u001b[32m.\u001b[0m\u001b[32m.\u001b[0m\u001b[32m.\u001b[0m\u001b[32m.\u001b[0m\u001b[32m.\u001b[0m\u001b[32m.\u001b[0m\u001b[32m.\u001b[0m\u001b[32m.\u001b[0m\u001b[32m.\u001b[0m\u001b[32m.\u001b[0m\u001b[32m.\u001b[0m\u001b[32m.\u001b[0m\u001b[32m.\u001b[0m\u001b[32m.\u001b[0m\u001b[32m.\u001b[0m\u001b[32m.\u001b[0m\u001b[32m.\u001b[0m\u001b[32m.\u001b[0m\u001b[32m.\u001b[0m\u001b[32m.\u001b[0m\u001b[32m.\u001b[0m\u001b[32m.\u001b[0m\u001b[32m.\u001b[0m\u001b[32m.\u001b[0m\u001b[32m.\u001b[0m\u001b[32m.\u001b[0m\u001b[32m.\u001b[0m\u001b[32m.\u001b[0m\u001b[32m.\u001b[0m\u001b[32m.\u001b[0m\u001b[32m.\u001b[0m\u001b[32m.\u001b[0m\u001b[32m.\u001b[0m\u001b[32m.\u001b[0m\u001b[32m.\u001b[0m\u001b[32m.\u001b[0m\u001b[32m.\u001b[0m\u001b[32m.\u001b[0m\u001b[32m.\u001b[0m\u001b[32m.\u001b[0m\u001b[32m.\u001b[0m\u001b[32m.\u001b[0m\u001b[32m.\u001b[0m\u001b[32m.\u001b[0m\u001b[32m.\u001b[0m\u001b[32m.\u001b[0m\u001b[32m.\u001b[0m\u001b[32m.\u001b[0m\u001b[32m.\u001b[0m\u001b[32m.\u001b[0m\u001b[32m [ 20%]\n\u001b[0m\u001b[32m.\u001b[0m\u001b[32m.\u001b[0m\u001b[32m.\u001b[0m\u001b[32m.\u001b[0m\u001b[32m.\u001b[0m\u001b[32m.\u001b[0m\u001b[32m.\u001b[0m\u001b[32m.\u001b[0m\u001b[32m.\u001b[0m\u001b[32m.\u001b[0m\u001b[32m.\u001b[0m\u001b[32m.\u001b[0m\u001b[32m.\u001b[0m\u001b[32m.\u001b[0m\u001b[32m.\u001b[0m\u001b[32m.\u001b[0m\u001b[32m.\u001b[0m\u001b[32m.\u001b[0m\u001b[32m.\u001b[0m\u001b[32m.\u001b[0m\u001b[32m.\u001b[0m\u001b[32m.\u001b[0m\u001b[32m.\u001b[0m\u001b[32m.\u001b[0m\u001b[32m.\u001b[0m\u001b[32m.\u001b[0m\u001b[32m.\u001b[0m\u001b[32m.\u001b[0m\u001b[32m.\u001b[0m\u001b[32m.\u001b[0m\u001b[32m.\u001b[0m\u001b[32m.\u001b[0m\u001b[32m.\u001b[0m\u001b[32m.\u001b[0m\u001b[32m.\u001b[0m\u001b[32m.\u001b[0m\u001b[32m.\u001b[0m\u001b[32m.\u001b[0m\u001b[32m.\u001b[0m\u001b[32m.\u001b[0m\u001b[32m.\u001b[0m\u001b[32m.\u001b[0m\u001b[32m.\u001b[0m\u001b[32m.\u001b[0m\u001b[32m.\u001b[0m\u001b[32m.\u001b[0m\u001b[32m.\u001b[0m\u001b[32m.\u001b[0m\u001b[32m.\u001b[0m\u001b[32m.\u001b[0m\u001b[32m.\u001b[0m\u001b[32m.\u001b[0m\u001b[32m.\u001b[0m\u001b[32m.\u001b[0m\u001b[32m.\u001b[0m\u001b[32m.\u001b[0m\u001b[32m.\u001b[0m\u001b[32m.\u001b[0m\u001b[32m.\u001b[0m\u001b[32m.\u001b[0m\u001b[32m.\u001b[0m\u001b[32m.\u001b[0m\u001b[32m.\u001b[0m\u001b[32m.\u001b[0m\u001b[32m.\u001b[0m\u001b[32m.\u001b[0m\u001b[32m.\u001b[0m\u001b[32m.\u001b[0m\u001b[32m.\u001b[0m\u001b[32m.\u001b[0m\u001b[32m.\u001b[0m\u001b[32m.\u001b[0m\u001b[32m [ 49%]\n\u001b[0m\u001b[32m.\u001b[0m\u001b[32m.\u001b[0m\u001b[32m.\u001b[0m\u001b[32m.\u001b[0m\u001b[32m.\u001b[0m\u001b[32m.\u001b[0m\u001b[32m.\u001b[0m\u001b[32m.\u001b[0m\u001b[32m.\u001b[0m\u001b[32m.\u001b[0m\u001b[32m.\u001b[0m\u001b[32m.\u001b[0m\u001b[32m.\u001b[0m\u001b[32m.\u001b[0m\u001b[32m.\u001b[0m\u001b[32m.\u001b[0m\u001b[32m.\u001b[0m\u001b[32m.\u001b[0m\u001b[32m.\u001b[0m\u001b[32m.\u001b[0m\u001b[32m.\u001b[0m\u001b[32m.\u001b[0m\u001b[32m.\u001b[0m\u001b[32m.\u001b[0m\u001b[32m.\u001b[0m\u001b[32m.\u001b[0m\u001b[32m.\u001b[0m\u001b[32m.\u001b[0m\u001b[32m.\u001b[0m\u001b[32m.\u001b[0m\u001b[32m.\u001b[0m\u001b[32m.\u001b[0m\u001b[32m.\u001b[0m\u001b[32m.\u001b[0m\u001b[32m.\u001b[0m\u001b[32m.\u001b[0m\u001b[32m.\u001b[0m\u001b[32m.\u001b[0m\u001b[32m.\u001b[0m\u001b[32m.\u001b[0m\u001b[32m.\u001b[0m\u001b[32m.\u001b[0m\u001b[32m.\u001b[0m\u001b[32m.\u001b[0m\u001b[32m.\u001b[0m\u001b[32m.\u001b[0m\u001b[32m.\u001b[0m\u001b[32m.\u001b[0m\u001b[32m.\u001b[0m\u001b[32m.\u001b[0m\u001b[32m.\u001b[0m\u001b[32m.\u001b[0m\u001b[32m.\u001b[0m\u001b[32m.\u001b[0m\u001b[32m.\u001b[0m\u001b[32m.\u001b[0m\u001b[32m.\u001b[0m\u001b[32m.\u001b[0m\u001b[32m.\u001b[0m\u001b[32m.\u001b[0m\u001b[32m.\u001b[0m\u001b[32m.\u001b[0m\u001b[32m.\u001b[0m\u001b[32m.\u001b[0m\u001b[32m.\u001b[0m\u001b[32m.\u001b[0m\u001b[32m.\u001b[0m\u001b[32m.\u001b[0m\u001b[32m.\u001b[0m\u001b[32m.\u001b[0m\u001b[32m.\u001b[0m\u001b[32m.\u001b[0m\u001b[32m [ 77%]\n\u001b[0m\u001b[32m.\u001b[0m\u001b[32m.\u001b[0m\u001b[32m.\u001b[0m\u001b[32m.\u001b[0m\u001b[32m.\u001b[0m\u001b[32m.\u001b[0m\u001b[32m.\u001b[0m\u001b[32m.\u001b[0m\u001b[32m.\u001b[0m\u001b[32m.\u001b[0m\u001b[32m.\u001b[0m\u001b[32m.\u001b[0m\u001b[32m.\u001b[0m\u001b[32m.\u001b[0m\u001b[32m.\u001b[0m\u001b[32m.\u001b[0m\u001b[33ms\u001b[0m\u001b[32m.\u001b[0m\u001b[32m.\u001b[0m\u001b[32m.\u001b[0m\u001b[32m.\u001b[0m\u001b[32m.\u001b[0m\u001b[32m.\u001b[0m\u001b[32m.\u001b[0m\u001b[32m.\u001b[0m\u001b[33ms\u001b[0m\u001b[33ms\u001b[0m\u001b[33ms\u001b[0m\u001b[33ms\u001b[0m\u001b[33ms\u001b[0m\u001b[33ms\u001b[0m\u001b[32m.\u001b[0m\u001b[32m.\u001b[0m\u001b[32m.\u001b[0m\u001b[32m.\u001b[0m\u001b[32m.\u001b[0m\u001b[32m.\u001b[0m\u001b[32m.\u001b[0m\u001b[32m.\u001b[0m\u001b[32m.\u001b[0m\u001b[32m.\u001b[0m\u001b[32m.\u001b[0m\u001b[32m.\u001b[0m\u001b[32m.\u001b[0m\u001b[32m.\u001b[0m\u001b[32m.\u001b[0m\u001b[32m.\u001b[0m\u001b[32m.\u001b[0m\u001b[32m.\u001b[0m\u001b[32m.\u001b[0m\u001b[32m.\u001b[0m\u001b[32m.\u001b[0m\u001b[32m.\u001b[0m\u001b[32m.\u001b[0m\u001b[32m.\u001b[0m\u001b[32m.\u001b[0m\u001b[32m.\u001b[0m\u001b[32m                [100%]\u001b[0m\n\n==================================== PASSES ====================================\n\u001b[36m\u001b[1m=========================== short test summary info ============================\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mtest_sanity\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mtest_valueerror\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mtest_mode_mismatch\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mtest_arc[0-180-bbox0]\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mtest_arc[0-180-bbox1]\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mtest_arc[0-180-bbox2]\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mtest_arc[0-180-bbox3]\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mtest_arc[0.5-180.4-bbox0]\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mtest_arc[0.5-180.4-bbox1]\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mtest_arc[0.5-180.4-bbox2]\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mtest_arc[0.5-180.4-bbox3]\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mtest_arc_end_le_start[bbox0]\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mtest_arc_end_le_start[bbox1]\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mtest_arc_end_le_start[bbox2]\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mtest_arc_end_le_start[bbox3]\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mtest_arc_no_loops[bbox0]\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mtest_arc_no_loops[bbox1]\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mtest_arc_no_loops[bbox2]\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mtest_arc_no_loops[bbox3]\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mtest_arc_width[bbox0]\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mtest_arc_width[bbox1]\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mtest_arc_width[bbox2]\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mtest_arc_width[bbox3]\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mtest_arc_width_pieslice_large[bbox0]\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mtest_arc_width_pieslice_large[bbox1]\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mtest_arc_width_pieslice_large[bbox2]\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mtest_arc_width_pieslice_large[bbox3]\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mtest_arc_width_fill[bbox0]\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mtest_arc_width_fill[bbox1]\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mtest_arc_width_fill[bbox2]\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mtest_arc_width_fill[bbox3]\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mtest_arc_width_non_whole_angle[bbox0]\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mtest_arc_width_non_whole_angle[bbox1]\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mtest_arc_width_non_whole_angle[bbox2]\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mtest_arc_width_non_whole_angle[bbox3]\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mtest_arc_high\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mtest_bitmap\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mtest_chord[bbox0-RGB]\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mtest_chord[bbox0-L]\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mtest_chord[bbox1-RGB]\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mtest_chord[bbox1-L]\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mtest_chord[bbox2-RGB]\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mtest_chord[bbox2-L]\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mtest_chord[bbox3-RGB]\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mtest_chord[bbox3-L]\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mtest_chord_width[bbox0]\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mtest_chord_width[bbox1]\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mtest_chord_width[bbox2]\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mtest_chord_width[bbox3]\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mtest_chord_width_fill[bbox0]\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mtest_chord_width_fill[bbox1]\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mtest_chord_width_fill[bbox2]\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mtest_chord_width_fill[bbox3]\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mtest_chord_zero_width[bbox0]\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mtest_chord_zero_width[bbox1]\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mtest_chord_zero_width[bbox2]\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mtest_chord_zero_width[bbox3]\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mtest_chord_too_fat\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mtest_circle[xy0-RGB]\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mtest_circle[xy0-L]\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mtest_circle[xy1-RGB]\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mtest_circle[xy1-L]\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mtest_ellipse[bbox0-RGB]\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mtest_ellipse[bbox0-L]\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mtest_ellipse[bbox1-RGB]\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mtest_ellipse[bbox1-L]\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mtest_ellipse[bbox2-RGB]\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mtest_ellipse[bbox2-L]\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mtest_ellipse[bbox3-RGB]\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mtest_ellipse[bbox3-L]\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mtest_ellipse_translucent[bbox0]\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mtest_ellipse_translucent[bbox1]\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mtest_ellipse_translucent[bbox2]\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mtest_ellipse_translucent[bbox3]\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mtest_ellipse_edge\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mtest_ellipse_symmetric\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mtest_ellipse_width[bbox0]\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mtest_ellipse_width[bbox1]\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mtest_ellipse_width[bbox2]\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mtest_ellipse_width[bbox3]\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mtest_ellipse_width_large\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mtest_ellipse_width_fill[bbox0]\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mtest_ellipse_width_fill[bbox1]\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mtest_ellipse_width_fill[bbox2]\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mtest_ellipse_width_fill[bbox3]\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mtest_ellipse_zero_width[bbox0]\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mtest_ellipse_zero_width[bbox1]\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mtest_ellipse_zero_width[bbox2]\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mtest_ellipse_zero_width[bbox3]\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mtest_ellipse_various_sizes\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mtest_ellipse_various_sizes_filled\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mtest_line[points0]\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mtest_line[points1]\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mtest_line[points2]\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mtest_line[points3]\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mtest_shape1\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mtest_shape2\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mtest_transform\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mtest_pieslice[-92-46-bbox0]\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mtest_pieslice[-92-46-bbox1]\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mtest_pieslice[-92-46-bbox2]\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mtest_pieslice[-92-46-bbox3]\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mtest_pieslice[-92.2-46.2-bbox0]\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mtest_pieslice[-92.2-46.2-bbox1]\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mtest_pieslice[-92.2-46.2-bbox2]\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mtest_pieslice[-92.2-46.2-bbox3]\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mtest_pieslice_width[bbox0]\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mtest_pieslice_width[bbox1]\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mtest_pieslice_width[bbox2]\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mtest_pieslice_width[bbox3]\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mtest_pieslice_width_fill[bbox0]\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mtest_pieslice_width_fill[bbox1]\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mtest_pieslice_width_fill[bbox2]\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mtest_pieslice_width_fill[bbox3]\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mtest_pieslice_zero_width[bbox0]\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mtest_pieslice_zero_width[bbox1]\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mtest_pieslice_zero_width[bbox2]\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mtest_pieslice_zero_width[bbox3]\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mtest_pieslice_wide\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mtest_pieslice_no_spikes\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mtest_point[points0]\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mtest_point[points1]\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mtest_point[points2]\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mtest_point[points3]\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mtest_point_I16\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mtest_polygon[points0]\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mtest_polygon[points1]\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mtest_polygon[points2]\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mtest_polygon[points3]\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mtest_polygon_kite[kite_points0-RGB]\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mtest_polygon_kite[kite_points0-L]\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mtest_polygon_kite[kite_points1-RGB]\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mtest_polygon_kite[kite_points1-L]\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mtest_polygon_1px_high\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mtest_polygon_1px_high_translucent\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mtest_polygon_translucent\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mtest_rectangle[bbox0]\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mtest_rectangle[bbox1]\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mtest_rectangle[bbox2]\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mtest_rectangle[bbox3]\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mtest_big_rectangle\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mtest_rectangle_width[bbox0]\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mtest_rectangle_width[bbox1]\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mtest_rectangle_width[bbox2]\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mtest_rectangle_width[bbox3]\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mtest_rectangle_width_fill[bbox0]\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mtest_rectangle_width_fill[bbox1]\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mtest_rectangle_width_fill[bbox2]\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mtest_rectangle_width_fill[bbox3]\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mtest_rectangle_zero_width[bbox0]\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mtest_rectangle_zero_width[bbox1]\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mtest_rectangle_zero_width[bbox2]\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mtest_rectangle_zero_width[bbox3]\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mtest_rectangle_I16[bbox0]\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mtest_rectangle_I16[bbox1]\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mtest_rectangle_I16[bbox2]\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mtest_rectangle_I16[bbox3]\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mtest_rectangle_translucent_outline[bbox0]\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mtest_rectangle_translucent_outline[bbox1]\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mtest_rectangle_translucent_outline[bbox2]\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mtest_rectangle_translucent_outline[bbox3]\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mtest_rounded_rectangle[xy0]\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mtest_rounded_rectangle[xy1]\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mtest_rounded_rectangle[xy2]\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mtest_rounded_rectangle_corners[True-True-True-True]\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mtest_rounded_rectangle_corners[True-True-True-False]\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mtest_rounded_rectangle_corners[True-True-False-True]\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mtest_rounded_rectangle_corners[True-True-False-False]\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mtest_rounded_rectangle_corners[True-False-True-True]\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mtest_rounded_rectangle_corners[True-False-True-False]\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mtest_rounded_rectangle_corners[True-False-False-True]\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mtest_rounded_rectangle_corners[True-False-False-False]\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mtest_rounded_rectangle_corners[False-True-True-True]\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mtest_rounded_rectangle_corners[False-True-True-False]\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mtest_rounded_rectangle_corners[False-True-False-True]\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mtest_rounded_rectangle_corners[False-True-False-False]\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mtest_rounded_rectangle_corners[False-False-True-True]\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mtest_rounded_rectangle_corners[False-False-True-False]\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mtest_rounded_rectangle_corners[False-False-False-True]\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mtest_rounded_rectangle_corners[False-False-False-False]\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mtest_rounded_rectangle_non_integer_radius[xy0-30.5-given]\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mtest_rounded_rectangle_non_integer_radius[xy1-90-width]\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mtest_rounded_rectangle_non_integer_radius[xy2-85-height]\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mtest_rounded_rectangle_zero_radius[bbox0]\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mtest_rounded_rectangle_zero_radius[bbox1]\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mtest_rounded_rectangle_zero_radius[bbox2]\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mtest_rounded_rectangle_zero_radius[bbox3]\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mtest_rounded_rectangle_translucent[xy0-x]\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mtest_rounded_rectangle_translucent[xy1-x_odd]\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mtest_rounded_rectangle_translucent[xy2-x_odd]\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mtest_rounded_rectangle_translucent[xy3-y]\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mtest_rounded_rectangle_translucent[xy4-y_odd]\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mtest_rounded_rectangle_translucent[xy5-y_odd]\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mtest_rounded_rectangle_translucent[xy6-both]\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mtest_floodfill[bbox0]\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mtest_floodfill[bbox1]\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mtest_floodfill[bbox2]\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mtest_floodfill[bbox3]\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mtest_floodfill_border[bbox0]\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mtest_floodfill_border[bbox1]\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mtest_floodfill_border[bbox2]\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mtest_floodfill_border[bbox3]\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mtest_floodfill_thresh[bbox0]\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mtest_floodfill_thresh[bbox1]\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mtest_floodfill_thresh[bbox2]\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mtest_floodfill_thresh[bbox3]\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mtest_floodfill_not_negative\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mtest_square\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mtest_triangle_right\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mtest_triangle_right_width[fill0-width]\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mtest_triangle_right_width[None-width_no_fill]\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mtest_line_horizontal\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mtest_line_vertical\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mtest_line_oblique_45\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mtest_wide_line_dot\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mtest_wide_line_larger_than_int\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mtest_line_joint[xy0]\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mtest_line_joint[xy1]\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mtest_line_joint[xy2]\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mtest_textsize_empty_string\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mtest_default_font_size\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mtest_same_color_outline[bbox0]\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mtest_same_color_outline[bbox1]\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mtest_same_color_outline[bbox2]\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mtest_same_color_outline[bbox3]\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mtest_draw_regular_polygon[4-square-args0]\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mtest_draw_regular_polygon[8-regular_octagon-args1]\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mtest_draw_regular_polygon[4-square_rotate_45-args2]\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mtest_draw_regular_polygon[3-triangle_width-args3]\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mtest_compute_regular_polygon_vertices[3-expected_vertices0]\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mtest_compute_regular_polygon_vertices[4-expected_vertices1]\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mtest_compute_regular_polygon_vertices[5-expected_vertices2]\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mtest_compute_regular_polygon_vertices[6-expected_vertices3]\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mtest_compute_regular_polygon_vertices_input_error_handling[None-bounding_circle0-0-TypeError-n_sides should be an int]\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mtest_compute_regular_polygon_vertices_input_error_handling[1-bounding_circle1-0-ValueError-n_sides should be an int > 2]\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mtest_compute_regular_polygon_vertices_input_error_handling[3-50-0-TypeError-bounding_circle should be a sequence]\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mtest_compute_regular_polygon_vertices_input_error_handling[3-bounding_circle3-0-ValueError-bounding_circle should contain 2D coordinates and a radius (e.g. (x, y, r) or ((x, y), r) )]\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mtest_compute_regular_polygon_vertices_input_error_handling[3-bounding_circle4-0-ValueError-bounding_circle should only contain numeric data]\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mtest_compute_regular_polygon_vertices_input_error_handling[3-bounding_circle5-0-ValueError-bounding_circle centre should contain 2D coordinates (e.g. (x, y))]\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mtest_compute_regular_polygon_vertices_input_error_handling[3-bounding_circle6-0-ValueError-bounding_circle radius should be > 0]\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mtest_compute_regular_polygon_vertices_input_error_handling[3-bounding_circle7-0-ValueError-rotation should be an int or float]\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mtest_continuous_horizontal_edges_polygon\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mtest_discontiguous_corners_polygon\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mtest_polygon2\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mtest_incorrectly_ordered_coordinates[xy0]\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mtest_incorrectly_ordered_coordinates[xy1]\u001b[0m\n\u001b[33mSKIPPED\u001b[0m [1] r2e_tests/test_1.py:1087: failing\n\u001b[33mSKIPPED\u001b[0m [1] r2e_tests/test_1.py:1298: freetype2 not available\n\u001b[33mSKIPPED\u001b[0m [1] r2e_tests/test_1.py:1312: freetype2 not available\n\u001b[33mSKIPPED\u001b[0m [1] r2e_tests/test_1.py:1329: freetype2 not available\n\u001b[33mSKIPPED\u001b[0m [1] r2e_tests/test_1.py:1343: freetype2 not available\n\u001b[33mSKIPPED\u001b[0m [1] r2e_tests/test_1.py:1364: freetype2 not available\n\u001b[33mSKIPPED\u001b[0m [1] r2e_tests/test_1.py:1380: freetype2 not available\n\u001b[32m======================== \u001b[32m\u001b[1m246 passed\u001b[0m, \u001b[33m7 skipped\u001b[0m\u001b[32m in 0.89s\u001b[0m\u001b[32m ========================\u001b[0m\n",
    "new_commit_res_stderr": "",
    "old_commit_res_code": 1,
    "old_commit_res_stdout": "\u001b[1m============================= test session starts ==============================\u001b[0m\nplatform linux -- Python 3.9.21, pytest-8.3.4, pluggy-1.5.0\nrootdir: /home/gcpuser/buckets/local_repoeval_bucket/repos/pillow_fdf103c5ad2cb3fe710e16f2fa3019262d088818\nconfigfile: pyproject.toml\nplugins: cov-6.0.0\ncollected 253 items\n\nr2e_tests/test_1.py \u001b[32m.\u001b[0m\u001b[32m.\u001b[0m\u001b[32m.\u001b[0m\u001b[32m.\u001b[0m\u001b[32m.\u001b[0m\u001b[32m.\u001b[0m\u001b[32m.\u001b[0m\u001b[32m.\u001b[0m\u001b[32m.\u001b[0m\u001b[32m.\u001b[0m\u001b[32m.\u001b[0m\u001b[32m.\u001b[0m\u001b[32m.\u001b[0m\u001b[32m.\u001b[0m\u001b[32m.\u001b[0m\u001b[32m.\u001b[0m\u001b[32m.\u001b[0m\u001b[32m.\u001b[0m\u001b[32m.\u001b[0m\u001b[32m.\u001b[0m\u001b[32m.\u001b[0m\u001b[32m.\u001b[0m\u001b[32m.\u001b[0m\u001b[32m.\u001b[0m\u001b[32m.\u001b[0m\u001b[32m.\u001b[0m\u001b[32m.\u001b[0m\u001b[32m.\u001b[0m\u001b[32m.\u001b[0m\u001b[32m.\u001b[0m\u001b[32m.\u001b[0m\u001b[32m.\u001b[0m\u001b[32m.\u001b[0m\u001b[32m.\u001b[0m\u001b[32m.\u001b[0m\u001b[32m.\u001b[0m\u001b[32m.\u001b[0m\u001b[32m.\u001b[0m\u001b[32m.\u001b[0m\u001b[32m.\u001b[0m\u001b[32m.\u001b[0m\u001b[32m.\u001b[0m\u001b[32m.\u001b[0m\u001b[32m.\u001b[0m\u001b[32m.\u001b[0m\u001b[32m.\u001b[0m\u001b[32m.\u001b[0m\u001b[32m.\u001b[0m\u001b[32m.\u001b[0m\u001b[32m.\u001b[0m\u001b[32m.\u001b[0m\u001b[32m.\u001b[0m\u001b[32m [ 20%]\n\u001b[0m\u001b[32m.\u001b[0m\u001b[32m.\u001b[0m\u001b[32m.\u001b[0m\u001b[32m.\u001b[0m\u001b[32m.\u001b[0m\u001b[32m.\u001b[0m\u001b[31mF\u001b[0m\u001b[31mF\u001b[0m\u001b[31mF\u001b[0m\u001b[31mF\u001b[0m\u001b[32m.\u001b[0m\u001b[32m.\u001b[0m\u001b[32m.\u001b[0m\u001b[32m.\u001b[0m\u001b[32m.\u001b[0m\u001b[32m.\u001b[0m\u001b[32m.\u001b[0m\u001b[32m.\u001b[0m\u001b[32m.\u001b[0m\u001b[32m.\u001b[0m\u001b[32m.\u001b[0m\u001b[32m.\u001b[0m\u001b[32m.\u001b[0m\u001b[32m.\u001b[0m\u001b[32m.\u001b[0m\u001b[32m.\u001b[0m\u001b[32m.\u001b[0m\u001b[32m.\u001b[0m\u001b[32m.\u001b[0m\u001b[32m.\u001b[0m\u001b[32m.\u001b[0m\u001b[32m.\u001b[0m\u001b[32m.\u001b[0m\u001b[32m.\u001b[0m\u001b[32m.\u001b[0m\u001b[32m.\u001b[0m\u001b[32m.\u001b[0m\u001b[32m.\u001b[0m\u001b[32m.\u001b[0m\u001b[32m.\u001b[0m\u001b[32m.\u001b[0m\u001b[32m.\u001b[0m\u001b[32m.\u001b[0m\u001b[32m.\u001b[0m\u001b[32m.\u001b[0m\u001b[32m.\u001b[0m\u001b[32m.\u001b[0m\u001b[32m.\u001b[0m\u001b[32m.\u001b[0m\u001b[32m.\u001b[0m\u001b[32m.\u001b[0m\u001b[32m.\u001b[0m\u001b[32m.\u001b[0m\u001b[32m.\u001b[0m\u001b[32m.\u001b[0m\u001b[32m.\u001b[0m\u001b[32m.\u001b[0m\u001b[32m.\u001b[0m\u001b[32m.\u001b[0m\u001b[32m.\u001b[0m\u001b[32m.\u001b[0m\u001b[32m.\u001b[0m\u001b[32m.\u001b[0m\u001b[32m.\u001b[0m\u001b[32m.\u001b[0m\u001b[32m.\u001b[0m\u001b[32m.\u001b[0m\u001b[32m.\u001b[0m\u001b[32m.\u001b[0m\u001b[32m.\u001b[0m\u001b[32m.\u001b[0m\u001b[32m.\u001b[0m\u001b[31m [ 49%]\n\u001b[0m\u001b[32m.\u001b[0m\u001b[32m.\u001b[0m\u001b[32m.\u001b[0m\u001b[32m.\u001b[0m\u001b[32m.\u001b[0m\u001b[32m.\u001b[0m\u001b[32m.\u001b[0m\u001b[32m.\u001b[0m\u001b[32m.\u001b[0m\u001b[32m.\u001b[0m\u001b[32m.\u001b[0m\u001b[32m.\u001b[0m\u001b[32m.\u001b[0m\u001b[32m.\u001b[0m\u001b[32m.\u001b[0m\u001b[32m.\u001b[0m\u001b[32m.\u001b[0m\u001b[32m.\u001b[0m\u001b[32m.\u001b[0m\u001b[32m.\u001b[0m\u001b[32m.\u001b[0m\u001b[32m.\u001b[0m\u001b[32m.\u001b[0m\u001b[32m.\u001b[0m\u001b[32m.\u001b[0m\u001b[32m.\u001b[0m\u001b[32m.\u001b[0m\u001b[32m.\u001b[0m\u001b[32m.\u001b[0m\u001b[32m.\u001b[0m\u001b[32m.\u001b[0m\u001b[32m.\u001b[0m\u001b[32m.\u001b[0m\u001b[32m.\u001b[0m\u001b[32m.\u001b[0m\u001b[32m.\u001b[0m\u001b[32m.\u001b[0m\u001b[32m.\u001b[0m\u001b[32m.\u001b[0m\u001b[32m.\u001b[0m\u001b[32m.\u001b[0m\u001b[32m.\u001b[0m\u001b[32m.\u001b[0m\u001b[32m.\u001b[0m\u001b[32m.\u001b[0m\u001b[32m.\u001b[0m\u001b[32m.\u001b[0m\u001b[32m.\u001b[0m\u001b[32m.\u001b[0m\u001b[32m.\u001b[0m\u001b[32m.\u001b[0m\u001b[32m.\u001b[0m\u001b[32m.\u001b[0m\u001b[32m.\u001b[0m\u001b[32m.\u001b[0m\u001b[32m.\u001b[0m\u001b[32m.\u001b[0m\u001b[32m.\u001b[0m\u001b[32m.\u001b[0m\u001b[32m.\u001b[0m\u001b[32m.\u001b[0m\u001b[32m.\u001b[0m\u001b[32m.\u001b[0m\u001b[32m.\u001b[0m\u001b[32m.\u001b[0m\u001b[32m.\u001b[0m\u001b[32m.\u001b[0m\u001b[32m.\u001b[0m\u001b[32m.\u001b[0m\u001b[32m.\u001b[0m\u001b[32m.\u001b[0m\u001b[32m.\u001b[0m\u001b[31m [ 77%]\n\u001b[0m\u001b[32m.\u001b[0m\u001b[32m.\u001b[0m\u001b[32m.\u001b[0m\u001b[32m.\u001b[0m\u001b[32m.\u001b[0m\u001b[32m.\u001b[0m\u001b[32m.\u001b[0m\u001b[32m.\u001b[0m\u001b[32m.\u001b[0m\u001b[32m.\u001b[0m\u001b[32m.\u001b[0m\u001b[32m.\u001b[0m\u001b[32m.\u001b[0m\u001b[32m.\u001b[0m\u001b[32m.\u001b[0m\u001b[32m.\u001b[0m\u001b[33ms\u001b[0m\u001b[32m.\u001b[0m\u001b[32m.\u001b[0m\u001b[32m.\u001b[0m\u001b[32m.\u001b[0m\u001b[32m.\u001b[0m\u001b[32m.\u001b[0m\u001b[32m.\u001b[0m\u001b[32m.\u001b[0m\u001b[33ms\u001b[0m\u001b[33ms\u001b[0m\u001b[33ms\u001b[0m\u001b[33ms\u001b[0m\u001b[33ms\u001b[0m\u001b[33ms\u001b[0m\u001b[32m.\u001b[0m\u001b[32m.\u001b[0m\u001b[32m.\u001b[0m\u001b[32m.\u001b[0m\u001b[32m.\u001b[0m\u001b[32m.\u001b[0m\u001b[32m.\u001b[0m\u001b[32m.\u001b[0m\u001b[32m.\u001b[0m\u001b[32m.\u001b[0m\u001b[32m.\u001b[0m\u001b[32m.\u001b[0m\u001b[32m.\u001b[0m\u001b[32m.\u001b[0m\u001b[32m.\u001b[0m\u001b[32m.\u001b[0m\u001b[32m.\u001b[0m\u001b[32m.\u001b[0m\u001b[32m.\u001b[0m\u001b[32m.\u001b[0m\u001b[32m.\u001b[0m\u001b[32m.\u001b[0m\u001b[32m.\u001b[0m\u001b[32m.\u001b[0m\u001b[32m.\u001b[0m\u001b[32m.\u001b[0m\u001b[31m                [100%]\u001b[0m\n\n=================================== FAILURES ===================================\n\u001b[31m\u001b[1m_____________________________ test_circle[xy0-RGB] _____________________________\u001b[0m\n\nmode = 'RGB', xy = (50.0, 50.0)\n\n    @pytest.mark.parametrize(\"mode\", (\"RGB\", \"L\"))\n    @pytest.mark.parametrize(\"xy\", ((W / 2, H / 2), [W / 2, H / 2]))\n    def test_circle(mode: str, xy: Sequence[float]) -> None:\n        # Arrange\n        im = Image.new(mode, (W, H))\n        draw = ImageDraw.Draw(im)\n        expected = f\"Tests/images/imagedraw_ellipse_{mode}.png\"\n    \n        # Act\n>       draw.circle(xy, 25, fill=\"green\", outline=\"blue\")\n\u001b[1m\u001b[31mE       AttributeError: 'ImageDraw' object has no attribute 'circle'\u001b[0m\n\n\u001b[1m\u001b[31mr2e_tests/test_1.py\u001b[0m:278: AttributeError\n\u001b[31m\u001b[1m______________________________ test_circle[xy0-L] ______________________________\u001b[0m\n\nmode = 'L', xy = (50.0, 50.0)\n\n    @pytest.mark.parametrize(\"mode\", (\"RGB\", \"L\"))\n    @pytest.mark.parametrize(\"xy\", ((W / 2, H / 2), [W / 2, H / 2]))\n    def test_circle(mode: str, xy: Sequence[float]) -> None:\n        # Arrange\n        im = Image.new(mode, (W, H))\n        draw = ImageDraw.Draw(im)\n        expected = f\"Tests/images/imagedraw_ellipse_{mode}.png\"\n    \n        # Act\n>       draw.circle(xy, 25, fill=\"green\", outline=\"blue\")\n\u001b[1m\u001b[31mE       AttributeError: 'ImageDraw' object has no attribute 'circle'\u001b[0m\n\n\u001b[1m\u001b[31mr2e_tests/test_1.py\u001b[0m:278: AttributeError\n\u001b[31m\u001b[1m_____________________________ test_circle[xy1-RGB] _____________________________\u001b[0m\n\nmode = 'RGB', xy = [50.0, 50.0]\n\n    @pytest.mark.parametrize(\"mode\", (\"RGB\", \"L\"))\n    @pytest.mark.parametrize(\"xy\", ((W / 2, H / 2), [W / 2, H / 2]))\n    def test_circle(mode: str, xy: Sequence[float]) -> None:\n        # Arrange\n        im = Image.new(mode, (W, H))\n        draw = ImageDraw.Draw(im)\n        expected = f\"Tests/images/imagedraw_ellipse_{mode}.png\"\n    \n        # Act\n>       draw.circle(xy, 25, fill=\"green\", outline=\"blue\")\n\u001b[1m\u001b[31mE       AttributeError: 'ImageDraw' object has no attribute 'circle'\u001b[0m\n\n\u001b[1m\u001b[31mr2e_tests/test_1.py\u001b[0m:278: AttributeError\n\u001b[31m\u001b[1m______________________________ test_circle[xy1-L] ______________________________\u001b[0m\n\nmode = 'L', xy = [50.0, 50.0]\n\n    @pytest.mark.parametrize(\"mode\", (\"RGB\", \"L\"))\n    @pytest.mark.parametrize(\"xy\", ((W / 2, H / 2), [W / 2, H / 2]))\n    def test_circle(mode: str, xy: Sequence[float]) -> None:\n        # Arrange\n        im = Image.new(mode, (W, H))\n        draw = ImageDraw.Draw(im)\n        expected = f\"Tests/images/imagedraw_ellipse_{mode}.png\"\n    \n        # Act\n>       draw.circle(xy, 25, fill=\"green\", outline=\"blue\")\n\u001b[1m\u001b[31mE       AttributeError: 'ImageDraw' object has no attribute 'circle'\u001b[0m\n\n\u001b[1m\u001b[31mr2e_tests/test_1.py\u001b[0m:278: AttributeError\n==================================== PASSES ====================================\n\u001b[36m\u001b[1m=========================== short test summary info ============================\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mtest_sanity\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mtest_valueerror\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mtest_mode_mismatch\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mtest_arc[0-180-bbox0]\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mtest_arc[0-180-bbox1]\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mtest_arc[0-180-bbox2]\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mtest_arc[0-180-bbox3]\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mtest_arc[0.5-180.4-bbox0]\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mtest_arc[0.5-180.4-bbox1]\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mtest_arc[0.5-180.4-bbox2]\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mtest_arc[0.5-180.4-bbox3]\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mtest_arc_end_le_start[bbox0]\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mtest_arc_end_le_start[bbox1]\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mtest_arc_end_le_start[bbox2]\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mtest_arc_end_le_start[bbox3]\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mtest_arc_no_loops[bbox0]\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mtest_arc_no_loops[bbox1]\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mtest_arc_no_loops[bbox2]\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mtest_arc_no_loops[bbox3]\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mtest_arc_width[bbox0]\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mtest_arc_width[bbox1]\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mtest_arc_width[bbox2]\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mtest_arc_width[bbox3]\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mtest_arc_width_pieslice_large[bbox0]\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mtest_arc_width_pieslice_large[bbox1]\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mtest_arc_width_pieslice_large[bbox2]\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mtest_arc_width_pieslice_large[bbox3]\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mtest_arc_width_fill[bbox0]\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mtest_arc_width_fill[bbox1]\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mtest_arc_width_fill[bbox2]\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mtest_arc_width_fill[bbox3]\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mtest_arc_width_non_whole_angle[bbox0]\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mtest_arc_width_non_whole_angle[bbox1]\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mtest_arc_width_non_whole_angle[bbox2]\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mtest_arc_width_non_whole_angle[bbox3]\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mtest_arc_high\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mtest_bitmap\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mtest_chord[bbox0-RGB]\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mtest_chord[bbox0-L]\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mtest_chord[bbox1-RGB]\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mtest_chord[bbox1-L]\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mtest_chord[bbox2-RGB]\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mtest_chord[bbox2-L]\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mtest_chord[bbox3-RGB]\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mtest_chord[bbox3-L]\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mtest_chord_width[bbox0]\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mtest_chord_width[bbox1]\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mtest_chord_width[bbox2]\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mtest_chord_width[bbox3]\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mtest_chord_width_fill[bbox0]\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mtest_chord_width_fill[bbox1]\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mtest_chord_width_fill[bbox2]\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mtest_chord_width_fill[bbox3]\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mtest_chord_zero_width[bbox0]\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mtest_chord_zero_width[bbox1]\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mtest_chord_zero_width[bbox2]\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mtest_chord_zero_width[bbox3]\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mtest_chord_too_fat\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mtest_ellipse[bbox0-RGB]\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mtest_ellipse[bbox0-L]\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mtest_ellipse[bbox1-RGB]\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mtest_ellipse[bbox1-L]\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mtest_ellipse[bbox2-RGB]\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mtest_ellipse[bbox2-L]\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mtest_ellipse[bbox3-RGB]\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mtest_ellipse[bbox3-L]\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mtest_ellipse_translucent[bbox0]\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mtest_ellipse_translucent[bbox1]\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mtest_ellipse_translucent[bbox2]\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mtest_ellipse_translucent[bbox3]\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mtest_ellipse_edge\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mtest_ellipse_symmetric\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mtest_ellipse_width[bbox0]\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mtest_ellipse_width[bbox1]\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mtest_ellipse_width[bbox2]\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mtest_ellipse_width[bbox3]\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mtest_ellipse_width_large\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mtest_ellipse_width_fill[bbox0]\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mtest_ellipse_width_fill[bbox1]\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mtest_ellipse_width_fill[bbox2]\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mtest_ellipse_width_fill[bbox3]\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mtest_ellipse_zero_width[bbox0]\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mtest_ellipse_zero_width[bbox1]\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mtest_ellipse_zero_width[bbox2]\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mtest_ellipse_zero_width[bbox3]\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mtest_ellipse_various_sizes\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mtest_ellipse_various_sizes_filled\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mtest_line[points0]\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mtest_line[points1]\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mtest_line[points2]\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mtest_line[points3]\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mtest_shape1\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mtest_shape2\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mtest_transform\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mtest_pieslice[-92-46-bbox0]\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mtest_pieslice[-92-46-bbox1]\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mtest_pieslice[-92-46-bbox2]\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mtest_pieslice[-92-46-bbox3]\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mtest_pieslice[-92.2-46.2-bbox0]\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mtest_pieslice[-92.2-46.2-bbox1]\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mtest_pieslice[-92.2-46.2-bbox2]\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mtest_pieslice[-92.2-46.2-bbox3]\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mtest_pieslice_width[bbox0]\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mtest_pieslice_width[bbox1]\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mtest_pieslice_width[bbox2]\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mtest_pieslice_width[bbox3]\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mtest_pieslice_width_fill[bbox0]\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mtest_pieslice_width_fill[bbox1]\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mtest_pieslice_width_fill[bbox2]\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mtest_pieslice_width_fill[bbox3]\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mtest_pieslice_zero_width[bbox0]\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mtest_pieslice_zero_width[bbox1]\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mtest_pieslice_zero_width[bbox2]\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mtest_pieslice_zero_width[bbox3]\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mtest_pieslice_wide\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mtest_pieslice_no_spikes\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mtest_point[points0]\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mtest_point[points1]\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mtest_point[points2]\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mtest_point[points3]\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mtest_point_I16\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mtest_polygon[points0]\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mtest_polygon[points1]\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mtest_polygon[points2]\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mtest_polygon[points3]\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mtest_polygon_kite[kite_points0-RGB]\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mtest_polygon_kite[kite_points0-L]\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mtest_polygon_kite[kite_points1-RGB]\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mtest_polygon_kite[kite_points1-L]\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mtest_polygon_1px_high\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mtest_polygon_1px_high_translucent\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mtest_polygon_translucent\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mtest_rectangle[bbox0]\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mtest_rectangle[bbox1]\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mtest_rectangle[bbox2]\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mtest_rectangle[bbox3]\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mtest_big_rectangle\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mtest_rectangle_width[bbox0]\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mtest_rectangle_width[bbox1]\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mtest_rectangle_width[bbox2]\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mtest_rectangle_width[bbox3]\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mtest_rectangle_width_fill[bbox0]\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mtest_rectangle_width_fill[bbox1]\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mtest_rectangle_width_fill[bbox2]\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mtest_rectangle_width_fill[bbox3]\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mtest_rectangle_zero_width[bbox0]\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mtest_rectangle_zero_width[bbox1]\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mtest_rectangle_zero_width[bbox2]\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mtest_rectangle_zero_width[bbox3]\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mtest_rectangle_I16[bbox0]\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mtest_rectangle_I16[bbox1]\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mtest_rectangle_I16[bbox2]\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mtest_rectangle_I16[bbox3]\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mtest_rectangle_translucent_outline[bbox0]\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mtest_rectangle_translucent_outline[bbox1]\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mtest_rectangle_translucent_outline[bbox2]\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mtest_rectangle_translucent_outline[bbox3]\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mtest_rounded_rectangle[xy0]\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mtest_rounded_rectangle[xy1]\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mtest_rounded_rectangle[xy2]\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mtest_rounded_rectangle_corners[True-True-True-True]\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mtest_rounded_rectangle_corners[True-True-True-False]\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mtest_rounded_rectangle_corners[True-True-False-True]\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mtest_rounded_rectangle_corners[True-True-False-False]\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mtest_rounded_rectangle_corners[True-False-True-True]\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mtest_rounded_rectangle_corners[True-False-True-False]\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mtest_rounded_rectangle_corners[True-False-False-True]\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mtest_rounded_rectangle_corners[True-False-False-False]\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mtest_rounded_rectangle_corners[False-True-True-True]\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mtest_rounded_rectangle_corners[False-True-True-False]\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mtest_rounded_rectangle_corners[False-True-False-True]\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mtest_rounded_rectangle_corners[False-True-False-False]\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mtest_rounded_rectangle_corners[False-False-True-True]\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mtest_rounded_rectangle_corners[False-False-True-False]\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mtest_rounded_rectangle_corners[False-False-False-True]\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mtest_rounded_rectangle_corners[False-False-False-False]\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mtest_rounded_rectangle_non_integer_radius[xy0-30.5-given]\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mtest_rounded_rectangle_non_integer_radius[xy1-90-width]\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mtest_rounded_rectangle_non_integer_radius[xy2-85-height]\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mtest_rounded_rectangle_zero_radius[bbox0]\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mtest_rounded_rectangle_zero_radius[bbox1]\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mtest_rounded_rectangle_zero_radius[bbox2]\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mtest_rounded_rectangle_zero_radius[bbox3]\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mtest_rounded_rectangle_translucent[xy0-x]\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mtest_rounded_rectangle_translucent[xy1-x_odd]\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mtest_rounded_rectangle_translucent[xy2-x_odd]\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mtest_rounded_rectangle_translucent[xy3-y]\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mtest_rounded_rectangle_translucent[xy4-y_odd]\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mtest_rounded_rectangle_translucent[xy5-y_odd]\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mtest_rounded_rectangle_translucent[xy6-both]\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mtest_floodfill[bbox0]\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mtest_floodfill[bbox1]\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mtest_floodfill[bbox2]\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mtest_floodfill[bbox3]\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mtest_floodfill_border[bbox0]\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mtest_floodfill_border[bbox1]\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mtest_floodfill_border[bbox2]\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mtest_floodfill_border[bbox3]\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mtest_floodfill_thresh[bbox0]\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mtest_floodfill_thresh[bbox1]\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mtest_floodfill_thresh[bbox2]\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mtest_floodfill_thresh[bbox3]\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mtest_floodfill_not_negative\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mtest_square\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mtest_triangle_right\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mtest_triangle_right_width[fill0-width]\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mtest_triangle_right_width[None-width_no_fill]\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mtest_line_horizontal\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mtest_line_vertical\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mtest_line_oblique_45\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mtest_wide_line_dot\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mtest_wide_line_larger_than_int\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mtest_line_joint[xy0]\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mtest_line_joint[xy1]\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mtest_line_joint[xy2]\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mtest_textsize_empty_string\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mtest_default_font_size\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mtest_same_color_outline[bbox0]\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mtest_same_color_outline[bbox1]\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mtest_same_color_outline[bbox2]\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mtest_same_color_outline[bbox3]\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mtest_draw_regular_polygon[4-square-args0]\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mtest_draw_regular_polygon[8-regular_octagon-args1]\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mtest_draw_regular_polygon[4-square_rotate_45-args2]\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mtest_draw_regular_polygon[3-triangle_width-args3]\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mtest_compute_regular_polygon_vertices[3-expected_vertices0]\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mtest_compute_regular_polygon_vertices[4-expected_vertices1]\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mtest_compute_regular_polygon_vertices[5-expected_vertices2]\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mtest_compute_regular_polygon_vertices[6-expected_vertices3]\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mtest_compute_regular_polygon_vertices_input_error_handling[None-bounding_circle0-0-TypeError-n_sides should be an int]\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mtest_compute_regular_polygon_vertices_input_error_handling[1-bounding_circle1-0-ValueError-n_sides should be an int > 2]\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mtest_compute_regular_polygon_vertices_input_error_handling[3-50-0-TypeError-bounding_circle should be a sequence]\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mtest_compute_regular_polygon_vertices_input_error_handling[3-bounding_circle3-0-ValueError-bounding_circle should contain 2D coordinates and a radius (e.g. (x, y, r) or ((x, y), r) )]\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mtest_compute_regular_polygon_vertices_input_error_handling[3-bounding_circle4-0-ValueError-bounding_circle should only contain numeric data]\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mtest_compute_regular_polygon_vertices_input_error_handling[3-bounding_circle5-0-ValueError-bounding_circle centre should contain 2D coordinates (e.g. (x, y))]\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mtest_compute_regular_polygon_vertices_input_error_handling[3-bounding_circle6-0-ValueError-bounding_circle radius should be > 0]\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mtest_compute_regular_polygon_vertices_input_error_handling[3-bounding_circle7-0-ValueError-rotation should be an int or float]\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mtest_continuous_horizontal_edges_polygon\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mtest_discontiguous_corners_polygon\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mtest_polygon2\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mtest_incorrectly_ordered_coordinates[xy0]\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mtest_incorrectly_ordered_coordinates[xy1]\u001b[0m\n\u001b[33mSKIPPED\u001b[0m [1] r2e_tests/test_1.py:1087: failing\n\u001b[33mSKIPPED\u001b[0m [1] r2e_tests/test_1.py:1298: freetype2 not available\n\u001b[33mSKIPPED\u001b[0m [1] r2e_tests/test_1.py:1312: freetype2 not available\n\u001b[33mSKIPPED\u001b[0m [1] r2e_tests/test_1.py:1329: freetype2 not available\n\u001b[33mSKIPPED\u001b[0m [1] r2e_tests/test_1.py:1343: freetype2 not available\n\u001b[33mSKIPPED\u001b[0m [1] r2e_tests/test_1.py:1364: freetype2 not available\n\u001b[33mSKIPPED\u001b[0m [1] r2e_tests/test_1.py:1380: freetype2 not available\n\u001b[31mFAILED\u001b[0m r2e_tests/test_1.py::\u001b[1mtest_circle[xy0-RGB]\u001b[0m - AttributeError: 'ImageDraw' object has no attribute 'circle'\n\u001b[31mFAILED\u001b[0m r2e_tests/test_1.py::\u001b[1mtest_circle[xy0-L]\u001b[0m - AttributeError: 'ImageDraw' object has no attribute 'circle'\n\u001b[31mFAILED\u001b[0m r2e_tests/test_1.py::\u001b[1mtest_circle[xy1-RGB]\u001b[0m - AttributeError: 'ImageDraw' object has no attribute 'circle'\n\u001b[31mFAILED\u001b[0m r2e_tests/test_1.py::\u001b[1mtest_circle[xy1-L]\u001b[0m - AttributeError: 'ImageDraw' object has no attribute 'circle'\n\u001b[31m=================== \u001b[31m\u001b[1m4 failed\u001b[0m, \u001b[32m242 passed\u001b[0m, \u001b[33m7 skipped\u001b[0m\u001b[31m in 0.79s\u001b[0m\u001b[31m ===================\u001b[0m\n",
    "old_commit_res_stderr": ""
}
Tests/test_imagedraw.py docs/reference/ImageDraw.rst docs/releasenotes/10.4.0.rst src/PIL/ImageDraw.py
We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN ISSUE ---
[ISSUE]
**Title:** `ImageDraw` object has no attribute `circle` when attempting to draw a circle

**Description:**
When trying to draw a circle using the `ImageDraw` class, an `AttributeError` is raised because the `circle` method does not exist.

**Example Code:**
```python
from PIL import Image, ImageDraw

# Create a new image
im = Image.new("RGB", (100, 100))
draw = ImageDraw.Draw(im)

# Attempt to draw a circle
draw.circle((50, 50), 25, fill="green", outline="blue")

# Save the image
im.save("circle.png")
```

**Expected Behavior:**
A green circle with a blue outline should be drawn at the center of the image without any errors.

**Actual Behavior:**
An `AttributeError` is raised:
```
AttributeError: 'ImageDraw' object has no attribute 'circle'
```
This prevents the circle from being drawn and the image from being saved correctly.
[/ISSUE]
--- END ISSUE ---

Below are some code segments, each from a relevant file. One or more of these files may contain bugs.

--- BEGIN FILE ---
```
### src/PIL/ImageDraw.py
#
# The Python Imaging Library
# $Id$
#
# drawing interface operations
#
# History:
# 1996-04-13 fl   Created (experimental)
# 1996-08-07 fl   Filled polygons, ellipses.
# 1996-08-13 fl   Added text support
# 1998-06-28 fl   Handle I and F images
# 1998-12-29 fl   Added arc; use arc primitive to draw ellipses
# 1999-01-10 fl   Added shape stuff (experimental)
# 1999-02-06 fl   Added bitmap support
# 1999-02-11 fl   Changed all primitives to take options
# 1999-02-20 fl   Fixed backwards compatibility
# 2000-10-12 fl   Copy on write, when necessary
# 2001-02-18 fl   Use default ink for bitmap/text also in fill mode
# 2002-10-24 fl   Added support for CSS-style color strings
# 2002-12-10 fl   Added experimental support for RGBA-on-RGB drawing
# 2002-12-11 fl   Refactored low-level drawing API (work in progress)
# 2004-08-26 fl   Made Draw() a factory function, added getdraw() support
# 2004-09-04 fl   Added width support to line primitive
# 2004-09-10 fl   Added font mode handling
# 2006-06-19 fl   Added font bearing support (getmask2)
#
# Copyright (c) 1997-2006 by Secret Labs AB
# Copyright (c) 1996-2006 by Fredrik Lundh
#
# See the README file for information on usage and redistribution.
#
from __future__ import annotations

import math
import numbers
import struct
from typing import TYPE_CHECKING, Sequence, cast

from . import Image, ImageColor
from ._typing import Coords

"""
A simple 2D drawing interface for PIL images.
<p>
Application code should use the <b>Draw</b> factory, instead of
directly.
"""


class ImageDraw:
    font = None

    def __init__(self, im: Image.Image, mode: str | None = None) -> None:
        """
        Create a drawing instance.

        :param im: The image to draw in.
        :param mode: Optional mode to use for color values.  For RGB
           images, this argument can be RGB or RGBA (to blend the
           drawing into the image).  For all other modes, this argument
           must be the same as the image mode.  If omitted, the mode
           defaults to the mode of the image.
        """
        im.load()
        if im.readonly:
            im._copy()  # make it writeable
        blend = 0
        if mode is None:
            mode = im.mode
        if mode != im.mode:
            if mode == "RGBA" and im.mode == "RGB":
                blend = 1
            else:
                msg = "mode mismatch"
                raise ValueError(msg)
        if mode == "P":
            self.palette = im.palette
        else:
            self.palette = None
        self._image = im
        self.im = im.im
        self.draw = Image.core.draw(self.im, blend)
        self.mode = mode
        if mode in ("I", "F"):
            self.ink = self.draw.draw_ink(1)
        else:
            self.ink = self.draw.draw_ink(-1)
        if mode in ("1", "P", "I", "F"):
            # FIXME: fix Fill2 to properly support matte for I+F images
            self.fontmode = "1"
        else:
            self.fontmode = "L"  # aliasing is okay for other modes
        self.fill = False

    if TYPE_CHECKING:
        from . import ImageFont

    def getfont(self) -> ImageFont.FreeTypeFont | ImageFont.ImageFont:
        """
        Get the current default font.

        To set the default font for this ImageDraw instance::

            from PIL import ImageDraw, ImageFont
            draw.font = ImageFont.truetype("Tests/fonts/FreeMono.ttf")

        To set the default font for all future ImageDraw instances::

            from PIL import ImageDraw, ImageFont
            ImageDraw.ImageDraw.font = ImageFont.truetype("Tests/fonts/FreeMono.ttf")

        If the current default font is ``None``,
        it is initialized with ``ImageFont.load_default()``.

        :returns: An image font."""
        if not self.font:
            # FIXME: should add a font repository
            from . import ImageFont

            self.font = ImageFont.load_default()
        return self.font

    def _getfont(self, font_size: float | None):
        if font_size is not None:
            from . import ImageFont

            font = ImageFont.load_default(font_size)
        else:
            font = self.getfont()
        return font

    def _getink(self, ink, fill=None) -> tuple[int | None, int | None]:
        if ink is None and fill is None:
            if self.fill:
                fill = self.ink
            else:
                ink = self.ink
        else:
            if ink is not None:
                if isinstance(ink, str):
                    ink = ImageColor.getcolor(ink, self.mode)
                if self.palette and not isinstance(ink, numbers.Number):
                    ink = self.palette.getcolor(ink, self._image)
                ink = self.draw.draw_ink(ink)
            if fill is not None:
                if isinstance(fill, str):
                    fill = ImageColor.getcolor(fill, self.mode)
                if self.palette and not isinstance(fill, numbers.Number):
                    fill = self.palette.getcolor(fill, self._image)
                fill = self.draw.draw_ink(fill)
        return ink, fill

    def arc(self, xy: Coords, start, end, fill=None, width=1) -> None:
        """Draw an arc."""
        ink, fill = self._getink(fill)
        if ink is not None:
            self.draw.draw_arc(xy, start, end, ink, width)

    def bitmap(self, xy: Sequence[int], bitmap, fill=None) -> None:
        """Draw a bitmap."""
        bitmap.load()
        ink, fill = self._getink(fill)
        if ink is None:
            ink = fill
        if ink is not None:
            self.draw.draw_bitmap(xy, bitmap.im, ink)

    def chord(self, xy: Coords, start, end, fill=None, outline=None, width=1) -> None:
        """Draw a chord."""
        ink, fill = self._getink(outline, fill)
        if fill is not None:
            self.draw.draw_chord(xy, start, end, fill, 1)
        if ink is not None and ink != fill and width != 0:
            self.draw.draw_chord(xy, start, end, ink, 0, width)

    def ellipse(self, xy: Coords, fill=None, outline=None, width=1) -> None:
        """Draw an ellipse."""
        ink, fill = self._getink(outline, fill)
        if fill is not None:
            self.draw.draw_ellipse(xy, fill, 1)
        if ink is not None and ink != fill and width != 0:
            self.draw.draw_ellipse(xy, ink, 0, width)

    def line(self, xy: Coords, fill=None, width=0, joint=None) -> None:
        """Draw a line, or a connected sequence of line segments."""
        ink = self._getink(fill)[0]
        if ink is not None:
            self.draw.draw_lines(xy, ink, width)
            if joint == "curve" and width > 4:
                points: Sequence[Sequence[float]]
                if isinstance(xy[0], (list, tuple)):
                    points = cast(Sequence[Sequence[float]], xy)
                else:
                    points = [
                        cast(Sequence[float], tuple(xy[i : i + 2]))
                        for i in range(0, len(xy), 2)
                    ]
                for i in range(1, len(points) - 1):
                    point = points[i]
                    angles = [
                        math.degrees(math.atan2(end[0] - start[0], start[1] - end[1]))
                        % 360
                        for start, end in (
                            (points[i - 1], point),
                            (point, points[i + 1]),
                        )
                    ]
                    if angles[0] == angles[1]:
                        # This is a straight line, so no joint is required
                        continue

                    def coord_at_angle(coord, angle):
                        x, y = coord
                        angle -= 90
                        distance = width / 2 - 1
                        return tuple(
                            p + (math.floor(p_d) if p_d > 0 else math.ceil(p_d))
                            for p, p_d in (
                                (x, distance * math.cos(math.radians(angle))),
                                (y, distance * math.sin(math.radians(angle))),
                            )
                        )

                    flipped = (
                        angles[1] > angles[0] and angles[1] - 180 > angles[0]
                    ) or (angles[1] < angles[0] and angles[1] + 180 > angles[0])
                    coords = [
                        (point[0] - width / 2 + 1, point[1] - width / 2 + 1),
                        (point[0] + width / 2 - 1, point[1] + width / 2 - 1),
                    ]
                    if flipped:
                        start, end = (angles[1] + 90, angles[0] + 90)
                    else:
                        start, end = (angles[0] - 90, angles[1] - 90)
                    self.pieslice(coords, start - 90, end - 90, fill)

                    if width > 8:
                        # Cover potential gaps between the line and the joint
                        if flipped:
                            gap_coords = [
                                coord_at_angle(point, angles[0] + 90),
                                point,
                                coord_at_angle(point, angles[1] + 90),
                            ]
                        else:
                            gap_coords = [
                                coord_at_angle(point, angles[0] - 90),
                                point,
                                coord_at_angle(point, angles[1] - 90),
                            ]
                        self.line(gap_coords, fill, width=3)

    def shape(self, shape, fill=None, outline=None) -> None:
        """(Experimental) Draw a shape."""
        shape.close()
        ink, fill = self._getink(outline, fill)
        if fill is not None:
            self.draw.draw_outline(shape, fill, 1)
        if ink is not None and ink != fill:
            self.draw.draw_outline(shape, ink, 0)

    def pieslice(
        self, xy: Coords, start, end, fill=None, outline=None, width=1
    ) -> None:
        """Draw a pieslice."""
        ink, fill = self._getink(outline, fill)
        if fill is not None:
            self.draw.draw_pieslice(xy, start, end, fill, 1)
        if ink is not None and ink != fill and width != 0:
            self.draw.draw_pieslice(xy, start, end, ink, 0, width)

    def point(self, xy: Coords, fill=None) -> None:
        """Draw one or more individual pixels."""
        ink, fill = self._getink(fill)
        if ink is not None:
            self.draw.draw_points(xy, ink)

    def polygon(self, xy: Coords, fill=None, outline=None, width=1) -> None:
        """Draw a polygon."""
        ink, fill = self._getink(outline, fill)
        if fill is not None:
            self.draw.draw_polygon(xy, fill, 1)
        if ink is not None and ink != fill and width != 0:
            if width == 1:
                self.draw.draw_polygon(xy, ink, 0, width)
            elif self.im is not None:
                # To avoid expanding the polygon outwards,
                # use the fill as a mask
                mask = Image.new("1", self.im.size)
                mask_ink = self._getink(1)[0]

                fill_im = mask.copy()
                draw = Draw(fill_im)
                draw.draw.draw_polygon(xy, mask_ink, 1)

                ink_im = mask.copy()
                draw = Draw(ink_im)
                width = width * 2 - 1
                draw.draw.draw_polygon(xy, mask_ink, 0, width)

                mask.paste(ink_im, mask=fill_im)

                im = Image.new(self.mode, self.im.size)
                draw = Draw(im)
                draw.draw.draw_polygon(xy, ink, 0, width)
                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, width=1
    ) -> None:
        """Draw a regular polygon."""
        xy = _compute_regular_polygon_vertices(bounding_circle, n_sides, rotation)
        self.polygon(xy, fill, outline, width)

    def rectangle(self, xy: Coords, fill=None, outline=None, width=1) -> None:
        """Draw a rectangle."""
        ink, fill = self._getink(outline, fill)
        if fill is not None:
            self.draw.draw_rectangle(xy, fill, 1)
        if ink is not None and ink != fill and width != 0:
            self.draw.draw_rectangle(xy, ink, 0, width)

    def rounded_rectangle(
        self, xy: Coords, radius=0, fill=None, outline=None, width=1, *, corners=None
    ) -> None:
        """Draw a rounded rectangle."""
        if isinstance(xy[0], (list, tuple)):
            (x0, y0), (x1, y1) = cast(Sequence[Sequence[float]], xy)
        else:
            x0, y0, x1, y1 = cast(Sequence[float], xy)
        if x1 < x0:
            msg = "x1 must be greater than or equal to x0"
            raise ValueError(msg)
        if y1 < y0:
            msg = "y1 must be greater than or equal to y0"
            raise ValueError(msg)
        if corners is None:
            corners = (True, True, True, True)

        d = radius * 2

        x0 = round(x0)
        y0 = round(y0)
        x1 = round(x1)
        y1 = round(y1)
        full_x, full_y = False, False
        if all(corners):
            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 - 1
            if full_y:
                # The two top and two bottom corners are joined
                d = y1 - y0
            if full_x and full_y:
                # If all corners are joined, that is a circle
                return self.ellipse(xy, fill, outline, width)

        if d == 0 or not any(corners):
            # If the corners have no curve,
            # or there are no corners,
            # that is a rectangle
            return self.rectangle(xy, fill, outline, width)

        r = d // 2
        ink, fill = self._getink(outline, fill)

        def draw_corners(pieslice) -> None:
            parts: tuple[tuple[tuple[float, float, float, float], int, int], ...]
            if full_x:
                # Draw top and bottom halves
                parts = (
                    ((x0, y0, x0 + d, y0 + d), 180, 360),
                    ((x0, y1 - d, x0 + d, y1), 0, 180),
                )
            elif full_y:
                # Draw left and right halves
                parts = (
                    ((x0, y0, x0 + d, y0 + d), 90, 270),
                    ((x1 - d, y0, x1, y0 + d), 270, 90),
                )
            else:
                # Draw four separate corners
                parts = tuple(
                    part
                    for i, part in enumerate(
                        (
                            ((x0, y0, x0 + d, y0 + d), 180, 270),
                            ((x1 - d, y0, x1, y0 + d), 270, 360),
                            ((x1 - d, y1 - d, x1, y1), 0, 90),
                            ((x0, y1 - d, x0 + d, y1), 90, 180),
                        )
                    )
                    if corners[i]
                )
            for part in parts:
                if pieslice:
                    self.draw.draw_pieslice(*(part + (fill, 1)))
                else:
                    self.draw.draw_arc(*(part + (ink, width)))

        if fill is not None:
            draw_corners(True)

            if full_x:
                self.draw.draw_rectangle((x0, y0 + r + 1, x1, y1 - r - 1), fill, 1)
            else:
                self.draw.draw_rectangle((x0 + r + 1, y0, x1 - r - 1, y1), fill, 1)
            if not full_x and not full_y:
                left = [x0, y0, x0 + r, y1]
                if corners[0]:
                    left[1] += r + 1
                if corners[3]:
                    left[3] -= r + 1
                self.draw.draw_rectangle(left, fill, 1)

                right = [x1 - r, y0, x1, y1]
                if corners[1]:
                    right[1] += r + 1
                if corners[2]:
                    right[3] -= r + 1
                self.draw.draw_rectangle(right, fill, 1)
        if ink is not None and ink != fill and width != 0:
            draw_corners(False)

            if not full_x:
                top = [x0, y0, x1, y0 + width - 1]
                if corners[0]:
                    top[0] += r + 1
                if corners[1]:
                    top[2] -= r + 1
                self.draw.draw_rectangle(top, ink, 1)

                bottom = [x0, y1 - width + 1, x1, y1]
                if corners[3]:
                    bottom[0] += r + 1
                if corners[2]:
                    bottom[2] -= r + 1
                self.draw.draw_rectangle(bottom, ink, 1)
            if not full_y:
                left = [x0, y0, x0 + width - 1, y1]
                if corners[0]:
                    left[1] += r + 1
                if corners[3]:
                    left[3] -= r + 1
                self.draw.draw_rectangle(left, ink, 1)

                right = [x1 - width + 1, y0, x1, y1]
                if corners[1]:
                    right[1] += r + 1
                if corners[2]:
                    right[3] -= r + 1
                self.draw.draw_rectangle(right, ink, 1)

    def _multiline_check(self, text) -> bool:
        split_character = "\n" if isinstance(text, str) else b"\n"

        return split_character in text

    def _multiline_split(self, text) -> list[str | bytes]:
        split_character = "\n" if isinstance(text, str) else b"\n"

        return text.split(split_character)

    def _multiline_spacing(self, font, spacing, stroke_width):
        return (
            self.textbbox((0, 0), "A", font, stroke_width=stroke_width)[3]
            + stroke_width
            + spacing
        )

    def text(
        self,
        xy,
        text,
        fill=None,
        font=None,
        anchor=None,
        spacing=4,
        align="left",
        direction=None,
        features=None,
        language=None,
        stroke_width=0,
        stroke_fill=None,
        embedded_color=False,
        *args,
        **kwargs,
    ) -> None:
        """Draw text."""
        if embedded_color and self.mode not in ("RGB", "RGBA"):
            msg = "Embedded color supported only in RGB and RGBA modes"
            raise ValueError(msg)

        if font is None:
            font = self._getfont(kwargs.get("font_size"))

        if self._multiline_check(text):
            return self.multiline_text(
                xy,
                text,
                fill,
                font,
                anchor,
                spacing,
                align,
                direction,
                features,
                language,
                stroke_width,
                stroke_fill,
                embedded_color,
            )

        def getink(fill):
            ink, fill = self._getink(fill)
            if ink is None:
                return fill
            return ink

        def draw_text(ink, stroke_width=0, stroke_offset=None) -> None:
            mode = self.fontmode
            if stroke_width == 0 and embedded_color:
                mode = "RGBA"
            coord = []
            start = []
            for i in range(2):
                coord.append(int(xy[i]))
                start.append(math.modf(xy[i])[0])
            try:
                mask, offset = font.getmask2(
                    text,
                    mode,
                    direction=direction,
                    features=features,
                    language=language,
                    stroke_width=stroke_width,
                    anchor=anchor,
                    ink=ink,
                    start=start,
                    *args,
                    **kwargs,
                )
                coord = [coord[0] + offset[0], coord[1] + offset[1]]
            except AttributeError:
                try:
                    mask = font.getmask(
                        text,
                        mode,
                        direction,
                        features,
                        language,
                        stroke_width,
                        anchor,
                        ink,
                        start=start,
                        *args,
                        **kwargs,
                    )
                except TypeError:
                    mask = font.getmask(text)
            if stroke_offset:
                coord = [coord[0] + stroke_offset[0], coord[1] + stroke_offset[1]]
            if mode == "RGBA":
                # font.getmask2(mode="RGBA") returns color in RGB bands and mask in A
                # extract mask and set text alpha
                color, mask = mask, mask.getband(3)
                ink_alpha = struct.pack("i", ink)[3]
                color.fillband(3, ink_alpha)
                x, y = coord
                if self.im is not None:
                    self.im.paste(
                        color, (x, y, x + mask.size[0], y + mask.size[1]), mask
                    )
            else:
                self.draw.draw_bitmap(coord, mask, ink)

        ink = getink(fill)
        if ink is not None:
            stroke_ink = None
            if stroke_width:
                stroke_ink = getink(stroke_fill) if stroke_fill is not None else ink

            if stroke_ink is not None:
                # Draw stroked text
                draw_text(stroke_ink, stroke_width)

                # Draw normal text
                draw_text(ink, 0)
            else:
                # Only draw normal text
                draw_text(ink)

    def multiline_text(
        self,
        xy,
        text,
        fill=None,
        font=None,
        anchor=None,
        spacing=4,
        align="left",
        direction=None,
        features=None,
        language=None,
        stroke_width=0,
        stroke_fill=None,
        embedded_color=False,
        *,
        font_size=None,
    ) -> None:
        if direction == "ttb":
            msg = "ttb direction is unsupported for multiline text"
            raise ValueError(msg)

        if anchor is None:
            anchor = "la"
        elif len(anchor) != 2:
            msg = "anchor must be a 2 character string"
            raise ValueError(msg)
        elif anchor[1] in "tb":
            msg = "anchor not supported for multiline text"
            raise ValueError(msg)

        if font is None:
            font = self._getfont(font_size)

        widths = []
        max_width = 0
        lines = self._multiline_split(text)
        line_spacing = self._multiline_spacing(font, spacing, stroke_width)
        for line in lines:
            line_width = self.textlength(
                line, font, direction=direction, features=features, language=language
            )
            widths.append(line_width)
            max_width = max(max_width, line_width)

        top = xy[1]
        if anchor[1] == "m":
            top -= (len(lines) - 1) * line_spacing / 2.0
        elif anchor[1] == "d":
            top -= (len(lines) - 1) * line_spacing

        for idx, line in enumerate(lines):
            left = xy[0]
            width_difference = max_width - widths[idx]

            # first align left by anchor
            if anchor[0] == "m":
                left -= width_difference / 2.0
            elif anchor[0] == "r":
                left -= width_difference

            # then align by align parameter
            if align == "left":
                pass
            elif align == "center":
                left += width_difference / 2.0
            elif align == "right":
                left += width_difference
            else:
                msg = 'align must be "left", "center" or "right"'
                raise ValueError(msg)

            self.text(
                (left, top),
                line,
                fill,
                font,
                anchor,
                direction=direction,
                features=features,
                language=language,
                stroke_width=stroke_width,
                stroke_fill=stroke_fill,
                embedded_color=embedded_color,
            )
            top += line_spacing

    def textlength(
        self,
        text,
        font=None,
        direction=None,
        features=None,
        language=None,
        embedded_color=False,
        *,
        font_size=None,
    ):
        """Get the length of a given string, in pixels with 1/64 precision."""
        if self._multiline_check(text):
            msg = "can't measure length of multiline text"
            raise ValueError(msg)
        if embedded_color and self.mode not in ("RGB", "RGBA"):
            msg = "Embedded color supported only in RGB and RGBA modes"
            raise ValueError(msg)

        if font is None:
            font = self._getfont(font_size)
        mode = "RGBA" if embedded_color else self.fontmode
        return font.getlength(text, mode, direction, features, language)

    def textbbox(
        self,
        xy,
        text,
        font=None,
        anchor=None,
        spacing=4,
        align="left",
        direction=None,
        features=None,
        language=None,
        stroke_width=0,
        embedded_color=False,
        *,
        font_size=None,
    ) -> tuple[int, int, int, int]:
        """Get the bounding box of a given string, in pixels."""
        if embedded_color and self.mode not in ("RGB", "RGBA"):
            msg = "Embedded color supported only in RGB and RGBA modes"
            raise ValueError(msg)

        if font is None:
            font = self._getfont(font_size)

        if self._multiline_check(text):
            return self.multiline_textbbox(
                xy,
                text,
                font,
                anchor,
                spacing,
                align,
                direction,
                features,
                language,
                stroke_width,
                embedded_color,
            )

        mode = "RGBA" if embedded_color else self.fontmode
        bbox = font.getbbox(
            text, mode, direction, features, language, stroke_width, anchor
        )
        return bbox[0] + xy[0], bbox[1] + xy[1], bbox[2] + xy[0], bbox[3] + xy[1]

    def multiline_textbbox(
        self,
        xy,
        text,
        font=None,
        anchor=None,
        spacing=4,
        align="left",
        direction=None,
        features=None,
        language=None,
        stroke_width=0,
        embedded_color=False,
        *,
        font_size=None,
    ) -> tuple[int, int, int, int]:
        if direction == "ttb":
            msg = "ttb direction is unsupported for multiline text"
            raise ValueError(msg)

        if anchor is None:
            anchor = "la"
        elif len(anchor) != 2:
            msg = "anchor must be a 2 character string"
            raise ValueError(msg)
        elif anchor[1] in "tb":
            msg = "anchor not supported for multiline text"
            raise ValueError(msg)

        if font is None:
            font = self._getfont(font_size)

        widths = []
        max_width = 0
        lines = self._multiline_split(text)
        line_spacing = self._multiline_spacing(font, spacing, stroke_width)
        for line in lines:
            line_width = self.textlength(
                line,
                font,
                direction=direction,
                features=features,
                language=language,
                embedded_color=embedded_color,
            )
            widths.append(line_width)
            max_width = max(max_width, line_width)

        top = xy[1]
        if anchor[1] == "m":
            top -= (len(lines) - 1) * line_spacing / 2.0
        elif anchor[1] == "d":
            top -= (len(lines) - 1) * line_spacing

        bbox: tuple[int, int, int, int] | None = None

        for idx, line in enumerate(lines):
            left = xy[0]
            width_difference = max_width - widths[idx]

            # first align left by anchor
            if anchor[0] == "m":
                left -= width_difference / 2.0
            elif anchor[0] == "r":
                left -= width_difference

            # then align by align parameter
            if align == "left":
                pass
            elif align == "center":
                left += width_difference / 2.0
            elif align == "right":
                left += width_difference
            else:
                msg = 'align must be "left", "center" or "right"'
                raise ValueError(msg)

            bbox_line = self.textbbox(
                (left, top),
                line,
                font,
                anchor,
                direction=direction,
                features=features,
                language=language,
                stroke_width=stroke_width,
                embedded_color=embedded_color,
            )
            if bbox is None:
                bbox = bbox_line
            else:
                bbox = (
                    min(bbox[0], bbox_line[0]),
                    min(bbox[1], bbox_line[1]),
                    max(bbox[2], bbox_line[2]),
                    max(bbox[3], bbox_line[3]),
                )

            top += line_spacing

        if bbox is None:
            return xy[0], xy[1], xy[0], xy[1]
        return bbox


def Draw(im, mode: str | None = None) -> ImageDraw:
    """
    A simple 2D drawing interface for PIL images.

    :param im: The image to draw in.
    :param mode: Optional mode to use for color values.  For RGB
       images, this argument can be RGB or RGBA (to blend the
       drawing into the image).  For all other modes, this argument
       must be the same as the image mode.  If omitted, the mode
       defaults to the mode of the image.
    """
    try:
        return im.getdraw(mode)
    except AttributeError:
        return ImageDraw(im, mode)


# experimental access to the outline API
try:
    Outline = Image.core.outline
except AttributeError:
    Outline = None


def getdraw(im=None, hints=None):
    """
    (Experimental) A more advanced 2D drawing interface for PIL images,
    based on the WCK interface.

    :param im: The image to draw in.
    :param hints: An optional list of hints.
    :returns: A (drawing context, drawing resource factory) tuple.
    """
    # FIXME: this needs more work!
    # FIXME: come up with a better 'hints' scheme.
    handler = None
    if not hints or "nicest" in hints:
        try:
            from . import _imagingagg as handler
        except ImportError:
            pass
    if handler is None:
        from . import ImageDraw2 as handler
    if im:
        im = handler.Draw(im)
    return im, handler


def floodfill(image: Image.Image, xy, value, border=None, thresh=0) -> None:
    """
    (experimental) Fills a bounded region with a given color.

    :param image: Target image.
    :param xy: Seed position (a 2-item coordinate tuple). See
        :ref:`coordinate-system`.
    :param value: Fill color.
    :param border: Optional border value.  If given, the region consists of
        pixels with a color different from the border color.  If not given,
        the region consists of pixels having the same color as the seed
        pixel.
    :param thresh: Optional threshold value which specifies a maximum
        tolerable difference of a pixel value from the 'background' in
        order for it to be replaced. Useful for filling regions of
        non-homogeneous, but similar, colors.
    """
    # based on an implementation by Eric S. Raymond
    # amended by yo1995 @20180806
    pixel = image.load()
    x, y = xy
    try:
        background = pixel[x, y]
        if _color_diff(value, background) <= thresh:
            return  # seed point already has fill color
        pixel[x, y] = value
    except (ValueError, IndexError):
        return  # seed point outside image
    edge = {(x, y)}
    # use a set to keep record of current and previous edge pixels
    # to reduce memory consumption
    full_edge = set()
    while edge:
        new_edge = set()
        for x, y in edge:  # 4 adjacent method
            for s, t in ((x + 1, y), (x - 1, y), (x, y + 1), (x, y - 1)):
                # If already processed, or if a coordinate is negative, skip
                if (s, t) in full_edge or s < 0 or t < 0:
                    continue
                try:
                    p = pixel[s, t]
                except (ValueError, IndexError):
                    pass
                else:
                    full_edge.add((s, t))
                    if border is None:
                        fill = _color_diff(p, background) <= thresh
                    else:
                        fill = p not in (value, border)
                    if fill:
                        pixel[s, t] = value
                        new_edge.add((s, t))
        full_edge = edge  # discard pixels processed
        edge = new_edge


def _compute_regular_polygon_vertices(
    bounding_circle, n_sides, rotation
) -> list[tuple[float, float]]:
    """
    Generate a list of vertices for a 2D regular polygon.

    :param bounding_circle: The bounding circle is a tuple defined
        by a point and radius. The polygon is inscribed in this circle.
        (e.g. ``bounding_circle=(x, y, r)`` or ``((x, y), r)``)
    :param n_sides: Number of sides
        (e.g. ``n_sides=3`` for a triangle, ``6`` for a hexagon)
    :param rotation: Apply an arbitrary rotation to the polygon
        (e.g. ``rotation=90``, applies a 90 degree rotation)
    :return: List of regular polygon vertices
        (e.g. ``[(25, 50), (50, 50), (50, 25), (25, 25)]``)

    How are the vertices computed?
    1. Compute the following variables
        - theta: Angle between the apothem & the nearest polygon vertex
        - side_length: Length of each polygon edge
        - centroid: Center of bounding circle (1st, 2nd elements of bounding_circle)
        - polygon_radius: Polygon radius (last element of bounding_circle)
        - angles: Location of each polygon vertex in polar grid
            (e.g. A square with 0 degree rotation => [225.0, 315.0, 45.0, 135.0])

    2. For each angle in angles, get the polygon vertex at that angle
        The vertex is computed using the equation below.
            X= xcos(φ) + ysin(φ)
            Y= −xsin(φ) + ycos(φ)

        Note:
            φ = angle in degrees
            x = 0
            y = polygon_radius

        The formula above assumes rotation around the origin.
        In our case, we are rotating around the centroid.
        To account for this, we use the formula below
            X = xcos(φ) + ysin(φ) + centroid_x
            Y = −xsin(φ) + ycos(φ) + centroid_y
    """
    # 1. Error Handling
    # 1.1 Check `n_sides` has an appropriate value
    if not isinstance(n_sides, int):
        msg = "n_sides should be an int"
        raise TypeError(msg)
    if n_sides < 3:
        msg = "n_sides should be an int > 2"
        raise ValueError(msg)

    # 1.2 Check `bounding_circle` has an appropriate value
    if not isinstance(bounding_circle, (list, tuple)):
        msg = "bounding_circle should be a sequence"
        raise TypeError(msg)

    if len(bounding_circle) == 3:
        *centroid, polygon_radius = bounding_circle
    elif len(bounding_circle) == 2:
        centroid, polygon_radius = bounding_circle
    else:
        msg = (
            "bounding_circle should contain 2D coordinates "
            "and a radius (e.g. (x, y, r) or ((x, y), r) )"
        )
        raise ValueError(msg)

    if not all(isinstance(i, (int, float)) for i in (*centroid, polygon_radius)):
        msg = "bounding_circle should only contain numeric data"
        raise ValueError(msg)

    if not len(centroid) == 2:
        msg = "bounding_circle centre should contain 2D coordinates (e.g. (x, y))"
        raise ValueError(msg)

    if polygon_radius <= 0:
        msg = "bounding_circle radius should be > 0"
        raise ValueError(msg)

    # 1.3 Check `rotation` has an appropriate value
    if not isinstance(rotation, (int, float)):
        msg = "rotation should be an int or float"
        raise ValueError(msg)

    # 2. Define Helper Functions
    def _apply_rotation(point: list[float], degrees: float) -> tuple[int, int]:
        return (
            round(
                point[0] * math.cos(math.radians(360 - degrees))
                - point[1] * math.sin(math.radians(360 - degrees))
                + centroid[0],
                2,
            ),
            round(
                point[1] * math.cos(math.radians(360 - degrees))
                + point[0] * math.sin(math.radians(360 - degrees))
                + centroid[1],
                2,
            ),
        )

    def _compute_polygon_vertex(angle: float) -> tuple[int, int]:
        start_point = [polygon_radius, 0]
        return _apply_rotation(start_point, angle)

    def _get_angles(n_sides: int, rotation: float) -> list[float]:
        angles = []
        degrees = 360 / n_sides
        # Start with the bottom left polygon vertex
        current_angle = (270 - 0.5 * degrees) + rotation
        for _ in range(0, n_sides):
            angles.append(current_angle)
            current_angle += degrees
            if current_angle > 360:
                current_angle -= 360
        return angles

    # 3. Variable Declarations
    angles = _get_angles(n_sides, rotation)

    # 4. Compute Vertices
    return [_compute_polygon_vertex(angle) for angle in angles]


def _color_diff(color1, color2: float | tuple[int, ...]) -> float:
    """
    Uses 1-norm distance to calculate difference between two values.
    """
    if isinstance(color2, tuple):
        return sum(abs(color1[i] - color2[i]) for i in range(0, len(color2)))
    else:
        return abs(color1 - color2)

```
--- END FILE ---

Please first localize the bug based on the issue statement, and then generate *SEARCH/REPLACE* edits to fix the issue.

Every *SEARCH/REPLACE* edit must use this format:
1. The file path
2. The start of search block: <<<<<<< SEARCH
3. A contiguous chunk of lines to search for in the existing source code
4. The dividing line: =======
5. The lines to replace into the source code
6. The end of the replace block: >>>>>>> REPLACE

Here is an example:

```python
### mathweb/flask/app.py
<<<<<<< SEARCH
from flask import Flask
=======
import math
from flask import Flask
>>>>>>> REPLACE
```
</solution>

Please note that the *SEARCH/REPLACE* edit REQUIRES PROPER INDENTATION. If you would like to add the line '        print(x)', you must fully write that out, with all those spaces before the code!
Wrap each *SEARCH/REPLACE* edit in a code block as shown in the example above. If you have multiple *SEARCH/REPLACE* edits, use a separate code block for each one.
[ISSUE]
**Title:** `ImageDraw` object has no attribute `circle` when attempting to draw a circle

**Description:**
When trying to draw a circle using the `ImageDraw` class, an `AttributeError` is raised because the `circle` method does not exist.

**Example Code:**
```python
from PIL import Image, ImageDraw

# Create a new image
im = Image.new("RGB", (100, 100))
draw = ImageDraw.Draw(im)

# Attempt to draw a circle
draw.circle((50, 50), 25, fill="green", outline="blue")

# Save the image
im.save("circle.png")
```

**Expected Behavior:**
A green circle with a blue outline should be drawn at the center of the image without any errors.

**Actual Behavior:**
An `AttributeError` is raised:
```
AttributeError: 'ImageDraw' object has no attribute 'circle'
```
This prevents the circle from being drawn and the image from being saved correctly.
[/ISSUE]
{
    "\u001b[1mtest_sanity\u001b[0m": "PASSED",
    "\u001b[1mtest_valueerror\u001b[0m": "PASSED",
    "\u001b[1mtest_mode_mismatch\u001b[0m": "PASSED",
    "\u001b[1mtest_arc[0-180-bbox0]\u001b[0m": "PASSED",
    "\u001b[1mtest_arc[0-180-bbox1]\u001b[0m": "PASSED",
    "\u001b[1mtest_arc[0-180-bbox2]\u001b[0m": "PASSED",
    "\u001b[1mtest_arc[0-180-bbox3]\u001b[0m": "PASSED",
    "\u001b[1mtest_arc[0.5-180.4-bbox0]\u001b[0m": "PASSED",
    "\u001b[1mtest_arc[0.5-180.4-bbox1]\u001b[0m": "PASSED",
    "\u001b[1mtest_arc[0.5-180.4-bbox2]\u001b[0m": "PASSED",
    "\u001b[1mtest_arc[0.5-180.4-bbox3]\u001b[0m": "PASSED",
    "\u001b[1mtest_arc_end_le_start[bbox0]\u001b[0m": "PASSED",
    "\u001b[1mtest_arc_end_le_start[bbox1]\u001b[0m": "PASSED",
    "\u001b[1mtest_arc_end_le_start[bbox2]\u001b[0m": "PASSED",
    "\u001b[1mtest_arc_end_le_start[bbox3]\u001b[0m": "PASSED",
    "\u001b[1mtest_arc_no_loops[bbox0]\u001b[0m": "PASSED",
    "\u001b[1mtest_arc_no_loops[bbox1]\u001b[0m": "PASSED",
    "\u001b[1mtest_arc_no_loops[bbox2]\u001b[0m": "PASSED",
    "\u001b[1mtest_arc_no_loops[bbox3]\u001b[0m": "PASSED",
    "\u001b[1mtest_arc_width[bbox0]\u001b[0m": "PASSED",
    "\u001b[1mtest_arc_width[bbox1]\u001b[0m": "PASSED",
    "\u001b[1mtest_arc_width[bbox2]\u001b[0m": "PASSED",
    "\u001b[1mtest_arc_width[bbox3]\u001b[0m": "PASSED",
    "\u001b[1mtest_arc_width_pieslice_large[bbox0]\u001b[0m": "PASSED",
    "\u001b[1mtest_arc_width_pieslice_large[bbox1]\u001b[0m": "PASSED",
    "\u001b[1mtest_arc_width_pieslice_large[bbox2]\u001b[0m": "PASSED",
    "\u001b[1mtest_arc_width_pieslice_large[bbox3]\u001b[0m": "PASSED",
    "\u001b[1mtest_arc_width_fill[bbox0]\u001b[0m": "PASSED",
    "\u001b[1mtest_arc_width_fill[bbox1]\u001b[0m": "PASSED",
    "\u001b[1mtest_arc_width_fill[bbox2]\u001b[0m": "PASSED",
    "\u001b[1mtest_arc_width_fill[bbox3]\u001b[0m": "PASSED",
    "\u001b[1mtest_arc_width_non_whole_angle[bbox0]\u001b[0m": "PASSED",
    "\u001b[1mtest_arc_width_non_whole_angle[bbox1]\u001b[0m": "PASSED",
    "\u001b[1mtest_arc_width_non_whole_angle[bbox2]\u001b[0m": "PASSED",
    "\u001b[1mtest_arc_width_non_whole_angle[bbox3]\u001b[0m": "PASSED",
    "\u001b[1mtest_arc_high\u001b[0m": "PASSED",
    "\u001b[1mtest_bitmap\u001b[0m": "PASSED",
    "\u001b[1mtest_chord[bbox0-RGB]\u001b[0m": "PASSED",
    "\u001b[1mtest_chord[bbox0-L]\u001b[0m": "PASSED",
    "\u001b[1mtest_chord[bbox1-RGB]\u001b[0m": "PASSED",
    "\u001b[1mtest_chord[bbox1-L]\u001b[0m": "PASSED",
    "\u001b[1mtest_chord[bbox2-RGB]\u001b[0m": "PASSED",
    "\u001b[1mtest_chord[bbox2-L]\u001b[0m": "PASSED",
    "\u001b[1mtest_chord[bbox3-RGB]\u001b[0m": "PASSED",
    "\u001b[1mtest_chord[bbox3-L]\u001b[0m": "PASSED",
    "\u001b[1mtest_chord_width[bbox0]\u001b[0m": "PASSED",
    "\u001b[1mtest_chord_width[bbox1]\u001b[0m": "PASSED",
    "\u001b[1mtest_chord_width[bbox2]\u001b[0m": "PASSED",
    "\u001b[1mtest_chord_width[bbox3]\u001b[0m": "PASSED",
    "\u001b[1mtest_chord_width_fill[bbox0]\u001b[0m": "PASSED",
    "\u001b[1mtest_chord_width_fill[bbox1]\u001b[0m": "PASSED",
    "\u001b[1mtest_chord_width_fill[bbox2]\u001b[0m": "PASSED",
    "\u001b[1mtest_chord_width_fill[bbox3]\u001b[0m": "PASSED",
    "\u001b[1mtest_chord_zero_width[bbox0]\u001b[0m": "PASSED",
    "\u001b[1mtest_chord_zero_width[bbox1]\u001b[0m": "PASSED",
    "\u001b[1mtest_chord_zero_width[bbox2]\u001b[0m": "PASSED",
    "\u001b[1mtest_chord_zero_width[bbox3]\u001b[0m": "PASSED",
    "\u001b[1mtest_chord_too_fat\u001b[0m": "PASSED",
    "\u001b[1mtest_circle[xy0-RGB]\u001b[0m": "PASSED",
    "\u001b[1mtest_circle[xy0-L]\u001b[0m": "PASSED",
    "\u001b[1mtest_circle[xy1-RGB]\u001b[0m": "PASSED",
    "\u001b[1mtest_circle[xy1-L]\u001b[0m": "PASSED",
    "\u001b[1mtest_ellipse[bbox0-RGB]\u001b[0m": "PASSED",
    "\u001b[1mtest_ellipse[bbox0-L]\u001b[0m": "PASSED",
    "\u001b[1mtest_ellipse[bbox1-RGB]\u001b[0m": "PASSED",
    "\u001b[1mtest_ellipse[bbox1-L]\u001b[0m": "PASSED",
    "\u001b[1mtest_ellipse[bbox2-RGB]\u001b[0m": "PASSED",
    "\u001b[1mtest_ellipse[bbox2-L]\u001b[0m": "PASSED",
    "\u001b[1mtest_ellipse[bbox3-RGB]\u001b[0m": "PASSED",
    "\u001b[1mtest_ellipse[bbox3-L]\u001b[0m": "PASSED",
    "\u001b[1mtest_ellipse_translucent[bbox0]\u001b[0m": "PASSED",
    "\u001b[1mtest_ellipse_translucent[bbox1]\u001b[0m": "PASSED",
    "\u001b[1mtest_ellipse_translucent[bbox2]\u001b[0m": "PASSED",
    "\u001b[1mtest_ellipse_translucent[bbox3]\u001b[0m": "PASSED",
    "\u001b[1mtest_ellipse_edge\u001b[0m": "PASSED",
    "\u001b[1mtest_ellipse_symmetric\u001b[0m": "PASSED",
    "\u001b[1mtest_ellipse_width[bbox0]\u001b[0m": "PASSED",
    "\u001b[1mtest_ellipse_width[bbox1]\u001b[0m": "PASSED",
    "\u001b[1mtest_ellipse_width[bbox2]\u001b[0m": "PASSED",
    "\u001b[1mtest_ellipse_width[bbox3]\u001b[0m": "PASSED",
    "\u001b[1mtest_ellipse_width_large\u001b[0m": "PASSED",
    "\u001b[1mtest_ellipse_width_fill[bbox0]\u001b[0m": "PASSED",
    "\u001b[1mtest_ellipse_width_fill[bbox1]\u001b[0m": "PASSED",
    "\u001b[1mtest_ellipse_width_fill[bbox2]\u001b[0m": "PASSED",
    "\u001b[1mtest_ellipse_width_fill[bbox3]\u001b[0m": "PASSED",
    "\u001b[1mtest_ellipse_zero_width[bbox0]\u001b[0m": "PASSED",
    "\u001b[1mtest_ellipse_zero_width[bbox1]\u001b[0m": "PASSED",
    "\u001b[1mtest_ellipse_zero_width[bbox2]\u001b[0m": "PASSED",
    "\u001b[1mtest_ellipse_zero_width[bbox3]\u001b[0m": "PASSED",
    "\u001b[1mtest_ellipse_various_sizes\u001b[0m": "PASSED",
    "\u001b[1mtest_ellipse_various_sizes_filled\u001b[0m": "PASSED",
    "\u001b[1mtest_line[points0]\u001b[0m": "PASSED",
    "\u001b[1mtest_line[points1]\u001b[0m": "PASSED",
    "\u001b[1mtest_line[points2]\u001b[0m": "PASSED",
    "\u001b[1mtest_line[points3]\u001b[0m": "PASSED",
    "\u001b[1mtest_shape1\u001b[0m": "PASSED",
    "\u001b[1mtest_shape2\u001b[0m": "PASSED",
    "\u001b[1mtest_transform\u001b[0m": "PASSED",
    "\u001b[1mtest_pieslice[-92-46-bbox0]\u001b[0m": "PASSED",
    "\u001b[1mtest_pieslice[-92-46-bbox1]\u001b[0m": "PASSED",
    "\u001b[1mtest_pieslice[-92-46-bbox2]\u001b[0m": "PASSED",
    "\u001b[1mtest_pieslice[-92-46-bbox3]\u001b[0m": "PASSED",
    "\u001b[1mtest_pieslice[-92.2-46.2-bbox0]\u001b[0m": "PASSED",
    "\u001b[1mtest_pieslice[-92.2-46.2-bbox1]\u001b[0m": "PASSED",
    "\u001b[1mtest_pieslice[-92.2-46.2-bbox2]\u001b[0m": "PASSED",
    "\u001b[1mtest_pieslice[-92.2-46.2-bbox3]\u001b[0m": "PASSED",
    "\u001b[1mtest_pieslice_width[bbox0]\u001b[0m": "PASSED",
    "\u001b[1mtest_pieslice_width[bbox1]\u001b[0m": "PASSED",
    "\u001b[1mtest_pieslice_width[bbox2]\u001b[0m": "PASSED",
    "\u001b[1mtest_pieslice_width[bbox3]\u001b[0m": "PASSED",
    "\u001b[1mtest_pieslice_width_fill[bbox0]\u001b[0m": "PASSED",
    "\u001b[1mtest_pieslice_width_fill[bbox1]\u001b[0m": "PASSED",
    "\u001b[1mtest_pieslice_width_fill[bbox2]\u001b[0m": "PASSED",
    "\u001b[1mtest_pieslice_width_fill[bbox3]\u001b[0m": "PASSED",
    "\u001b[1mtest_pieslice_zero_width[bbox0]\u001b[0m": "PASSED",
    "\u001b[1mtest_pieslice_zero_width[bbox1]\u001b[0m": "PASSED",
    "\u001b[1mtest_pieslice_zero_width[bbox2]\u001b[0m": "PASSED",
    "\u001b[1mtest_pieslice_zero_width[bbox3]\u001b[0m": "PASSED",
    "\u001b[1mtest_pieslice_wide\u001b[0m": "PASSED",
    "\u001b[1mtest_pieslice_no_spikes\u001b[0m": "PASSED",
    "\u001b[1mtest_point[points0]\u001b[0m": "PASSED",
    "\u001b[1mtest_point[points1]\u001b[0m": "PASSED",
    "\u001b[1mtest_point[points2]\u001b[0m": "PASSED",
    "\u001b[1mtest_point[points3]\u001b[0m": "PASSED",
    "\u001b[1mtest_point_I16\u001b[0m": "PASSED",
    "\u001b[1mtest_polygon[points0]\u001b[0m": "PASSED",
    "\u001b[1mtest_polygon[points1]\u001b[0m": "PASSED",
    "\u001b[1mtest_polygon[points2]\u001b[0m": "PASSED",
    "\u001b[1mtest_polygon[points3]\u001b[0m": "PASSED",
    "\u001b[1mtest_polygon_kite[kite_points0-RGB]\u001b[0m": "PASSED",
    "\u001b[1mtest_polygon_kite[kite_points0-L]\u001b[0m": "PASSED",
    "\u001b[1mtest_polygon_kite[kite_points1-RGB]\u001b[0m": "PASSED",
    "\u001b[1mtest_polygon_kite[kite_points1-L]\u001b[0m": "PASSED",
    "\u001b[1mtest_polygon_1px_high\u001b[0m": "PASSED",
    "\u001b[1mtest_polygon_1px_high_translucent\u001b[0m": "PASSED",
    "\u001b[1mtest_polygon_translucent\u001b[0m": "PASSED",
    "\u001b[1mtest_rectangle[bbox0]\u001b[0m": "PASSED",
    "\u001b[1mtest_rectangle[bbox1]\u001b[0m": "PASSED",
    "\u001b[1mtest_rectangle[bbox2]\u001b[0m": "PASSED",
    "\u001b[1mtest_rectangle[bbox3]\u001b[0m": "PASSED",
    "\u001b[1mtest_big_rectangle\u001b[0m": "PASSED",
    "\u001b[1mtest_rectangle_width[bbox0]\u001b[0m": "PASSED",
    "\u001b[1mtest_rectangle_width[bbox1]\u001b[0m": "PASSED",
    "\u001b[1mtest_rectangle_width[bbox2]\u001b[0m": "PASSED",
    "\u001b[1mtest_rectangle_width[bbox3]\u001b[0m": "PASSED",
    "\u001b[1mtest_rectangle_width_fill[bbox0]\u001b[0m": "PASSED",
    "\u001b[1mtest_rectangle_width_fill[bbox1]\u001b[0m": "PASSED",
    "\u001b[1mtest_rectangle_width_fill[bbox2]\u001b[0m": "PASSED",
    "\u001b[1mtest_rectangle_width_fill[bbox3]\u001b[0m": "PASSED",
    "\u001b[1mtest_rectangle_zero_width[bbox0]\u001b[0m": "PASSED",
    "\u001b[1mtest_rectangle_zero_width[bbox1]\u001b[0m": "PASSED",
    "\u001b[1mtest_rectangle_zero_width[bbox2]\u001b[0m": "PASSED",
    "\u001b[1mtest_rectangle_zero_width[bbox3]\u001b[0m": "PASSED",
    "\u001b[1mtest_rectangle_I16[bbox0]\u001b[0m": "PASSED",
    "\u001b[1mtest_rectangle_I16[bbox1]\u001b[0m": "PASSED",
    "\u001b[1mtest_rectangle_I16[bbox2]\u001b[0m": "PASSED",
    "\u001b[1mtest_rectangle_I16[bbox3]\u001b[0m": "PASSED",
    "\u001b[1mtest_rectangle_translucent_outline[bbox0]\u001b[0m": "PASSED",
    "\u001b[1mtest_rectangle_translucent_outline[bbox1]\u001b[0m": "PASSED",
    "\u001b[1mtest_rectangle_translucent_outline[bbox2]\u001b[0m": "PASSED",
    "\u001b[1mtest_rectangle_translucent_outline[bbox3]\u001b[0m": "PASSED",
    "\u001b[1mtest_rounded_rectangle[xy0]\u001b[0m": "PASSED",
    "\u001b[1mtest_rounded_rectangle[xy1]\u001b[0m": "PASSED",
    "\u001b[1mtest_rounded_rectangle[xy2]\u001b[0m": "PASSED",
    "\u001b[1mtest_rounded_rectangle_corners[True-True-True-True]\u001b[0m": "PASSED",
    "\u001b[1mtest_rounded_rectangle_corners[True-True-True-False]\u001b[0m": "PASSED",
    "\u001b[1mtest_rounded_rectangle_corners[True-True-False-True]\u001b[0m": "PASSED",
    "\u001b[1mtest_rounded_rectangle_corners[True-True-False-False]\u001b[0m": "PASSED",
    "\u001b[1mtest_rounded_rectangle_corners[True-False-True-True]\u001b[0m": "PASSED",
    "\u001b[1mtest_rounded_rectangle_corners[True-False-True-False]\u001b[0m": "PASSED",
    "\u001b[1mtest_rounded_rectangle_corners[True-False-False-True]\u001b[0m": "PASSED",
    "\u001b[1mtest_rounded_rectangle_corners[True-False-False-False]\u001b[0m": "PASSED",
    "\u001b[1mtest_rounded_rectangle_corners[False-True-True-True]\u001b[0m": "PASSED",
    "\u001b[1mtest_rounded_rectangle_corners[False-True-True-False]\u001b[0m": "PASSED",
    "\u001b[1mtest_rounded_rectangle_corners[False-True-False-True]\u001b[0m": "PASSED",
    "\u001b[1mtest_rounded_rectangle_corners[False-True-False-False]\u001b[0m": "PASSED",
    "\u001b[1mtest_rounded_rectangle_corners[False-False-True-True]\u001b[0m": "PASSED",
    "\u001b[1mtest_rounded_rectangle_corners[False-False-True-False]\u001b[0m": "PASSED",
    "\u001b[1mtest_rounded_rectangle_corners[False-False-False-True]\u001b[0m": "PASSED",
    "\u001b[1mtest_rounded_rectangle_corners[False-False-False-False]\u001b[0m": "PASSED",
    "\u001b[1mtest_rounded_rectangle_non_integer_radius[xy0-30.5-given]\u001b[0m": "PASSED",
    "\u001b[1mtest_rounded_rectangle_non_integer_radius[xy1-90-width]\u001b[0m": "PASSED",
    "\u001b[1mtest_rounded_rectangle_non_integer_radius[xy2-85-height]\u001b[0m": "PASSED",
    "\u001b[1mtest_rounded_rectangle_zero_radius[bbox0]\u001b[0m": "PASSED",
    "\u001b[1mtest_rounded_rectangle_zero_radius[bbox1]\u001b[0m": "PASSED",
    "\u001b[1mtest_rounded_rectangle_zero_radius[bbox2]\u001b[0m": "PASSED",
    "\u001b[1mtest_rounded_rectangle_zero_radius[bbox3]\u001b[0m": "PASSED",
    "\u001b[1mtest_rounded_rectangle_translucent[xy0-x]\u001b[0m": "PASSED",
    "\u001b[1mtest_rounded_rectangle_translucent[xy1-x_odd]\u001b[0m": "PASSED",
    "\u001b[1mtest_rounded_rectangle_translucent[xy2-x_odd]\u001b[0m": "PASSED",
    "\u001b[1mtest_rounded_rectangle_translucent[xy3-y]\u001b[0m": "PASSED",
    "\u001b[1mtest_rounded_rectangle_translucent[xy4-y_odd]\u001b[0m": "PASSED",
    "\u001b[1mtest_rounded_rectangle_translucent[xy5-y_odd]\u001b[0m": "PASSED",
    "\u001b[1mtest_rounded_rectangle_translucent[xy6-both]\u001b[0m": "PASSED",
    "\u001b[1mtest_floodfill[bbox0]\u001b[0m": "PASSED",
    "\u001b[1mtest_floodfill[bbox1]\u001b[0m": "PASSED",
    "\u001b[1mtest_floodfill[bbox2]\u001b[0m": "PASSED",
    "\u001b[1mtest_floodfill[bbox3]\u001b[0m": "PASSED",
    "\u001b[1mtest_floodfill_border[bbox0]\u001b[0m": "PASSED",
    "\u001b[1mtest_floodfill_border[bbox1]\u001b[0m": "PASSED",
    "\u001b[1mtest_floodfill_border[bbox2]\u001b[0m": "PASSED",
    "\u001b[1mtest_floodfill_border[bbox3]\u001b[0m": "PASSED",
    "\u001b[1mtest_floodfill_thresh[bbox0]\u001b[0m": "PASSED",
    "\u001b[1mtest_floodfill_thresh[bbox1]\u001b[0m": "PASSED",
    "\u001b[1mtest_floodfill_thresh[bbox2]\u001b[0m": "PASSED",
    "\u001b[1mtest_floodfill_thresh[bbox3]\u001b[0m": "PASSED",
    "\u001b[1mtest_floodfill_not_negative\u001b[0m": "PASSED",
    "\u001b[1mtest_square\u001b[0m": "PASSED",
    "\u001b[1mtest_triangle_right\u001b[0m": "PASSED",
    "\u001b[1mtest_triangle_right_width[fill0-width]\u001b[0m": "PASSED",
    "\u001b[1mtest_triangle_right_width[None-width_no_fill]\u001b[0m": "PASSED",
    "\u001b[1mtest_line_horizontal\u001b[0m": "PASSED",
    "\u001b[1mtest_line_vertical\u001b[0m": "PASSED",
    "\u001b[1mtest_line_oblique_45\u001b[0m": "PASSED",
    "\u001b[1mtest_wide_line_dot\u001b[0m": "PASSED",
    "\u001b[1mtest_wide_line_larger_than_int\u001b[0m": "PASSED",
    "\u001b[1mtest_line_joint[xy0]\u001b[0m": "PASSED",
    "\u001b[1mtest_line_joint[xy1]\u001b[0m": "PASSED",
    "\u001b[1mtest_line_joint[xy2]\u001b[0m": "PASSED",
    "\u001b[1mtest_textsize_empty_string\u001b[0m": "PASSED",
    "\u001b[1mtest_default_font_size\u001b[0m": "PASSED",
    "\u001b[1mtest_same_color_outline[bbox0]\u001b[0m": "PASSED",
    "\u001b[1mtest_same_color_outline[bbox1]\u001b[0m": "PASSED",
    "\u001b[1mtest_same_color_outline[bbox2]\u001b[0m": "PASSED",
    "\u001b[1mtest_same_color_outline[bbox3]\u001b[0m": "PASSED",
    "\u001b[1mtest_draw_regular_polygon[4-square-args0]\u001b[0m": "PASSED",
    "\u001b[1mtest_draw_regular_polygon[8-regular_octagon-args1]\u001b[0m": "PASSED",
    "\u001b[1mtest_draw_regular_polygon[4-square_rotate_45-args2]\u001b[0m": "PASSED",
    "\u001b[1mtest_draw_regular_polygon[3-triangle_width-args3]\u001b[0m": "PASSED",
    "\u001b[1mtest_compute_regular_polygon_vertices[3-expected_vertices0]\u001b[0m": "PASSED",
    "\u001b[1mtest_compute_regular_polygon_vertices[4-expected_vertices1]\u001b[0m": "PASSED",
    "\u001b[1mtest_compute_regular_polygon_vertices[5-expected_vertices2]\u001b[0m": "PASSED",
    "\u001b[1mtest_compute_regular_polygon_vertices[6-expected_vertices3]\u001b[0m": "PASSED",
    "\u001b[1mtest_compute_regular_polygon_vertices_input_error_handling[None-bounding_circle0-0-TypeError-n_sides should be an int]\u001b[0m": "PASSED",
    "\u001b[1mtest_compute_regular_polygon_vertices_input_error_handling[1-bounding_circle1-0-ValueError-n_sides should be an int > 2]\u001b[0m": "PASSED",
    "\u001b[1mtest_compute_regular_polygon_vertices_input_error_handling[3-50-0-TypeError-bounding_circle should be a sequence]\u001b[0m": "PASSED",
    "\u001b[1mtest_compute_regular_polygon_vertices_input_error_handling[3-bounding_circle3-0-ValueError-bounding_circle should contain 2D coordinates and a radius (e.g. (x, y, r) or ((x, y), r) )]\u001b[0m": "PASSED",
    "\u001b[1mtest_compute_regular_polygon_vertices_input_error_handling[3-bounding_circle4-0-ValueError-bounding_circle should only contain numeric data]\u001b[0m": "PASSED",
    "\u001b[1mtest_compute_regular_polygon_vertices_input_error_handling[3-bounding_circle5-0-ValueError-bounding_circle centre should contain 2D coordinates (e.g. (x, y))]\u001b[0m": "PASSED",
    "\u001b[1mtest_compute_regular_polygon_vertices_input_error_handling[3-bounding_circle6-0-ValueError-bounding_circle radius should be > 0]\u001b[0m": "PASSED",
    "\u001b[1mtest_compute_regular_polygon_vertices_input_error_handling[3-bounding_circle7-0-ValueError-rotation should be an int or float]\u001b[0m": "PASSED",
    "\u001b[1mtest_continuous_horizontal_edges_polygon\u001b[0m": "PASSED",
    "\u001b[1mtest_discontiguous_corners_polygon\u001b[0m": "PASSED",
    "\u001b[1mtest_polygon2\u001b[0m": "PASSED",
    "\u001b[1mtest_incorrectly_ordered_coordinates[xy0]\u001b[0m": "PASSED",
    "\u001b[1mtest_incorrectly_ordered_coordinates[xy1]\u001b[0m": "PASSED"
}
diff --git a/src/PIL/ImageDraw.py b/src/PIL/ImageDraw.py
index 42f2ee8c7..17c176430 100644
--- a/src/PIL/ImageDraw.py
+++ b/src/PIL/ImageDraw.py
@@ -181,6 +181,13 @@ class ImageDraw:
         if ink is not None and ink != fill and width != 0:
             self.draw.draw_ellipse(xy, ink, 0, width)
 
+    def circle(
+        self, xy: Sequence[float], radius: float, fill=None, outline=None, width=1
+    ) -> None:
+        """Draw a circle given center coordinates and a radius."""
+        ellipse_xy = (xy[0] - radius, xy[1] - radius, xy[0] + radius, xy[1] + radius)
+        self.ellipse(ellipse_xy, fill, outline, width)
+
     def line(self, xy: Coords, fill=None, width=0, joint=None) -> None:
         """Draw a line, or a connected sequence of line segments."""
         ink = self._getink(fill)[0]
 

{"src/PIL/ImageDraw.py": "#\n# The Python Imaging Library\n# $Id$\n#\n# drawing interface operations\n#\n# History:\n# 1996-04-13 fl   Created (experimental)\n# 1996-08-07 fl   Filled polygons, ellipses.\n# 1996-08-13 fl   Added text support\n# 1998-06-28 fl   Handle I and F images\n# 1998-12-29 fl   Added arc; use arc primitive to draw ellipses\n# 1999-01-10 fl   Added shape stuff (experimental)\n# 1999-02-06 fl   Added bitmap support\n# 1999-02-11 fl   Changed all primitives to take options\n# 1999-02-20 fl   Fixed backwards compatibility\n# 2000-10-12 fl   Copy on write, when necessary\n# 2001-02-18 fl   Use default ink for bitmap/text also in fill mode\n# 2002-10-24 fl   Added support for CSS-style color strings\n# 2002-12-10 fl   Added experimental support for RGBA-on-RGB drawing\n# 2002-12-11 fl   Refactored low-level drawing API (work in progress)\n# 2004-08-26 fl   Made Draw() a factory function, added getdraw() support\n# 2004-09-04 fl   Added width support to line primitive\n# 2004-09-10 fl   Added font mode handling\n# 2006-06-19 fl   Added font bearing support (getmask2)\n#\n# Copyright (c) 1997-2006 by Secret Labs AB\n# Copyright (c) 1996-2006 by Fredrik Lundh\n#\n# See the README file for information on usage and redistribution.\n#\nfrom __future__ import annotations\n\nimport math\nimport numbers\nimport struct\nfrom typing import TYPE_CHECKING, Sequence, cast\n\nfrom . import Image, ImageColor\nfrom ._typing import Coords\n\n\"\"\"\nA simple 2D drawing interface for PIL images.\n<p>\nApplication code should use the <b>Draw</b> factory, instead of\ndirectly.\n\"\"\"\n\n\nclass ImageDraw:\n    font = None\n\n    def __init__(self, im: Image.Image, mode: str | None = None) -> None:\n        \"\"\"\n        Create a drawing instance.\n\n        :param im: The image to draw in.\n        :param mode: Optional mode to use for color values.  For RGB\n           images, this argument can be RGB or RGBA (to blend the\n           drawing into the image).  For all other modes, this argument\n           must be the same as the image mode.  If omitted, the mode\n           defaults to the mode of the image.\n        \"\"\"\n        im.load()\n        if im.readonly:\n            im._copy()  # make it writeable\n        blend = 0\n        if mode is None:\n            mode = im.mode\n        if mode != im.mode:\n            if mode == \"RGBA\" and im.mode == \"RGB\":\n                blend = 1\n            else:\n                msg = \"mode mismatch\"\n                raise ValueError(msg)\n        if mode == \"P\":\n            self.palette = im.palette\n        else:\n            self.palette = None\n        self._image = im\n        self.im = im.im\n        self.draw = Image.core.draw(self.im, blend)\n        self.mode = mode\n        if mode in (\"I\", \"F\"):\n            self.ink = self.draw.draw_ink(1)\n        else:\n            self.ink = self.draw.draw_ink(-1)\n        if mode in (\"1\", \"P\", \"I\", \"F\"):\n            # FIXME: fix Fill2 to properly support matte for I+F images\n            self.fontmode = \"1\"\n        else:\n            self.fontmode = \"L\"  # aliasing is okay for other modes\n        self.fill = False\n\n    if TYPE_CHECKING:\n        from . import ImageFont\n\n    def getfont(self) -> ImageFont.FreeTypeFont | ImageFont.ImageFont:\n        \"\"\"\n        Get the current default font.\n\n        To set the default font for this ImageDraw instance::\n\n            from PIL import ImageDraw, ImageFont\n            draw.font = ImageFont.truetype(\"Tests/fonts/FreeMono.ttf\")\n\n        To set the default font for all future ImageDraw instances::\n\n            from PIL import ImageDraw, ImageFont\n            ImageDraw.ImageDraw.font = ImageFont.truetype(\"Tests/fonts/FreeMono.ttf\")\n\n        If the current default font is ``None``,\n        it is initialized with ``ImageFont.load_default()``.\n\n        :returns: An image font.\"\"\"\n        if not self.font:\n            # FIXME: should add a font repository\n            from . import ImageFont\n\n            self.font = ImageFont.load_default()\n        return self.font\n\n    def _getfont(self, font_size: float | None):\n        if font_size is not None:\n            from . import ImageFont\n\n            font = ImageFont.load_default(font_size)\n        else:\n            font = self.getfont()\n        return font\n\n    def _getink(self, ink, fill=None) -> tuple[int | None, int | None]:\n        if ink is None and fill is None:\n            if self.fill:\n                fill = self.ink\n            else:\n                ink = self.ink\n        else:\n            if ink is not None:\n                if isinstance(ink, str):\n                    ink = ImageColor.getcolor(ink, self.mode)\n                if self.palette and not isinstance(ink, numbers.Number):\n                    ink = self.palette.getcolor(ink, self._image)\n                ink = self.draw.draw_ink(ink)\n            if fill is not None:\n                if isinstance(fill, str):\n                    fill = ImageColor.getcolor(fill, self.mode)\n                if self.palette and not isinstance(fill, numbers.Number):\n                    fill = self.palette.getcolor(fill, self._image)\n                fill = self.draw.draw_ink(fill)\n        return ink, fill\n\n    def arc(self, xy: Coords, start, end, fill=None, width=1) -> None:\n        \"\"\"Draw an arc.\"\"\"\n        ink, fill = self._getink(fill)\n        if ink is not None:\n            self.draw.draw_arc(xy, start, end, ink, width)\n\n    def bitmap(self, xy: Sequence[int], bitmap, fill=None) -> None:\n        \"\"\"Draw a bitmap.\"\"\"\n        bitmap.load()\n        ink, fill = self._getink(fill)\n        if ink is None:\n            ink = fill\n        if ink is not None:\n            self.draw.draw_bitmap(xy, bitmap.im, ink)\n\n    def chord(self, xy: Coords, start, end, fill=None, outline=None, width=1) -> None:\n        \"\"\"Draw a chord.\"\"\"\n        ink, fill = self._getink(outline, fill)\n        if fill is not None:\n            self.draw.draw_chord(xy, start, end, fill, 1)\n        if ink is not None and ink != fill and width != 0:\n            self.draw.draw_chord(xy, start, end, ink, 0, width)\n\n    def ellipse(self, xy: Coords, fill=None, outline=None, width=1) -> None:\n        \"\"\"Draw an ellipse.\"\"\"\n        ink, fill = self._getink(outline, fill)\n        if fill is not None:\n            self.draw.draw_ellipse(xy, fill, 1)\n        if ink is not None and ink != fill and width != 0:\n            self.draw.draw_ellipse(xy, ink, 0, width)\n\n    def line(self, xy: Coords, fill=None, width=0, joint=None) -> None:\n        \"\"\"Draw a line, or a connected sequence of line segments.\"\"\"\n        ink = self._getink(fill)[0]\n        if ink is not None:\n            self.draw.draw_lines(xy, ink, width)\n            if joint == \"curve\" and width > 4:\n                points: Sequence[Sequence[float]]\n                if isinstance(xy[0], (list, tuple)):\n                    points = cast(Sequence[Sequence[float]], xy)\n                else:\n                    points = [\n                        cast(Sequence[float], tuple(xy[i : i + 2]))\n                        for i in range(0, len(xy), 2)\n                    ]\n                for i in range(1, len(points) - 1):\n                    point = points[i]\n                    angles = [\n                        math.degrees(math.atan2(end[0] - start[0], start[1] - end[1]))\n                        % 360\n                        for start, end in (\n                            (points[i - 1], point),\n                            (point, points[i + 1]),\n                        )\n                    ]\n                    if angles[0] == angles[1]:\n                        # This is a straight line, so no joint is required\n                        continue\n\n                    def coord_at_angle(coord, angle):\n                        x, y = coord\n                        angle -= 90\n                        distance = width / 2 - 1\n                        return tuple(\n                            p + (math.floor(p_d) if p_d > 0 else math.ceil(p_d))\n                            for p, p_d in (\n                                (x, distance * math.cos(math.radians(angle))),\n                                (y, distance * math.sin(math.radians(angle))),\n                            )\n                        )\n\n                    flipped = (\n                        angles[1] > angles[0] and angles[1] - 180 > angles[0]\n                    ) or (angles[1] < angles[0] and angles[1] + 180 > angles[0])\n                    coords = [\n                        (point[0] - width / 2 + 1, point[1] - width / 2 + 1),\n                        (point[0] + width / 2 - 1, point[1] + width / 2 - 1),\n                    ]\n                    if flipped:\n                        start, end = (angles[1] + 90, angles[0] + 90)\n                    else:\n                        start, end = (angles[0] - 90, angles[1] - 90)\n                    self.pieslice(coords, start - 90, end - 90, fill)\n\n                    if width > 8:\n                        # Cover potential gaps between the line and the joint\n                        if flipped:\n                            gap_coords = [\n                                coord_at_angle(point, angles[0] + 90),\n                                point,\n                                coord_at_angle(point, angles[1] + 90),\n                            ]\n                        else:\n                            gap_coords = [\n                                coord_at_angle(point, angles[0] - 90),\n                                point,\n                                coord_at_angle(point, angles[1] - 90),\n                            ]\n                        self.line(gap_coords, fill, width=3)\n\n    def shape(self, shape, fill=None, outline=None) -> None:\n        \"\"\"(Experimental) Draw a shape.\"\"\"\n        shape.close()\n        ink, fill = self._getink(outline, fill)\n        if fill is not None:\n            self.draw.draw_outline(shape, fill, 1)\n        if ink is not None and ink != fill:\n            self.draw.draw_outline(shape, ink, 0)\n\n    def pieslice(\n        self, xy: Coords, start, end, fill=None, outline=None, width=1\n    ) -> None:\n        \"\"\"Draw a pieslice.\"\"\"\n        ink, fill = self._getink(outline, fill)\n        if fill is not None:\n            self.draw.draw_pieslice(xy, start, end, fill, 1)\n        if ink is not None and ink != fill and width != 0:\n            self.draw.draw_pieslice(xy, start, end, ink, 0, width)\n\n    def point(self, xy: Coords, fill=None) -> None:\n        \"\"\"Draw one or more individual pixels.\"\"\"\n        ink, fill = self._getink(fill)\n        if ink is not None:\n            self.draw.draw_points(xy, ink)\n\n    def polygon(self, xy: Coords, fill=None, outline=None, width=1) -> None:\n        \"\"\"Draw a polygon.\"\"\"\n        ink, fill = self._getink(outline, fill)\n        if fill is not None:\n            self.draw.draw_polygon(xy, fill, 1)\n        if ink is not None and ink != fill and width != 0:\n            if width == 1:\n                self.draw.draw_polygon(xy, ink, 0, width)\n            elif self.im is not None:\n                # To avoid expanding the polygon outwards,\n                # use the fill as a mask\n                mask = Image.new(\"1\", self.im.size)\n                mask_ink = self._getink(1)[0]\n\n                fill_im = mask.copy()\n                draw = Draw(fill_im)\n                draw.draw.draw_polygon(xy, mask_ink, 1)\n\n                ink_im = mask.copy()\n                draw = Draw(ink_im)\n                width = width * 2 - 1\n                draw.draw.draw_polygon(xy, mask_ink, 0, width)\n\n                mask.paste(ink_im, mask=fill_im)\n\n                im = Image.new(self.mode, self.im.size)\n                draw = Draw(im)\n                draw.draw.draw_polygon(xy, ink, 0, width)\n                self.im.paste(im.im, (0, 0) + im.size, mask.im)\n\n    def regular_polygon(\n        self, bounding_circle, n_sides, rotation=0, fill=None, outline=None, width=1\n    ) -> None:\n        \"\"\"Draw a regular polygon.\"\"\"\n        xy = _compute_regular_polygon_vertices(bounding_circle, n_sides, rotation)\n        self.polygon(xy, fill, outline, width)\n\n    def rectangle(self, xy: Coords, fill=None, outline=None, width=1) -> None:\n        \"\"\"Draw a rectangle.\"\"\"\n        ink, fill = self._getink(outline, fill)\n        if fill is not None:\n            self.draw.draw_rectangle(xy, fill, 1)\n        if ink is not None and ink != fill and width != 0:\n            self.draw.draw_rectangle(xy, ink, 0, width)\n\n    def rounded_rectangle(\n        self, xy: Coords, radius=0, fill=None, outline=None, width=1, *, corners=None\n    ) -> None:\n        \"\"\"Draw a rounded rectangle.\"\"\"\n        if isinstance(xy[0], (list, tuple)):\n            (x0, y0), (x1, y1) = cast(Sequence[Sequence[float]], xy)\n        else:\n            x0, y0, x1, y1 = cast(Sequence[float], xy)\n        if x1 < x0:\n            msg = \"x1 must be greater than or equal to x0\"\n            raise ValueError(msg)\n        if y1 < y0:\n            msg = \"y1 must be greater than or equal to y0\"\n            raise ValueError(msg)\n        if corners is None:\n            corners = (True, True, True, True)\n\n        d = radius * 2\n\n        x0 = round(x0)\n        y0 = round(y0)\n        x1 = round(x1)\n        y1 = round(y1)\n        full_x, full_y = False, False\n        if all(corners):\n            full_x = d >= x1 - x0 - 1\n            if full_x:\n                # The two left and two right corners are joined\n                d = x1 - x0\n            full_y = d >= y1 - y0 - 1\n            if full_y:\n                # The two top and two bottom corners are joined\n                d = y1 - y0\n            if full_x and full_y:\n                # If all corners are joined, that is a circle\n                return self.ellipse(xy, fill, outline, width)\n\n        if d == 0 or not any(corners):\n            # If the corners have no curve,\n            # or there are no corners,\n            # that is a rectangle\n            return self.rectangle(xy, fill, outline, width)\n\n        r = d // 2\n        ink, fill = self._getink(outline, fill)\n\n        def draw_corners(pieslice) -> None:\n            parts: tuple[tuple[tuple[float, float, float, float], int, int], ...]\n            if full_x:\n                # Draw top and bottom halves\n                parts = (\n                    ((x0, y0, x0 + d, y0 + d), 180, 360),\n                    ((x0, y1 - d, x0 + d, y1), 0, 180),\n                )\n            elif full_y:\n                # Draw left and right halves\n                parts = (\n                    ((x0, y0, x0 + d, y0 + d), 90, 270),\n                    ((x1 - d, y0, x1, y0 + d), 270, 90),\n                )\n            else:\n                # Draw four separate corners\n                parts = tuple(\n                    part\n                    for i, part in enumerate(\n                        (\n                            ((x0, y0, x0 + d, y0 + d), 180, 270),\n                            ((x1 - d, y0, x1, y0 + d), 270, 360),\n                            ((x1 - d, y1 - d, x1, y1), 0, 90),\n                            ((x0, y1 - d, x0 + d, y1), 90, 180),\n                        )\n                    )\n                    if corners[i]\n                )\n            for part in parts:\n                if pieslice:\n                    self.draw.draw_pieslice(*(part + (fill, 1)))\n                else:\n                    self.draw.draw_arc(*(part + (ink, width)))\n\n        if fill is not None:\n            draw_corners(True)\n\n            if full_x:\n                self.draw.draw_rectangle((x0, y0 + r + 1, x1, y1 - r - 1), fill, 1)\n            else:\n                self.draw.draw_rectangle((x0 + r + 1, y0, x1 - r - 1, y1), fill, 1)\n            if not full_x and not full_y:\n                left = [x0, y0, x0 + r, y1]\n                if corners[0]:\n                    left[1] += r + 1\n                if corners[3]:\n                    left[3] -= r + 1\n                self.draw.draw_rectangle(left, fill, 1)\n\n                right = [x1 - r, y0, x1, y1]\n                if corners[1]:\n                    right[1] += r + 1\n                if corners[2]:\n                    right[3] -= r + 1\n                self.draw.draw_rectangle(right, fill, 1)\n        if ink is not None and ink != fill and width != 0:\n            draw_corners(False)\n\n            if not full_x:\n                top = [x0, y0, x1, y0 + width - 1]\n                if corners[0]:\n                    top[0] += r + 1\n                if corners[1]:\n                    top[2] -= r + 1\n                self.draw.draw_rectangle(top, ink, 1)\n\n                bottom = [x0, y1 - width + 1, x1, y1]\n                if corners[3]:\n                    bottom[0] += r + 1\n                if corners[2]:\n                    bottom[2] -= r + 1\n                self.draw.draw_rectangle(bottom, ink, 1)\n            if not full_y:\n                left = [x0, y0, x0 + width - 1, y1]\n                if corners[0]:\n                    left[1] += r + 1\n                if corners[3]:\n                    left[3] -= r + 1\n                self.draw.draw_rectangle(left, ink, 1)\n\n                right = [x1 - width + 1, y0, x1, y1]\n                if corners[1]:\n                    right[1] += r + 1\n                if corners[2]:\n                    right[3] -= r + 1\n                self.draw.draw_rectangle(right, ink, 1)\n\n    def _multiline_check(self, text) -> bool:\n        split_character = \"\\n\" if isinstance(text, str) else b\"\\n\"\n\n        return split_character in text\n\n    def _multiline_split(self, text) -> list[str | bytes]:\n        split_character = \"\\n\" if isinstance(text, str) else b\"\\n\"\n\n        return text.split(split_character)\n\n    def _multiline_spacing(self, font, spacing, stroke_width):\n        return (\n            self.textbbox((0, 0), \"A\", font, stroke_width=stroke_width)[3]\n            + stroke_width\n            + spacing\n        )\n\n    def text(\n        self,\n        xy,\n        text,\n        fill=None,\n        font=None,\n        anchor=None,\n        spacing=4,\n        align=\"left\",\n        direction=None,\n        features=None,\n        language=None,\n        stroke_width=0,\n        stroke_fill=None,\n        embedded_color=False,\n        *args,\n        **kwargs,\n    ) -> None:\n        \"\"\"Draw text.\"\"\"\n        if embedded_color and self.mode not in (\"RGB\", \"RGBA\"):\n            msg = \"Embedded color supported only in RGB and RGBA modes\"\n            raise ValueError(msg)\n\n        if font is None:\n            font = self._getfont(kwargs.get(\"font_size\"))\n\n        if self._multiline_check(text):\n            return self.multiline_text(\n                xy,\n                text,\n                fill,\n                font,\n                anchor,\n                spacing,\n                align,\n                direction,\n                features,\n                language,\n                stroke_width,\n                stroke_fill,\n                embedded_color,\n            )\n\n        def getink(fill):\n            ink, fill = self._getink(fill)\n            if ink is None:\n                return fill\n            return ink\n\n        def draw_text(ink, stroke_width=0, stroke_offset=None) -> None:\n            mode = self.fontmode\n            if stroke_width == 0 and embedded_color:\n                mode = \"RGBA\"\n            coord = []\n            start = []\n            for i in range(2):\n                coord.append(int(xy[i]))\n                start.append(math.modf(xy[i])[0])\n            try:\n                mask, offset = font.getmask2(\n                    text,\n                    mode,\n                    direction=direction,\n                    features=features,\n                    language=language,\n                    stroke_width=stroke_width,\n                    anchor=anchor,\n                    ink=ink,\n                    start=start,\n                    *args,\n                    **kwargs,\n                )\n                coord = [coord[0] + offset[0], coord[1] + offset[1]]\n            except AttributeError:\n                try:\n                    mask = font.getmask(\n                        text,\n                        mode,\n                        direction,\n                        features,\n                        language,\n                        stroke_width,\n                        anchor,\n                        ink,\n                        start=start,\n                        *args,\n                        **kwargs,\n                    )\n                except TypeError:\n                    mask = font.getmask(text)\n            if stroke_offset:\n                coord = [coord[0] + stroke_offset[0], coord[1] + stroke_offset[1]]\n            if mode == \"RGBA\":\n                # font.getmask2(mode=\"RGBA\") returns color in RGB bands and mask in A\n                # extract mask and set text alpha\n                color, mask = mask, mask.getband(3)\n                ink_alpha = struct.pack(\"i\", ink)[3]\n                color.fillband(3, ink_alpha)\n                x, y = coord\n                if self.im is not None:\n                    self.im.paste(\n                        color, (x, y, x + mask.size[0], y + mask.size[1]), mask\n                    )\n            else:\n                self.draw.draw_bitmap(coord, mask, ink)\n\n        ink = getink(fill)\n        if ink is not None:\n            stroke_ink = None\n            if stroke_width:\n                stroke_ink = getink(stroke_fill) if stroke_fill is not None else ink\n\n            if stroke_ink is not None:\n                # Draw stroked text\n                draw_text(stroke_ink, stroke_width)\n\n                # Draw normal text\n                draw_text(ink, 0)\n            else:\n                # Only draw normal text\n                draw_text(ink)\n\n    def multiline_text(\n        self,\n        xy,\n        text,\n        fill=None,\n        font=None,\n        anchor=None,\n        spacing=4,\n        align=\"left\",\n        direction=None,\n        features=None,\n        language=None,\n        stroke_width=0,\n        stroke_fill=None,\n        embedded_color=False,\n        *,\n        font_size=None,\n    ) -> None:\n        if direction == \"ttb\":\n            msg = \"ttb direction is unsupported for multiline text\"\n            raise ValueError(msg)\n\n        if anchor is None:\n            anchor = \"la\"\n        elif len(anchor) != 2:\n            msg = \"anchor must be a 2 character string\"\n            raise ValueError(msg)\n        elif anchor[1] in \"tb\":\n            msg = \"anchor not supported for multiline text\"\n            raise ValueError(msg)\n\n        if font is None:\n            font = self._getfont(font_size)\n\n        widths = []\n        max_width = 0\n        lines = self._multiline_split(text)\n        line_spacing = self._multiline_spacing(font, spacing, stroke_width)\n        for line in lines:\n            line_width = self.textlength(\n                line, font, direction=direction, features=features, language=language\n            )\n            widths.append(line_width)\n            max_width = max(max_width, line_width)\n\n        top = xy[1]\n        if anchor[1] == \"m\":\n            top -= (len(lines) - 1) * line_spacing / 2.0\n        elif anchor[1] == \"d\":\n            top -= (len(lines) - 1) * line_spacing\n\n        for idx, line in enumerate(lines):\n            left = xy[0]\n            width_difference = max_width - widths[idx]\n\n            # first align left by anchor\n            if anchor[0] == \"m\":\n                left -= width_difference / 2.0\n            elif anchor[0] == \"r\":\n                left -= width_difference\n\n            # then align by align parameter\n            if align == \"left\":\n                pass\n            elif align == \"center\":\n                left += width_difference / 2.0\n            elif align == \"right\":\n                left += width_difference\n            else:\n                msg = 'align must be \"left\", \"center\" or \"right\"'\n                raise ValueError(msg)\n\n            self.text(\n                (left, top),\n                line,\n                fill,\n                font,\n                anchor,\n                direction=direction,\n                features=features,\n                language=language,\n                stroke_width=stroke_width,\n                stroke_fill=stroke_fill,\n                embedded_color=embedded_color,\n            )\n            top += line_spacing\n\n    def textlength(\n        self,\n        text,\n        font=None,\n        direction=None,\n        features=None,\n        language=None,\n        embedded_color=False,\n        *,\n        font_size=None,\n    ):\n        \"\"\"Get the length of a given string, in pixels with 1/64 precision.\"\"\"\n        if self._multiline_check(text):\n            msg = \"can't measure length of multiline text\"\n            raise ValueError(msg)\n        if embedded_color and self.mode not in (\"RGB\", \"RGBA\"):\n            msg = \"Embedded color supported only in RGB and RGBA modes\"\n            raise ValueError(msg)\n\n        if font is None:\n            font = self._getfont(font_size)\n        mode = \"RGBA\" if embedded_color else self.fontmode\n        return font.getlength(text, mode, direction, features, language)\n\n    def textbbox(\n        self,\n        xy,\n        text,\n        font=None,\n        anchor=None,\n        spacing=4,\n        align=\"left\",\n        direction=None,\n        features=None,\n        language=None,\n        stroke_width=0,\n        embedded_color=False,\n        *,\n        font_size=None,\n    ) -> tuple[int, int, int, int]:\n        \"\"\"Get the bounding box of a given string, in pixels.\"\"\"\n        if embedded_color and self.mode not in (\"RGB\", \"RGBA\"):\n            msg = \"Embedded color supported only in RGB and RGBA modes\"\n            raise ValueError(msg)\n\n        if font is None:\n            font = self._getfont(font_size)\n\n        if self._multiline_check(text):\n            return self.multiline_textbbox(\n                xy,\n                text,\n                font,\n                anchor,\n                spacing,\n                align,\n                direction,\n                features,\n                language,\n                stroke_width,\n                embedded_color,\n            )\n\n        mode = \"RGBA\" if embedded_color else self.fontmode\n        bbox = font.getbbox(\n            text, mode, direction, features, language, stroke_width, anchor\n        )\n        return bbox[0] + xy[0], bbox[1] + xy[1], bbox[2] + xy[0], bbox[3] + xy[1]\n\n    def multiline_textbbox(\n        self,\n        xy,\n        text,\n        font=None,\n        anchor=None,\n        spacing=4,\n        align=\"left\",\n        direction=None,\n        features=None,\n        language=None,\n        stroke_width=0,\n        embedded_color=False,\n        *,\n        font_size=None,\n    ) -> tuple[int, int, int, int]:\n        if direction == \"ttb\":\n            msg = \"ttb direction is unsupported for multiline text\"\n            raise ValueError(msg)\n\n        if anchor is None:\n            anchor = \"la\"\n        elif len(anchor) != 2:\n            msg = \"anchor must be a 2 character string\"\n            raise ValueError(msg)\n        elif anchor[1] in \"tb\":\n            msg = \"anchor not supported for multiline text\"\n            raise ValueError(msg)\n\n        if font is None:\n            font = self._getfont(font_size)\n\n        widths = []\n        max_width = 0\n        lines = self._multiline_split(text)\n        line_spacing = self._multiline_spacing(font, spacing, stroke_width)\n        for line in lines:\n            line_width = self.textlength(\n                line,\n                font,\n                direction=direction,\n                features=features,\n                language=language,\n                embedded_color=embedded_color,\n            )\n            widths.append(line_width)\n            max_width = max(max_width, line_width)\n\n        top = xy[1]\n        if anchor[1] == \"m\":\n            top -= (len(lines) - 1) * line_spacing / 2.0\n        elif anchor[1] == \"d\":\n            top -= (len(lines) - 1) * line_spacing\n\n        bbox: tuple[int, int, int, int] | None = None\n\n        for idx, line in enumerate(lines):\n            left = xy[0]\n            width_difference = max_width - widths[idx]\n\n            # first align left by anchor\n            if anchor[0] == \"m\":\n                left -= width_difference / 2.0\n            elif anchor[0] == \"r\":\n                left -= width_difference\n\n            # then align by align parameter\n            if align == \"left\":\n                pass\n            elif align == \"center\":\n                left += width_difference / 2.0\n            elif align == \"right\":\n                left += width_difference\n            else:\n                msg = 'align must be \"left\", \"center\" or \"right\"'\n                raise ValueError(msg)\n\n            bbox_line = self.textbbox(\n                (left, top),\n                line,\n                font,\n                anchor,\n                direction=direction,\n                features=features,\n                language=language,\n                stroke_width=stroke_width,\n                embedded_color=embedded_color,\n            )\n            if bbox is None:\n                bbox = bbox_line\n            else:\n                bbox = (\n                    min(bbox[0], bbox_line[0]),\n                    min(bbox[1], bbox_line[1]),\n                    max(bbox[2], bbox_line[2]),\n                    max(bbox[3], bbox_line[3]),\n                )\n\n            top += line_spacing\n\n        if bbox is None:\n            return xy[0], xy[1], xy[0], xy[1]\n        return bbox\n\n\ndef Draw(im, mode: str | None = None) -> ImageDraw:\n    \"\"\"\n    A simple 2D drawing interface for PIL images.\n\n    :param im: The image to draw in.\n    :param mode: Optional mode to use for color values.  For RGB\n       images, this argument can be RGB or RGBA (to blend the\n       drawing into the image).  For all other modes, this argument\n       must be the same as the image mode.  If omitted, the mode\n       defaults to the mode of the image.\n    \"\"\"\n    try:\n        return im.getdraw(mode)\n    except AttributeError:\n        return ImageDraw(im, mode)\n\n\n# experimental access to the outline API\ntry:\n    Outline = Image.core.outline\nexcept AttributeError:\n    Outline = None\n\n\ndef getdraw(im=None, hints=None):\n    \"\"\"\n    (Experimental) A more advanced 2D drawing interface for PIL images,\n    based on the WCK interface.\n\n    :param im: The image to draw in.\n    :param hints: An optional list of hints.\n    :returns: A (drawing context, drawing resource factory) tuple.\n    \"\"\"\n    # FIXME: this needs more work!\n    # FIXME: come up with a better 'hints' scheme.\n    handler = None\n    if not hints or \"nicest\" in hints:\n        try:\n            from . import _imagingagg as handler\n        except ImportError:\n            pass\n    if handler is None:\n        from . import ImageDraw2 as handler\n    if im:\n        im = handler.Draw(im)\n    return im, handler\n\n\ndef floodfill(image: Image.Image, xy, value, border=None, thresh=0) -> None:\n    \"\"\"\n    (experimental) Fills a bounded region with a given color.\n\n    :param image: Target image.\n    :param xy: Seed position (a 2-item coordinate tuple). See\n        :ref:`coordinate-system`.\n    :param value: Fill color.\n    :param border: Optional border value.  If given, the region consists of\n        pixels with a color different from the border color.  If not given,\n        the region consists of pixels having the same color as the seed\n        pixel.\n    :param thresh: Optional threshold value which specifies a maximum\n        tolerable difference of a pixel value from the 'background' in\n        order for it to be replaced. Useful for filling regions of\n        non-homogeneous, but similar, colors.\n    \"\"\"\n    # based on an implementation by Eric S. Raymond\n    # amended by yo1995 @20180806\n    pixel = image.load()\n    x, y = xy\n    try:\n        background = pixel[x, y]\n        if _color_diff(value, background) <= thresh:\n            return  # seed point already has fill color\n        pixel[x, y] = value\n    except (ValueError, IndexError):\n        return  # seed point outside image\n    edge = {(x, y)}\n    # use a set to keep record of current and previous edge pixels\n    # to reduce memory consumption\n    full_edge = set()\n    while edge:\n        new_edge = set()\n        for x, y in edge:  # 4 adjacent method\n            for s, t in ((x + 1, y), (x - 1, y), (x, y + 1), (x, y - 1)):\n                # If already processed, or if a coordinate is negative, skip\n                if (s, t) in full_edge or s < 0 or t < 0:\n                    continue\n                try:\n                    p = pixel[s, t]\n                except (ValueError, IndexError):\n                    pass\n                else:\n                    full_edge.add((s, t))\n                    if border is None:\n                        fill = _color_diff(p, background) <= thresh\n                    else:\n                        fill = p not in (value, border)\n                    if fill:\n                        pixel[s, t] = value\n                        new_edge.add((s, t))\n        full_edge = edge  # discard pixels processed\n        edge = new_edge\n\n\ndef _compute_regular_polygon_vertices(\n    bounding_circle, n_sides, rotation\n) -> list[tuple[float, float]]:\n    \"\"\"\n    Generate a list of vertices for a 2D regular polygon.\n\n    :param bounding_circle: The bounding circle is a tuple defined\n        by a point and radius. The polygon is inscribed in this circle.\n        (e.g. ``bounding_circle=(x, y, r)`` or ``((x, y), r)``)\n    :param n_sides: Number of sides\n        (e.g. ``n_sides=3`` for a triangle, ``6`` for a hexagon)\n    :param rotation: Apply an arbitrary rotation to the polygon\n        (e.g. ``rotation=90``, applies a 90 degree rotation)\n    :return: List of regular polygon vertices\n        (e.g. ``[(25, 50), (50, 50), (50, 25), (25, 25)]``)\n\n    How are the vertices computed?\n    1. Compute the following variables\n        - theta: Angle between the apothem & the nearest polygon vertex\n        - side_length: Length of each polygon edge\n        - centroid: Center of bounding circle (1st, 2nd elements of bounding_circle)\n        - polygon_radius: Polygon radius (last element of bounding_circle)\n        - angles: Location of each polygon vertex in polar grid\n            (e.g. A square with 0 degree rotation => [225.0, 315.0, 45.0, 135.0])\n\n    2. For each angle in angles, get the polygon vertex at that angle\n        The vertex is computed using the equation below.\n            X= xcos(\u03c6) + ysin(\u03c6)\n            Y= \u2212xsin(\u03c6) + ycos(\u03c6)\n\n        Note:\n            \u03c6 = angle in degrees\n            x = 0\n            y = polygon_radius\n\n        The formula above assumes rotation around the origin.\n        In our case, we are rotating around the centroid.\n        To account for this, we use the formula below\n            X = xcos(\u03c6) + ysin(\u03c6) + centroid_x\n            Y = \u2212xsin(\u03c6) + ycos(\u03c6) + centroid_y\n    \"\"\"\n    # 1. Error Handling\n    # 1.1 Check `n_sides` has an appropriate value\n    if not isinstance(n_sides, int):\n        msg = \"n_sides should be an int\"\n        raise TypeError(msg)\n    if n_sides < 3:\n        msg = \"n_sides should be an int > 2\"\n        raise ValueError(msg)\n\n    # 1.2 Check `bounding_circle` has an appropriate value\n    if not isinstance(bounding_circle, (list, tuple)):\n        msg = \"bounding_circle should be a sequence\"\n        raise TypeError(msg)\n\n    if len(bounding_circle) == 3:\n        *centroid, polygon_radius = bounding_circle\n    elif len(bounding_circle) == 2:\n        centroid, polygon_radius = bounding_circle\n    else:\n        msg = (\n            \"bounding_circle should contain 2D coordinates \"\n            \"and a radius (e.g. (x, y, r) or ((x, y), r) )\"\n        )\n        raise ValueError(msg)\n\n    if not all(isinstance(i, (int, float)) for i in (*centroid, polygon_radius)):\n        msg = \"bounding_circle should only contain numeric data\"\n        raise ValueError(msg)\n\n    if not len(centroid) == 2:\n        msg = \"bounding_circle centre should contain 2D coordinates (e.g. (x, y))\"\n        raise ValueError(msg)\n\n    if polygon_radius <= 0:\n        msg = \"bounding_circle radius should be > 0\"\n        raise ValueError(msg)\n\n    # 1.3 Check `rotation` has an appropriate value\n    if not isinstance(rotation, (int, float)):\n        msg = \"rotation should be an int or float\"\n        raise ValueError(msg)\n\n    # 2. Define Helper Functions\n    def _apply_rotation(point: list[float], degrees: float) -> tuple[int, int]:\n        return (\n            round(\n                point[0] * math.cos(math.radians(360 - degrees))\n                - point[1] * math.sin(math.radians(360 - degrees))\n                + centroid[0],\n                2,\n            ),\n            round(\n                point[1] * math.cos(math.radians(360 - degrees))\n                + point[0] * math.sin(math.radians(360 - degrees))\n                + centroid[1],\n                2,\n            ),\n        )\n\n    def _compute_polygon_vertex(angle: float) -> tuple[int, int]:\n        start_point = [polygon_radius, 0]\n        return _apply_rotation(start_point, angle)\n\n    def _get_angles(n_sides: int, rotation: float) -> list[float]:\n        angles = []\n        degrees = 360 / n_sides\n        # Start with the bottom left polygon vertex\n        current_angle = (270 - 0.5 * degrees) + rotation\n        for _ in range(0, n_sides):\n            angles.append(current_angle)\n            current_angle += degrees\n            if current_angle > 360:\n                current_angle -= 360\n        return angles\n\n    # 3. Variable Declarations\n    angles = _get_angles(n_sides, rotation)\n\n    # 4. Compute Vertices\n    return [_compute_polygon_vertex(angle) for angle in angles]\n\n\ndef _color_diff(color1, color2: float | tuple[int, ...]) -> float:\n    \"\"\"\n    Uses 1-norm distance to calculate difference between two values.\n    \"\"\"\n    if isinstance(color2, tuple):\n        return sum(abs(color1[i] - color2[i]) for i in range(0, len(color2)))\n    else:\n        return abs(color1 - color2)\n"}
A user will ask you to solve a task. You should first draft your thinking process (inner monologue). Then, generate the solution.

Your response format must follow the template below:
<think>
Your thoughts or/and draft, like working through an exercise on scratch paper. Be as casual and as long as you want until you are confident to generate a correct solution.
</think>
<solution>
Final solution presented to the user.
</solution>
system
We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN ISSUE ---
[ISSUE]
**Title:** `ImageDraw` object has no attribute `circle` when attempting to draw a circle

**Description:**
When trying to draw a circle using the `ImageDraw` class, an `AttributeError` is raised because the `circle` method does not exist.

**Example Code:**
```python
from PIL import Image, ImageDraw

# Create a new image
im = Image.new("RGB", (100, 100))
draw = ImageDraw.Draw(im)

# Attempt to draw a circle
draw.circle((50, 50), 25, fill="green", outline="blue")

# Save the image
im.save("circle.png")
```

**Expected Behavior:**
A green circle with a blue outline should be drawn at the center of the image without any errors.

**Actual Behavior:**
An `AttributeError` is raised:
```
AttributeError: 'ImageDraw' object has no attribute 'circle'
```
This prevents the circle from being drawn and the image from being saved correctly.
[/ISSUE]
--- END ISSUE ---

Below are some code segments, each from a relevant file. One or more of these files may contain bugs.

--- BEGIN FILE ---
```
### src/PIL/ImageDraw.py
#
# The Python Imaging Library
# $Id$
#
# drawing interface operations
#
# History:
# 1996-04-13 fl   Created (experimental)
# 1996-08-07 fl   Filled polygons, ellipses.
# 1996-08-13 fl   Added text support
# 1998-06-28 fl   Handle I and F images
# 1998-12-29 fl   Added arc; use arc primitive to draw ellipses
# 1999-01-10 fl   Added shape stuff (experimental)
# 1999-02-06 fl   Added bitmap support
# 1999-02-11 fl   Changed all primitives to take options
# 1999-02-20 fl   Fixed backwards compatibility
# 2000-10-12 fl   Copy on write, when necessary
# 2001-02-18 fl   Use default ink for bitmap/text also in fill mode
# 2002-10-24 fl   Added support for CSS-style color strings
# 2002-12-10 fl   Added experimental support for RGBA-on-RGB drawing
# 2002-12-11 fl   Refactored low-level drawing API (work in progress)
# 2004-08-26 fl   Made Draw() a factory function, added getdraw() support
# 2004-09-04 fl   Added width support to line primitive
# 2004-09-10 fl   Added font mode handling
# 2006-06-19 fl   Added font bearing support (getmask2)
#
# Copyright (c) 1997-2006 by Secret Labs AB
# Copyright (c) 1996-2006 by Fredrik Lundh
#
# See the README file for information on usage and redistribution.
#
from __future__ import annotations

import math
import numbers
import struct
from typing import TYPE_CHECKING, Sequence, cast

from . import Image, ImageColor
from ._typing import Coords

"""
A simple 2D drawing interface for PIL images.
<p>
Application code should use the <b>Draw</b> factory, instead of
directly.
"""


class ImageDraw:
    font = None

    def __init__(self, im: Image.Image, mode: str | None = None) -> None:
        """
        Create a drawing instance.

        :param im: The image to draw in.
        :param mode: Optional mode to use for color values.  For RGB
           images, this argument can be RGB or RGBA (to blend the
           drawing into the image).  For all other modes, this argument
           must be the same as the image mode.  If omitted, the mode
           defaults to the mode of the image.
        """
        im.load()
        if im.readonly:
            im._copy()  # make it writeable
        blend = 0
        if mode is None:
            mode = im.mode
        if mode != im.mode:
            if mode == "RGBA" and im.mode == "RGB":
                blend = 1
            else:
                msg = "mode mismatch"
                raise ValueError(msg)
        if mode == "P":
            self.palette = im.palette
        else:
            self.palette = None
        self._image = im
        self.im = im.im
        self.draw = Image.core.draw(self.im, blend)
        self.mode = mode
        if mode in ("I", "F"):
            self.ink = self.draw.draw_ink(1)
        else:
            self.ink = self.draw.draw_ink(-1)
        if mode in ("1", "P", "I", "F"):
            # FIXME: fix Fill2 to properly support matte for I+F images
            self.fontmode = "1"
        else:
            self.fontmode = "L"  # aliasing is okay for other modes
        self.fill = False

    if TYPE_CHECKING:
        from . import ImageFont

    def getfont(self) -> ImageFont.FreeTypeFont | ImageFont.ImageFont:
        """
        Get the current default font.

        To set the default font for this ImageDraw instance::

            from PIL import ImageDraw, ImageFont
            draw.font = ImageFont.truetype("Tests/fonts/FreeMono.ttf")

        To set the default font for all future ImageDraw instances::

            from PIL import ImageDraw, ImageFont
            ImageDraw.ImageDraw.font = ImageFont.truetype("Tests/fonts/FreeMono.ttf")

        If the current default font is ``None``,
        it is initialized with ``ImageFont.load_default()``.

        :returns: An image font."""
        if not self.font:
            # FIXME: should add a font repository
            from . import ImageFont

            self.font = ImageFont.load_default()
        return self.font

    def _getfont(self, font_size: float | None):
        if font_size is not None:
            from . import ImageFont

            font = ImageFont.load_default(font_size)
        else:
            font = self.getfont()
        return font

    def _getink(self, ink, fill=None) -> tuple[int | None, int | None]:
        if ink is None and fill is None:
            if self.fill:
                fill = self.ink
            else:
                ink = self.ink
        else:
            if ink is not None:
                if isinstance(ink, str):
                    ink = ImageColor.getcolor(ink, self.mode)
                if self.palette and not isinstance(ink, numbers.Number):
                    ink = self.palette.getcolor(ink, self._image)
                ink = self.draw.draw_ink(ink)
            if fill is not None:
                if isinstance(fill, str):
                    fill = ImageColor.getcolor(fill, self.mode)
                if self.palette and not isinstance(fill, numbers.Number):
                    fill = self.palette.getcolor(fill, self._image)
                fill = self.draw.draw_ink(fill)
        return ink, fill

    def arc(self, xy: Coords, start, end, fill=None, width=1) -> None:
        """Draw an arc."""
        ink, fill = self._getink(fill)
        if ink is not None:
            self.draw.draw_arc(xy, start, end, ink, width)

    def bitmap(self, xy: Sequence[int], bitmap, fill=None) -> None:
        """Draw a bitmap."""
        bitmap.load()
        ink, fill = self._getink(fill)
        if ink is None:
            ink = fill
        if ink is not None:
            self.draw.draw_bitmap(xy, bitmap.im, ink)

    def chord(self, xy: Coords, start, end, fill=None, outline=None, width=1) -> None:
        """Draw a chord."""
        ink, fill = self._getink(outline, fill)
        if fill is not None:
            self.draw.draw_chord(xy, start, end, fill, 1)
        if ink is not None and ink != fill and width != 0:
            self.draw.draw_chord(xy, start, end, ink, 0, width)

    def ellipse(self, xy: Coords, fill=None, outline=None, width=1) -> None:
        """Draw an ellipse."""
        ink, fill = self._getink(outline, fill)
        if fill is not None:
            self.draw.draw_ellipse(xy, fill, 1)
        if ink is not None and ink != fill and width != 0:
            self.draw.draw_ellipse(xy, ink, 0, width)

    def line(self, xy: Coords, fill=None, width=0, joint=None) -> None:
        """Draw a line, or a connected sequence of line segments."""
        ink = self._getink(fill)[0]
        if ink is not None:
            self.draw.draw_lines(xy, ink, width)
            if joint == "curve" and width > 4:
                points: Sequence[Sequence[float]]
                if isinstance(xy[0], (list, tuple)):
                    points = cast(Sequence[Sequence[float]], xy)
                else:
                    points = [
                        cast(Sequence[float], tuple(xy[i : i + 2]))
                        for i in range(0, len(xy), 2)
                    ]
                for i in range(1, len(points) - 1):
                    point = points[i]
                    angles = [
                        math.degrees(math.atan2(end[0] - start[0], start[1] - end[1]))
                        % 360
                        for start, end in (
                            (points[i - 1], point),
                            (point, points[i + 1]),
                        )
                    ]
                    if angles[0] == angles[1]:
                        # This is a straight line, so no joint is required
                        continue

                    def coord_at_angle(coord, angle):
                        x, y = coord
                        angle -= 90
                        distance = width / 2 - 1
                        return tuple(
                            p + (math.floor(p_d) if p_d > 0 else math.ceil(p_d))
                            for p, p_d in (
                                (x, distance * math.cos(math.radians(angle))),
                                (y, distance * math.sin(math.radians(angle))),
                            )
                        )

                    flipped = (
                        angles[1] > angles[0] and angles[1] - 180 > angles[0]
                    ) or (angles[1] < angles[0] and angles[1] + 180 > angles[0])
                    coords = [
                        (point[0] - width / 2 + 1, point[1] - width / 2 + 1),
                        (point[0] + width / 2 - 1, point[1] + width / 2 - 1),
                    ]
                    if flipped:
                        start, end = (angles[1] + 90, angles[0] + 90)
                    else:
                        start, end = (angles[0] - 90, angles[1] - 90)
                    self.pieslice(coords, start - 90, end - 90, fill)

                    if width > 8:
                        # Cover potential gaps between the line and the joint
                        if flipped:
                            gap_coords = [
                                coord_at_angle(point, angles[0] + 90),
                                point,
                                coord_at_angle(point, angles[1] + 90),
                            ]
                        else:
                            gap_coords = [
                                coord_at_angle(point, angles[0] - 90),
                                point,
                                coord_at_angle(point, angles[1] - 90),
                            ]
                        self.line(gap_coords, fill, width=3)

    def shape(self, shape, fill=None, outline=None) -> None:
        """(Experimental) Draw a shape."""
        shape.close()
        ink, fill = self._getink(outline, fill)
        if fill is not None:
            self.draw.draw_outline(shape, fill, 1)
        if ink is not None and ink != fill:
            self.draw.draw_outline(shape, ink, 0)

    def pieslice(
        self, xy: Coords, start, end, fill=None, outline=None, width=1
    ) -> None:
        """Draw a pieslice."""
        ink, fill = self._getink(outline, fill)
        if fill is not None:
            self.draw.draw_pieslice(xy, start, end, fill, 1)
        if ink is not None and ink != fill and width != 0:
            self.draw.draw_pieslice(xy, start, end, ink, 0, width)

    def point(self, xy: Coords, fill=None) -> None:
        """Draw one or more individual pixels."""
        ink, fill = self._getink(fill)
        if ink is not None:
            self.draw.draw_points(xy, ink)

    def polygon(self, xy: Coords, fill=None, outline=None, width=1) -> None:
        """Draw a polygon."""
        ink, fill = self._getink(outline, fill)
        if fill is not None:
            self.draw.draw_polygon(xy, fill, 1)
        if ink is not None and ink != fill and width != 0:
            if width == 1:
                self.draw.draw_polygon(xy, ink, 0, width)
            elif self.im is not None:
                # To avoid expanding the polygon outwards,
                # use the fill as a mask
                mask = Image.new("1", self.im.size)
                mask_ink = self._getink(1)[0]

                fill_im = mask.copy()
                draw = Draw(fill_im)
                draw.draw.draw_polygon(xy, mask_ink, 1)

                ink_im = mask.copy()
                draw = Draw(ink_im)
                width = width * 2 - 1
                draw.draw.draw_polygon(xy, mask_ink, 0, width)

                mask.paste(ink_im, mask=fill_im)

                im = Image.new(self.mode, self.im.size)
                draw = Draw(im)
                draw.draw.draw_polygon(xy, ink, 0, width)
                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, width=1
    ) -> None:
        """Draw a regular polygon."""
        xy = _compute_regular_polygon_vertices(bounding_circle, n_sides, rotation)
        self.polygon(xy, fill, outline, width)

    def rectangle(self, xy: Coords, fill=None, outline=None, width=1) -> None:
        """Draw a rectangle."""
        ink, fill = self._getink(outline, fill)
        if fill is not None:
            self.draw.draw_rectangle(xy, fill, 1)
        if ink is not None and ink != fill and width != 0:
            self.draw.draw_rectangle(xy, ink, 0, width)

    def rounded_rectangle(
        self, xy: Coords, radius=0, fill=None, outline=None, width=1, *, corners=None
    ) -> None:
        """Draw a rounded rectangle."""
        if isinstance(xy[0], (list, tuple)):
            (x0, y0), (x1, y1) = cast(Sequence[Sequence[float]], xy)
        else:
            x0, y0, x1, y1 = cast(Sequence[float], xy)
        if x1 < x0:
            msg = "x1 must be greater than or equal to x0"
            raise ValueError(msg)
        if y1 < y0:
            msg = "y1 must be greater than or equal to y0"
            raise ValueError(msg)
        if corners is None:
            corners = (True, True, True, True)

        d = radius * 2

        x0 = round(x0)
        y0 = round(y0)
        x1 = round(x1)
        y1 = round(y1)
        full_x, full_y = False, False
        if all(corners):
            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 - 1
            if full_y:
                # The two top and two bottom corners are joined
                d = y1 - y0
            if full_x and full_y:
                # If all corners are joined, that is a circle
                return self.ellipse(xy, fill, outline, width)

        if d == 0 or not any(corners):
            # If the corners have no curve,
            # or there are no corners,
            # that is a rectangle
            return self.rectangle(xy, fill, outline, width)

        r = d // 2
        ink, fill = self._getink(outline, fill)

        def draw_corners(pieslice) -> None:
            parts: tuple[tuple[tuple[float, float, float, float], int, int], ...]
            if full_x:
                # Draw top and bottom halves
                parts = (
                    ((x0, y0, x0 + d, y0 + d), 180, 360),
                    ((x0, y1 - d, x0 + d, y1), 0, 180),
                )
            elif full_y:
                # Draw left and right halves
                parts = (
                    ((x0, y0, x0 + d, y0 + d), 90, 270),
                    ((x1 - d, y0, x1, y0 + d), 270, 90),
                )
            else:
                # Draw four separate corners
                parts = tuple(
                    part
                    for i, part in enumerate(
                        (
                            ((x0, y0, x0 + d, y0 + d), 180, 270),
                            ((x1 - d, y0, x1, y0 + d), 270, 360),
                            ((x1 - d, y1 - d, x1, y1), 0, 90),
                            ((x0, y1 - d, x0 + d, y1), 90, 180),
                        )
                    )
                    if corners[i]
                )
            for part in parts:
                if pieslice:
                    self.draw.draw_pieslice(*(part + (fill, 1)))
                else:
                    self.draw.draw_arc(*(part + (ink, width)))

        if fill is not None:
            draw_corners(True)

            if full_x:
                self.draw.draw_rectangle((x0, y0 + r + 1, x1, y1 - r - 1), fill, 1)
            else:
                self.draw.draw_rectangle((x0 + r + 1, y0, x1 - r - 1, y1), fill, 1)
            if not full_x and not full_y:
                left = [x0, y0, x0 + r, y1]
                if corners[0]:
                    left[1] += r + 1
                if corners[3]:
                    left[3] -= r + 1
                self.draw.draw_rectangle(left, fill, 1)

                right = [x1 - r, y0, x1, y1]
                if corners[1]:
                    right[1] += r + 1
                if corners[2]:
                    right[3] -= r + 1
                self.draw.draw_rectangle(right, fill, 1)
        if ink is not None and ink != fill and width != 0:
            draw_corners(False)

            if not full_x:
                top = [x0, y0, x1, y0 + width - 1]
                if corners[0]:
                    top[0] += r + 1
                if corners[1]:
                    top[2] -= r + 1
                self.draw.draw_rectangle(top, ink, 1)

                bottom = [x0, y1 - width + 1, x1, y1]
                if corners[3]:
                    bottom[0] += r + 1
                if corners[2]:
                    bottom[2] -= r + 1
                self.draw.draw_rectangle(bottom, ink, 1)
            if not full_y:
                left = [x0, y0, x0 + width - 1, y1]
                if corners[0]:
                    left[1] += r + 1
                if corners[3]:
                    left[3] -= r + 1
                self.draw.draw_rectangle(left, ink, 1)

                right = [x1 - width + 1, y0, x1, y1]
                if corners[1]:
                    right[1] += r + 1
                if corners[2]:
                    right[3] -= r + 1
                self.draw.draw_rectangle(right, ink, 1)

    def _multiline_check(self, text) -> bool:
        split_character = "\n" if isinstance(text, str) else b"\n"

        return split_character in text

    def _multiline_split(self, text) -> list[str | bytes]:
        split_character = "\n" if isinstance(text, str) else b"\n"

        return text.split(split_character)

    def _multiline_spacing(self, font, spacing, stroke_width):
        return (
            self.textbbox((0, 0), "A", font, stroke_width=stroke_width)[3]
            + stroke_width
            + spacing
        )

    def text(
        self,
        xy,
        text,
        fill=None,
        font=None,
        anchor=None,
        spacing=4,
        align="left",
        direction=None,
        features=None,
        language=None,
        stroke_width=0,
        stroke_fill=None,
        embedded_color=False,
        *args,
        **kwargs,
    ) -> None:
        """Draw text."""
        if embedded_color and self.mode not in ("RGB", "RGBA"):
            msg = "Embedded color supported only in RGB and RGBA modes"
            raise ValueError(msg)

        if font is None:
            font = self._getfont(kwargs.get("font_size"))

        if self._multiline_check(text):
            return self.multiline_text(
                xy,
                text,
                fill,
                font,
                anchor,
                spacing,
                align,
                direction,
                features,
                language,
                stroke_width,
                stroke_fill,
                embedded_color,
            )

        def getink(fill):
            ink, fill = self._getink(fill)
            if ink is None:
                return fill
            return ink

        def draw_text(ink, stroke_width=0, stroke_offset=None) -> None:
            mode = self.fontmode
            if stroke_width == 0 and embedded_color:
                mode = "RGBA"
            coord = []
            start = []
            for i in range(2):
                coord.append(int(xy[i]))
                start.append(math.modf(xy[i])[0])
            try:
                mask, offset = font.getmask2(
                    text,
                    mode,
                    direction=direction,
                    features=features,
                    language=language,
                    stroke_width=stroke_width,
                    anchor=anchor,
                    ink=ink,
                    start=start,
                    *args,
                    **kwargs,
                )
                coord = [coord[0] + offset[0], coord[1] + offset[1]]
            except AttributeError:
                try:
                    mask = font.getmask(
                        text,
                        mode,
                        direction,
                        features,
                        language,
                        stroke_width,
                        anchor,
                        ink,
                        start=start,
                        *args,
                        **kwargs,
                    )
                except TypeError:
                    mask = font.getmask(text)
            if stroke_offset:
                coord = [coord[0] + stroke_offset[0], coord[1] + stroke_offset[1]]
            if mode == "RGBA":
                # font.getmask2(mode="RGBA") returns color in RGB bands and mask in A
                # extract mask and set text alpha
                color, mask = mask, mask.getband(3)
                ink_alpha = struct.pack("i", ink)[3]
                color.fillband(3, ink_alpha)
                x, y = coord
                if self.im is not None:
                    self.im.paste(
                        color, (x, y, x + mask.size[0], y + mask.size[1]), mask
                    )
            else:
                self.draw.draw_bitmap(coord, mask, ink)

        ink = getink(fill)
        if ink is not None:
            stroke_ink = None
            if stroke_width:
                stroke_ink = getink(stroke_fill) if stroke_fill is not None else ink

            if stroke_ink is not None:
                # Draw stroked text
                draw_text(stroke_ink, stroke_width)

                # Draw normal text
                draw_text(ink, 0)
            else:
                # Only draw normal text
                draw_text(ink)

    def multiline_text(
        self,
        xy,
        text,
        fill=None,
        font=None,
        anchor=None,
        spacing=4,
        align="left",
        direction=None,
        features=None,
        language=None,
        stroke_width=0,
        stroke_fill=None,
        embedded_color=False,
        *,
        font_size=None,
    ) -> None:
        if direction == "ttb":
            msg = "ttb direction is unsupported for multiline text"
            raise ValueError(msg)

        if anchor is None:
            anchor = "la"
        elif len(anchor) != 2:
            msg = "anchor must be a 2 character string"
            raise ValueError(msg)
        elif anchor[1] in "tb":
            msg = "anchor not supported for multiline text"
            raise ValueError(msg)

        if font is None:
            font = self._getfont(font_size)

        widths = []
        max_width = 0
        lines = self._multiline_split(text)
        line_spacing = self._multiline_spacing(font, spacing, stroke_width)
        for line in lines:
            line_width = self.textlength(
                line, font, direction=direction, features=features, language=language
            )
            widths.append(line_width)
            max_width = max(max_width, line_width)

        top = xy[1]
        if anchor[1] == "m":
            top -= (len(lines) - 1) * line_spacing / 2.0
        elif anchor[1] == "d":
            top -= (len(lines) - 1) * line_spacing

        for idx, line in enumerate(lines):
            left = xy[0]
            width_difference = max_width - widths[idx]

            # first align left by anchor
            if anchor[0] == "m":
                left -= width_difference / 2.0
            elif anchor[0] == "r":
                left -= width_difference

            # then align by align parameter
            if align == "left":
                pass
            elif align == "center":
                left += width_difference / 2.0
            elif align == "right":
                left += width_difference
            else:
                msg = 'align must be "left", "center" or "right"'
                raise ValueError(msg)

            self.text(
                (left, top),
                line,
                fill,
                font,
                anchor,
                direction=direction,
                features=features,
                language=language,
                stroke_width=stroke_width,
                stroke_fill=stroke_fill,
                embedded_color=embedded_color,
            )
            top += line_spacing

    def textlength(
        self,
        text,
        font=None,
        direction=None,
        features=None,
        language=None,
        embedded_color=False,
        *,
        font_size=None,
    ):
        """Get the length of a given string, in pixels with 1/64 precision."""
        if self._multiline_check(text):
            msg = "can't measure length of multiline text"
            raise ValueError(msg)
        if embedded_color and self.mode not in ("RGB", "RGBA"):
            msg = "Embedded color supported only in RGB and RGBA modes"
            raise ValueError(msg)

        if font is None:
            font = self._getfont(font_size)
        mode = "RGBA" if embedded_color else self.fontmode
        return font.getlength(text, mode, direction, features, language)

    def textbbox(
        self,
        xy,
        text,
        font=None,
        anchor=None,
        spacing=4,
        align="left",
        direction=None,
        features=None,
        language=None,
        stroke_width=0,
        embedded_color=False,
        *,
        font_size=None,
    ) -> tuple[int, int, int, int]:
        """Get the bounding box of a given string, in pixels."""
        if embedded_color and self.mode not in ("RGB", "RGBA"):
            msg = "Embedded color supported only in RGB and RGBA modes"
            raise ValueError(msg)

        if font is None:
            font = self._getfont(font_size)

        if self._multiline_check(text):
            return self.multiline_textbbox(
                xy,
                text,
                font,
                anchor,
                spacing,
                align,
                direction,
                features,
                language,
                stroke_width,
                embedded_color,
            )

        mode = "RGBA" if embedded_color else self.fontmode
        bbox = font.getbbox(
            text, mode, direction, features, language, stroke_width, anchor
        )
        return bbox[0] + xy[0], bbox[1] + xy[1], bbox[2] + xy[0], bbox[3] + xy[1]

    def multiline_textbbox(
        self,
        xy,
        text,
        font=None,
        anchor=None,
        spacing=4,
        align="left",
        direction=None,
        features=None,
        language=None,
        stroke_width=0,
        embedded_color=False,
        *,
        font_size=None,
    ) -> tuple[int, int, int, int]:
        if direction == "ttb":
            msg = "ttb direction is unsupported for multiline text"
            raise ValueError(msg)

        if anchor is None:
            anchor = "la"
        elif len(anchor) != 2:
            msg = "anchor must be a 2 character string"
            raise ValueError(msg)
        elif anchor[1] in "tb":
            msg = "anchor not supported for multiline text"
            raise ValueError(msg)

        if font is None:
            font = self._getfont(font_size)

        widths = []
        max_width = 0
        lines = self._multiline_split(text)
        line_spacing = self._multiline_spacing(font, spacing, stroke_width)
        for line in lines:
            line_width = self.textlength(
                line,
                font,
                direction=direction,
                features=features,
                language=language,
                embedded_color=embedded_color,
            )
            widths.append(line_width)
            max_width = max(max_width, line_width)

        top = xy[1]
        if anchor[1] == "m":
            top -= (len(lines) - 1) * line_spacing / 2.0
        elif anchor[1] == "d":
            top -= (len(lines) - 1) * line_spacing

        bbox: tuple[int, int, int, int] | None = None

        for idx, line in enumerate(lines):
            left = xy[0]
            width_difference = max_width - widths[idx]

            # first align left by anchor
            if anchor[0] == "m":
                left -= width_difference / 2.0
            elif anchor[0] == "r":
                left -= width_difference

            # then align by align parameter
            if align == "left":
                pass
            elif align == "center":
                left += width_difference / 2.0
            elif align == "right":
                left += width_difference
            else:
                msg = 'align must be "left", "center" or "right"'
                raise ValueError(msg)

            bbox_line = self.textbbox(
                (left, top),
                line,
                font,
                anchor,
                direction=direction,
                features=features,
                language=language,
                stroke_width=stroke_width,
                embedded_color=embedded_color,
            )
            if bbox is None:
                bbox = bbox_line
            else:
                bbox = (
                    min(bbox[0], bbox_line[0]),
                    min(bbox[1], bbox_line[1]),
                    max(bbox[2], bbox_line[2]),
                    max(bbox[3], bbox_line[3]),
                )

            top += line_spacing

        if bbox is None:
            return xy[0], xy[1], xy[0], xy[1]
        return bbox


def Draw(im, mode: str | None = None) -> ImageDraw:
    """
    A simple 2D drawing interface for PIL images.

    :param im: The image to draw in.
    :param mode: Optional mode to use for color values.  For RGB
       images, this argument can be RGB or RGBA (to blend the
       drawing into the image).  For all other modes, this argument
       must be the same as the image mode.  If omitted, the mode
       defaults to the mode of the image.
    """
    try:
        return im.getdraw(mode)
    except AttributeError:
        return ImageDraw(im, mode)


# experimental access to the outline API
try:
    Outline = Image.core.outline
except AttributeError:
    Outline = None


def getdraw(im=None, hints=None):
    """
    (Experimental) A more advanced 2D drawing interface for PIL images,
    based on the WCK interface.

    :param im: The image to draw in.
    :param hints: An optional list of hints.
    :returns: A (drawing context, drawing resource factory) tuple.
    """
    # FIXME: this needs more work!
    # FIXME: come up with a better 'hints' scheme.
    handler = None
    if not hints or "nicest" in hints:
        try:
            from . import _imagingagg as handler
        except ImportError:
            pass
    if handler is None:
        from . import ImageDraw2 as handler
    if im:
        im = handler.Draw(im)
    return im, handler


def floodfill(image: Image.Image, xy, value, border=None, thresh=0) -> None:
    """
    (experimental) Fills a bounded region with a given color.

    :param image: Target image.
    :param xy: Seed position (a 2-item coordinate tuple). See
        :ref:`coordinate-system`.
    :param value: Fill color.
    :param border: Optional border value.  If given, the region consists of
        pixels with a color different from the border color.  If not given,
        the region consists of pixels having the same color as the seed
        pixel.
    :param thresh: Optional threshold value which specifies a maximum
        tolerable difference of a pixel value from the 'background' in
        order for it to be replaced. Useful for filling regions of
        non-homogeneous, but similar, colors.
    """
    # based on an implementation by Eric S. Raymond
    # amended by yo1995 @20180806
    pixel = image.load()
    x, y = xy
    try:
        background = pixel[x, y]
        if _color_diff(value, background) <= thresh:
            return  # seed point already has fill color
        pixel[x, y] = value
    except (ValueError, IndexError):
        return  # seed point outside image
    edge = {(x, y)}
    # use a set to keep record of current and previous edge pixels
    # to reduce memory consumption
    full_edge = set()
    while edge:
        new_edge = set()
        for x, y in edge:  # 4 adjacent method
            for s, t in ((x + 1, y), (x - 1, y), (x, y + 1), (x, y - 1)):
                # If already processed, or if a coordinate is negative, skip
                if (s, t) in full_edge or s < 0 or t < 0:
                    continue
                try:
                    p = pixel[s, t]
                except (ValueError, IndexError):
                    pass
                else:
                    full_edge.add((s, t))
                    if border is None:
                        fill = _color_diff(p, background) <= thresh
                    else:
                        fill = p not in (value, border)
                    if fill:
                        pixel[s, t] = value
                        new_edge.add((s, t))
        full_edge = edge  # discard pixels processed
        edge = new_edge


def _compute_regular_polygon_vertices(
    bounding_circle, n_sides, rotation
) -> list[tuple[float, float]]:
    """
    Generate a list of vertices for a 2D regular polygon.

    :param bounding_circle: The bounding circle is a tuple defined
        by a point and radius. The polygon is inscribed in this circle.
        (e.g. ``bounding_circle=(x, y, r)`` or ``((x, y), r)``)
    :param n_sides: Number of sides
        (e.g. ``n_sides=3`` for a triangle, ``6`` for a hexagon)
    :param rotation: Apply an arbitrary rotation to the polygon
        (e.g. ``rotation=90``, applies a 90 degree rotation)
    :return: List of regular polygon vertices
        (e.g. ``[(25, 50), (50, 50), (50, 25), (25, 25)]``)

    How are the vertices computed?
    1. Compute the following variables
        - theta: Angle between the apothem & the nearest polygon vertex
        - side_length: Length of each polygon edge
        - centroid: Center of bounding circle (1st, 2nd elements of bounding_circle)
        - polygon_radius: Polygon radius (last element of bounding_circle)
        - angles: Location of each polygon vertex in polar grid
            (e.g. A square with 0 degree rotation => [225.0, 315.0, 45.0, 135.0])

    2. For each angle in angles, get the polygon vertex at that angle
        The vertex is computed using the equation below.
            X= xcos(φ) + ysin(φ)
            Y= −xsin(φ) + ycos(φ)

        Note:
            φ = angle in degrees
            x = 0
            y = polygon_radius

        The formula above assumes rotation around the origin.
        In our case, we are rotating around the centroid.
        To account for this, we use the formula below
            X = xcos(φ) + ysin(φ) + centroid_x
            Y = −xsin(φ) + ycos(φ) + centroid_y
    """
    # 1. Error Handling
    # 1.1 Check `n_sides` has an appropriate value
    if not isinstance(n_sides, int):
        msg = "n_sides should be an int"
        raise TypeError(msg)
    if n_sides < 3:
        msg = "n_sides should be an int > 2"
        raise ValueError(msg)

    # 1.2 Check `bounding_circle` has an appropriate value
    if not isinstance(bounding_circle, (list, tuple)):
        msg = "bounding_circle should be a sequence"
        raise TypeError(msg)

    if len(bounding_circle) == 3:
        *centroid, polygon_radius = bounding_circle
    elif len(bounding_circle) == 2:
        centroid, polygon_radius = bounding_circle
    else:
        msg = (
            "bounding_circle should contain 2D coordinates "
            "and a radius (e.g. (x, y, r) or ((x, y), r) )"
        )
        raise ValueError(msg)

    if not all(isinstance(i, (int, float)) for i in (*centroid, polygon_radius)):
        msg = "bounding_circle should only contain numeric data"
        raise ValueError(msg)

    if not len(centroid) == 2:
        msg = "bounding_circle centre should contain 2D coordinates (e.g. (x, y))"
        raise ValueError(msg)

    if polygon_radius <= 0:
        msg = "bounding_circle radius should be > 0"
        raise ValueError(msg)

    # 1.3 Check `rotation` has an appropriate value
    if not isinstance(rotation, (int, float)):
        msg = "rotation should be an int or float"
        raise ValueError(msg)

    # 2. Define Helper Functions
    def _apply_rotation(point: list[float], degrees: float) -> tuple[int, int]:
        return (
            round(
                point[0] * math.cos(math.radians(360 - degrees))
                - point[1] * math.sin(math.radians(360 - degrees))
                + centroid[0],
                2,
            ),
            round(
                point[1] * math.cos(math.radians(360 - degrees))
                + point[0] * math.sin(math.radians(360 - degrees))
                + centroid[1],
                2,
            ),
        )

    def _compute_polygon_vertex(angle: float) -> tuple[int, int]:
        start_point = [polygon_radius, 0]
        return _apply_rotation(start_point, angle)

    def _get_angles(n_sides: int, rotation: float) -> list[float]:
        angles = []
        degrees = 360 / n_sides
        # Start with the bottom left polygon vertex
        current_angle = (270 - 0.5 * degrees) + rotation
        for _ in range(0, n_sides):
            angles.append(current_angle)
            current_angle += degrees
            if current_angle > 360:
                current_angle -= 360
        return angles

    # 3. Variable Declarations
    angles = _get_angles(n_sides, rotation)

    # 4. Compute Vertices
    return [_compute_polygon_vertex(angle) for angle in angles]


def _color_diff(color1, color2: float | tuple[int, ...]) -> float:
    """
    Uses 1-norm distance to calculate difference between two values.
    """
    if isinstance(color2, tuple):
        return sum(abs(color1[i] - color2[i]) for i in range(0, len(color2)))
    else:
        return abs(color1 - color2)

```
--- END FILE ---

Please first localize the bug based on the issue statement, and then generate *SEARCH/REPLACE* edits to fix the issue.

Every *SEARCH/REPLACE* edit must use this format:
1. The file path
2. The start of search block: <<<<<<< SEARCH
3. A contiguous chunk of lines to search for in the existing source code
4. The dividing line: =======
5. The lines to replace into the source code
6. The end of the replace block: >>>>>>> REPLACE

Here is an example:

```python
### mathweb/flask/app.py
<<<<<<< SEARCH
from flask import Flask
=======
import math
from flask import Flask
>>>>>>> REPLACE
```
</solution>

Please note that the *SEARCH/REPLACE* edit REQUIRES PROPER INDENTATION. If you would like to add the line '        print(x)', you must fully write that out, with all those spaces before the code!
Wrap each *SEARCH/REPLACE* edit in a code block as shown in the example above. If you have multiple *SEARCH/REPLACE* edits, use a separate code block for each one.
user