mirror of
https://github.com/python-pillow/Pillow.git
synced 2025-01-12 10:16:17 +03:00
Merge pull request #8151 from radarhere/type_hint_imagedraw
This commit is contained in:
commit
4b258be3bb
|
@ -448,6 +448,7 @@ def test_shape1() -> None:
|
||||||
x3, y3 = 95, 5
|
x3, y3 = 95, 5
|
||||||
|
|
||||||
# Act
|
# Act
|
||||||
|
assert ImageDraw.Outline is not None
|
||||||
s = ImageDraw.Outline()
|
s = ImageDraw.Outline()
|
||||||
s.move(x0, y0)
|
s.move(x0, y0)
|
||||||
s.curve(x1, y1, x2, y2, x3, y3)
|
s.curve(x1, y1, x2, y2, x3, y3)
|
||||||
|
@ -469,6 +470,7 @@ def test_shape2() -> None:
|
||||||
x3, y3 = 5, 95
|
x3, y3 = 5, 95
|
||||||
|
|
||||||
# Act
|
# Act
|
||||||
|
assert ImageDraw.Outline is not None
|
||||||
s = ImageDraw.Outline()
|
s = ImageDraw.Outline()
|
||||||
s.move(x0, y0)
|
s.move(x0, y0)
|
||||||
s.curve(x1, y1, x2, y2, x3, y3)
|
s.curve(x1, y1, x2, y2, x3, y3)
|
||||||
|
@ -487,6 +489,7 @@ def test_transform() -> None:
|
||||||
draw = ImageDraw.Draw(im)
|
draw = ImageDraw.Draw(im)
|
||||||
|
|
||||||
# Act
|
# Act
|
||||||
|
assert ImageDraw.Outline is not None
|
||||||
s = ImageDraw.Outline()
|
s = ImageDraw.Outline()
|
||||||
s.line(0, 0)
|
s.line(0, 0)
|
||||||
s.transform((0, 0, 0, 0, 0, 0))
|
s.transform((0, 0, 0, 0, 0, 0))
|
||||||
|
@ -913,7 +916,12 @@ def test_rounded_rectangle_translucent(
|
||||||
def test_floodfill(bbox: Coords) -> None:
|
def test_floodfill(bbox: Coords) -> None:
|
||||||
red = ImageColor.getrgb("red")
|
red = ImageColor.getrgb("red")
|
||||||
|
|
||||||
for mode, value in [("L", 1), ("RGBA", (255, 0, 0, 0)), ("RGB", red)]:
|
mode_values: list[tuple[str, int | tuple[int, ...]]] = [
|
||||||
|
("L", 1),
|
||||||
|
("RGBA", (255, 0, 0, 0)),
|
||||||
|
("RGB", red),
|
||||||
|
]
|
||||||
|
for mode, value in mode_values:
|
||||||
# Arrange
|
# Arrange
|
||||||
im = Image.new(mode, (W, H))
|
im = Image.new(mode, (W, H))
|
||||||
draw = ImageDraw.Draw(im)
|
draw = ImageDraw.Draw(im)
|
||||||
|
@ -1429,6 +1437,7 @@ def test_same_color_outline(bbox: Coords) -> None:
|
||||||
x2, y2 = 95, 50
|
x2, y2 = 95, 50
|
||||||
x3, y3 = 95, 5
|
x3, y3 = 95, 5
|
||||||
|
|
||||||
|
assert ImageDraw.Outline is not None
|
||||||
s = ImageDraw.Outline()
|
s = ImageDraw.Outline()
|
||||||
s.move(x0, y0)
|
s.move(x0, y0)
|
||||||
s.curve(x1, y1, x2, y2, x3, y3)
|
s.curve(x1, y1, x2, y2, x3, y3)
|
||||||
|
@ -1467,7 +1476,7 @@ def test_same_color_outline(bbox: Coords) -> None:
|
||||||
(4, "square", {}),
|
(4, "square", {}),
|
||||||
(8, "regular_octagon", {}),
|
(8, "regular_octagon", {}),
|
||||||
(4, "square_rotate_45", {"rotation": 45}),
|
(4, "square_rotate_45", {"rotation": 45}),
|
||||||
(3, "triangle_width", {"width": 5, "outline": "yellow"}),
|
(3, "triangle_width", {"outline": "yellow", "width": 5}),
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
def test_draw_regular_polygon(
|
def test_draw_regular_polygon(
|
||||||
|
@ -1477,7 +1486,10 @@ def test_draw_regular_polygon(
|
||||||
filename = f"Tests/images/imagedraw_{polygon_name}.png"
|
filename = f"Tests/images/imagedraw_{polygon_name}.png"
|
||||||
draw = ImageDraw.Draw(im)
|
draw = ImageDraw.Draw(im)
|
||||||
bounding_circle = ((W // 2, H // 2), 25)
|
bounding_circle = ((W // 2, H // 2), 25)
|
||||||
draw.regular_polygon(bounding_circle, n_sides, fill="red", **args)
|
rotation = int(args.get("rotation", 0))
|
||||||
|
outline = args.get("outline")
|
||||||
|
width = int(args.get("width", 1))
|
||||||
|
draw.regular_polygon(bounding_circle, n_sides, rotation, "red", outline, width)
|
||||||
assert_image_equal_tofile(im, filename)
|
assert_image_equal_tofile(im, filename)
|
||||||
|
|
||||||
|
|
||||||
|
@ -1630,6 +1642,6 @@ def test_incorrectly_ordered_coordinates(xy: tuple[int, int, int, int]) -> None:
|
||||||
draw.rounded_rectangle(xy)
|
draw.rounded_rectangle(xy)
|
||||||
|
|
||||||
|
|
||||||
def test_getdraw():
|
def test_getdraw() -> None:
|
||||||
with pytest.warns(DeprecationWarning):
|
with pytest.warns(DeprecationWarning):
|
||||||
ImageDraw.getdraw(None, [])
|
ImageDraw.getdraw(None, [])
|
||||||
|
|
|
@ -35,15 +35,24 @@ import math
|
||||||
import numbers
|
import numbers
|
||||||
import struct
|
import struct
|
||||||
from types import ModuleType
|
from types import ModuleType
|
||||||
from typing import TYPE_CHECKING, AnyStr, Sequence, cast
|
from typing import TYPE_CHECKING, AnyStr, Callable, List, Sequence, Tuple, Union, cast
|
||||||
|
|
||||||
from . import Image, ImageColor
|
from . import Image, ImageColor
|
||||||
from ._deprecate import deprecate
|
from ._deprecate import deprecate
|
||||||
from ._typing import Coords
|
from ._typing import Coords
|
||||||
|
|
||||||
|
# experimental access to the outline API
|
||||||
|
Outline: Callable[[], Image.core._Outline] | None
|
||||||
|
try:
|
||||||
|
Outline = Image.core.outline
|
||||||
|
except AttributeError:
|
||||||
|
Outline = None
|
||||||
|
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
from . import ImageDraw2, ImageFont
|
from . import ImageDraw2, ImageFont
|
||||||
|
|
||||||
|
_Ink = Union[float, Tuple[int, ...], str]
|
||||||
|
|
||||||
"""
|
"""
|
||||||
A simple 2D drawing interface for PIL images.
|
A simple 2D drawing interface for PIL images.
|
||||||
<p>
|
<p>
|
||||||
|
@ -134,34 +143,47 @@ class ImageDraw:
|
||||||
else:
|
else:
|
||||||
return self.getfont()
|
return self.getfont()
|
||||||
|
|
||||||
def _getink(self, ink, fill=None) -> tuple[int | None, int | None]:
|
def _getink(
|
||||||
|
self, ink: _Ink | None, fill: _Ink | None = None
|
||||||
|
) -> tuple[int | None, int | None]:
|
||||||
|
result_ink = None
|
||||||
|
result_fill = None
|
||||||
if ink is None and fill is None:
|
if ink is None and fill is None:
|
||||||
if self.fill:
|
if self.fill:
|
||||||
fill = self.ink
|
result_fill = self.ink
|
||||||
else:
|
else:
|
||||||
ink = self.ink
|
result_ink = self.ink
|
||||||
else:
|
else:
|
||||||
if ink is not None:
|
if ink is not None:
|
||||||
if isinstance(ink, str):
|
if isinstance(ink, str):
|
||||||
ink = ImageColor.getcolor(ink, self.mode)
|
ink = ImageColor.getcolor(ink, self.mode)
|
||||||
if self.palette and not isinstance(ink, numbers.Number):
|
if self.palette and not isinstance(ink, numbers.Number):
|
||||||
ink = self.palette.getcolor(ink, self._image)
|
ink = self.palette.getcolor(ink, self._image)
|
||||||
ink = self.draw.draw_ink(ink)
|
result_ink = self.draw.draw_ink(ink)
|
||||||
if fill is not None:
|
if fill is not None:
|
||||||
if isinstance(fill, str):
|
if isinstance(fill, str):
|
||||||
fill = ImageColor.getcolor(fill, self.mode)
|
fill = ImageColor.getcolor(fill, self.mode)
|
||||||
if self.palette and not isinstance(fill, numbers.Number):
|
if self.palette and not isinstance(fill, numbers.Number):
|
||||||
fill = self.palette.getcolor(fill, self._image)
|
fill = self.palette.getcolor(fill, self._image)
|
||||||
fill = self.draw.draw_ink(fill)
|
result_fill = self.draw.draw_ink(fill)
|
||||||
return ink, fill
|
return result_ink, result_fill
|
||||||
|
|
||||||
def arc(self, xy: Coords, start, end, fill=None, width=1) -> None:
|
def arc(
|
||||||
|
self,
|
||||||
|
xy: Coords,
|
||||||
|
start: float,
|
||||||
|
end: float,
|
||||||
|
fill: _Ink | None = None,
|
||||||
|
width: int = 1,
|
||||||
|
) -> None:
|
||||||
"""Draw an arc."""
|
"""Draw an arc."""
|
||||||
ink, fill = self._getink(fill)
|
ink, fill = self._getink(fill)
|
||||||
if ink is not None:
|
if ink is not None:
|
||||||
self.draw.draw_arc(xy, start, end, ink, width)
|
self.draw.draw_arc(xy, start, end, ink, width)
|
||||||
|
|
||||||
def bitmap(self, xy: Sequence[int], bitmap, fill=None) -> None:
|
def bitmap(
|
||||||
|
self, xy: Sequence[int], bitmap: Image.Image, fill: _Ink | None = None
|
||||||
|
) -> None:
|
||||||
"""Draw a bitmap."""
|
"""Draw a bitmap."""
|
||||||
bitmap.load()
|
bitmap.load()
|
||||||
ink, fill = self._getink(fill)
|
ink, fill = self._getink(fill)
|
||||||
|
@ -170,30 +192,55 @@ class ImageDraw:
|
||||||
if ink is not None:
|
if ink is not None:
|
||||||
self.draw.draw_bitmap(xy, bitmap.im, ink)
|
self.draw.draw_bitmap(xy, bitmap.im, ink)
|
||||||
|
|
||||||
def chord(self, xy: Coords, start, end, fill=None, outline=None, width=1) -> None:
|
def chord(
|
||||||
|
self,
|
||||||
|
xy: Coords,
|
||||||
|
start: float,
|
||||||
|
end: float,
|
||||||
|
fill: _Ink | None = None,
|
||||||
|
outline: _Ink | None = None,
|
||||||
|
width: int = 1,
|
||||||
|
) -> None:
|
||||||
"""Draw a chord."""
|
"""Draw a chord."""
|
||||||
ink, fill = self._getink(outline, fill)
|
ink, fill_ink = self._getink(outline, fill)
|
||||||
if fill is not None:
|
if fill_ink is not None:
|
||||||
self.draw.draw_chord(xy, start, end, fill, 1)
|
self.draw.draw_chord(xy, start, end, fill_ink, 1)
|
||||||
if ink is not None and ink != fill and width != 0:
|
if ink is not None and ink != fill_ink and width != 0:
|
||||||
self.draw.draw_chord(xy, start, end, ink, 0, width)
|
self.draw.draw_chord(xy, start, end, ink, 0, width)
|
||||||
|
|
||||||
def ellipse(self, xy: Coords, fill=None, outline=None, width=1) -> None:
|
def ellipse(
|
||||||
|
self,
|
||||||
|
xy: Coords,
|
||||||
|
fill: _Ink | None = None,
|
||||||
|
outline: _Ink | None = None,
|
||||||
|
width: int = 1,
|
||||||
|
) -> None:
|
||||||
"""Draw an ellipse."""
|
"""Draw an ellipse."""
|
||||||
ink, fill = self._getink(outline, fill)
|
ink, fill_ink = self._getink(outline, fill)
|
||||||
if fill is not None:
|
if fill_ink is not None:
|
||||||
self.draw.draw_ellipse(xy, fill, 1)
|
self.draw.draw_ellipse(xy, fill_ink, 1)
|
||||||
if ink is not None and ink != fill and width != 0:
|
if ink is not None and ink != fill_ink and width != 0:
|
||||||
self.draw.draw_ellipse(xy, ink, 0, width)
|
self.draw.draw_ellipse(xy, ink, 0, width)
|
||||||
|
|
||||||
def circle(
|
def circle(
|
||||||
self, xy: Sequence[float], radius: float, fill=None, outline=None, width=1
|
self,
|
||||||
|
xy: Sequence[float],
|
||||||
|
radius: float,
|
||||||
|
fill: _Ink | None = None,
|
||||||
|
outline: _Ink | None = None,
|
||||||
|
width: int = 1,
|
||||||
) -> None:
|
) -> None:
|
||||||
"""Draw a circle given center coordinates and a radius."""
|
"""Draw a circle given center coordinates and a radius."""
|
||||||
ellipse_xy = (xy[0] - radius, xy[1] - radius, xy[0] + radius, xy[1] + radius)
|
ellipse_xy = (xy[0] - radius, xy[1] - radius, xy[0] + radius, xy[1] + radius)
|
||||||
self.ellipse(ellipse_xy, fill, outline, width)
|
self.ellipse(ellipse_xy, fill, outline, width)
|
||||||
|
|
||||||
def line(self, xy: Coords, fill=None, width=0, joint=None) -> None:
|
def line(
|
||||||
|
self,
|
||||||
|
xy: Coords,
|
||||||
|
fill: _Ink | None = None,
|
||||||
|
width: int = 0,
|
||||||
|
joint: str | None = None,
|
||||||
|
) -> None:
|
||||||
"""Draw a line, or a connected sequence of line segments."""
|
"""Draw a line, or a connected sequence of line segments."""
|
||||||
ink = self._getink(fill)[0]
|
ink = self._getink(fill)[0]
|
||||||
if ink is not None:
|
if ink is not None:
|
||||||
|
@ -223,7 +270,7 @@ class ImageDraw:
|
||||||
|
|
||||||
def coord_at_angle(
|
def coord_at_angle(
|
||||||
coord: Sequence[float], angle: float
|
coord: Sequence[float], angle: float
|
||||||
) -> tuple[float, float]:
|
) -> tuple[float, ...]:
|
||||||
x, y = coord
|
x, y = coord
|
||||||
angle -= 90
|
angle -= 90
|
||||||
distance = width / 2 - 1
|
distance = width / 2 - 1
|
||||||
|
@ -264,37 +311,54 @@ class ImageDraw:
|
||||||
]
|
]
|
||||||
self.line(gap_coords, fill, width=3)
|
self.line(gap_coords, fill, width=3)
|
||||||
|
|
||||||
def shape(self, shape, fill=None, outline=None) -> None:
|
def shape(
|
||||||
|
self,
|
||||||
|
shape: Image.core._Outline,
|
||||||
|
fill: _Ink | None = None,
|
||||||
|
outline: _Ink | None = None,
|
||||||
|
) -> None:
|
||||||
"""(Experimental) Draw a shape."""
|
"""(Experimental) Draw a shape."""
|
||||||
shape.close()
|
shape.close()
|
||||||
ink, fill = self._getink(outline, fill)
|
ink, fill_ink = self._getink(outline, fill)
|
||||||
if fill is not None:
|
if fill_ink is not None:
|
||||||
self.draw.draw_outline(shape, fill, 1)
|
self.draw.draw_outline(shape, fill_ink, 1)
|
||||||
if ink is not None and ink != fill:
|
if ink is not None and ink != fill_ink:
|
||||||
self.draw.draw_outline(shape, ink, 0)
|
self.draw.draw_outline(shape, ink, 0)
|
||||||
|
|
||||||
def pieslice(
|
def pieslice(
|
||||||
self, xy: Coords, start, end, fill=None, outline=None, width=1
|
self,
|
||||||
|
xy: Coords,
|
||||||
|
start: float,
|
||||||
|
end: float,
|
||||||
|
fill: _Ink | None = None,
|
||||||
|
outline: _Ink | None = None,
|
||||||
|
width: int = 1,
|
||||||
) -> None:
|
) -> None:
|
||||||
"""Draw a pieslice."""
|
"""Draw a pieslice."""
|
||||||
ink, fill = self._getink(outline, fill)
|
ink, fill_ink = self._getink(outline, fill)
|
||||||
if fill is not None:
|
if fill_ink is not None:
|
||||||
self.draw.draw_pieslice(xy, start, end, fill, 1)
|
self.draw.draw_pieslice(xy, start, end, fill_ink, 1)
|
||||||
if ink is not None and ink != fill and width != 0:
|
if ink is not None and ink != fill_ink and width != 0:
|
||||||
self.draw.draw_pieslice(xy, start, end, ink, 0, width)
|
self.draw.draw_pieslice(xy, start, end, ink, 0, width)
|
||||||
|
|
||||||
def point(self, xy: Coords, fill=None) -> None:
|
def point(self, xy: Coords, fill: _Ink | None = None) -> None:
|
||||||
"""Draw one or more individual pixels."""
|
"""Draw one or more individual pixels."""
|
||||||
ink, fill = self._getink(fill)
|
ink, fill = self._getink(fill)
|
||||||
if ink is not None:
|
if ink is not None:
|
||||||
self.draw.draw_points(xy, ink)
|
self.draw.draw_points(xy, ink)
|
||||||
|
|
||||||
def polygon(self, xy: Coords, fill=None, outline=None, width=1) -> None:
|
def polygon(
|
||||||
|
self,
|
||||||
|
xy: Coords,
|
||||||
|
fill: _Ink | None = None,
|
||||||
|
outline: _Ink | None = None,
|
||||||
|
width: int = 1,
|
||||||
|
) -> None:
|
||||||
"""Draw a polygon."""
|
"""Draw a polygon."""
|
||||||
ink, fill = self._getink(outline, fill)
|
ink, fill_ink = self._getink(outline, fill)
|
||||||
if fill is not None:
|
if fill_ink is not None:
|
||||||
self.draw.draw_polygon(xy, fill, 1)
|
self.draw.draw_polygon(xy, fill_ink, 1)
|
||||||
if ink is not None and ink != fill and width != 0:
|
if ink is not None and ink != fill_ink and width != 0:
|
||||||
if width == 1:
|
if width == 1:
|
||||||
self.draw.draw_polygon(xy, ink, 0, width)
|
self.draw.draw_polygon(xy, ink, 0, width)
|
||||||
elif self.im is not None:
|
elif self.im is not None:
|
||||||
|
@ -320,22 +384,41 @@ class ImageDraw:
|
||||||
self.im.paste(im.im, (0, 0) + im.size, mask.im)
|
self.im.paste(im.im, (0, 0) + im.size, mask.im)
|
||||||
|
|
||||||
def regular_polygon(
|
def regular_polygon(
|
||||||
self, bounding_circle, n_sides, rotation=0, fill=None, outline=None, width=1
|
self,
|
||||||
|
bounding_circle: Sequence[Sequence[float] | float],
|
||||||
|
n_sides: int,
|
||||||
|
rotation: float = 0,
|
||||||
|
fill: _Ink | None = None,
|
||||||
|
outline: _Ink | None = None,
|
||||||
|
width: int = 1,
|
||||||
) -> None:
|
) -> None:
|
||||||
"""Draw a regular polygon."""
|
"""Draw a regular polygon."""
|
||||||
xy = _compute_regular_polygon_vertices(bounding_circle, n_sides, rotation)
|
xy = _compute_regular_polygon_vertices(bounding_circle, n_sides, rotation)
|
||||||
self.polygon(xy, fill, outline, width)
|
self.polygon(xy, fill, outline, width)
|
||||||
|
|
||||||
def rectangle(self, xy: Coords, fill=None, outline=None, width=1) -> None:
|
def rectangle(
|
||||||
|
self,
|
||||||
|
xy: Coords,
|
||||||
|
fill: _Ink | None = None,
|
||||||
|
outline: _Ink | None = None,
|
||||||
|
width: int = 1,
|
||||||
|
) -> None:
|
||||||
"""Draw a rectangle."""
|
"""Draw a rectangle."""
|
||||||
ink, fill = self._getink(outline, fill)
|
ink, fill_ink = self._getink(outline, fill)
|
||||||
if fill is not None:
|
if fill_ink is not None:
|
||||||
self.draw.draw_rectangle(xy, fill, 1)
|
self.draw.draw_rectangle(xy, fill_ink, 1)
|
||||||
if ink is not None and ink != fill and width != 0:
|
if ink is not None and ink != fill_ink and width != 0:
|
||||||
self.draw.draw_rectangle(xy, ink, 0, width)
|
self.draw.draw_rectangle(xy, ink, 0, width)
|
||||||
|
|
||||||
def rounded_rectangle(
|
def rounded_rectangle(
|
||||||
self, xy: Coords, radius=0, fill=None, outline=None, width=1, *, corners=None
|
self,
|
||||||
|
xy: Coords,
|
||||||
|
radius: float = 0,
|
||||||
|
fill: _Ink | None = None,
|
||||||
|
outline: _Ink | None = None,
|
||||||
|
width: int = 1,
|
||||||
|
*,
|
||||||
|
corners: tuple[bool, bool, bool, bool] | None = None,
|
||||||
) -> None:
|
) -> None:
|
||||||
"""Draw a rounded rectangle."""
|
"""Draw a rounded rectangle."""
|
||||||
if isinstance(xy[0], (list, tuple)):
|
if isinstance(xy[0], (list, tuple)):
|
||||||
|
@ -377,10 +460,10 @@ class ImageDraw:
|
||||||
# that is a rectangle
|
# that is a rectangle
|
||||||
return self.rectangle(xy, fill, outline, width)
|
return self.rectangle(xy, fill, outline, width)
|
||||||
|
|
||||||
r = d // 2
|
r = int(d // 2)
|
||||||
ink, fill = self._getink(outline, fill)
|
ink, fill_ink = self._getink(outline, fill)
|
||||||
|
|
||||||
def draw_corners(pieslice) -> None:
|
def draw_corners(pieslice: bool) -> None:
|
||||||
parts: tuple[tuple[tuple[float, float, float, float], int, int], ...]
|
parts: tuple[tuple[tuple[float, float, float, float], int, int], ...]
|
||||||
if full_x:
|
if full_x:
|
||||||
# Draw top and bottom halves
|
# Draw top and bottom halves
|
||||||
|
@ -410,32 +493,32 @@ class ImageDraw:
|
||||||
)
|
)
|
||||||
for part in parts:
|
for part in parts:
|
||||||
if pieslice:
|
if pieslice:
|
||||||
self.draw.draw_pieslice(*(part + (fill, 1)))
|
self.draw.draw_pieslice(*(part + (fill_ink, 1)))
|
||||||
else:
|
else:
|
||||||
self.draw.draw_arc(*(part + (ink, width)))
|
self.draw.draw_arc(*(part + (ink, width)))
|
||||||
|
|
||||||
if fill is not None:
|
if fill_ink is not None:
|
||||||
draw_corners(True)
|
draw_corners(True)
|
||||||
|
|
||||||
if full_x:
|
if full_x:
|
||||||
self.draw.draw_rectangle((x0, y0 + r + 1, x1, y1 - r - 1), fill, 1)
|
self.draw.draw_rectangle((x0, y0 + r + 1, x1, y1 - r - 1), fill_ink, 1)
|
||||||
else:
|
else:
|
||||||
self.draw.draw_rectangle((x0 + r + 1, y0, x1 - r - 1, y1), fill, 1)
|
self.draw.draw_rectangle((x0 + r + 1, y0, x1 - r - 1, y1), fill_ink, 1)
|
||||||
if not full_x and not full_y:
|
if not full_x and not full_y:
|
||||||
left = [x0, y0, x0 + r, y1]
|
left = [x0, y0, x0 + r, y1]
|
||||||
if corners[0]:
|
if corners[0]:
|
||||||
left[1] += r + 1
|
left[1] += r + 1
|
||||||
if corners[3]:
|
if corners[3]:
|
||||||
left[3] -= r + 1
|
left[3] -= r + 1
|
||||||
self.draw.draw_rectangle(left, fill, 1)
|
self.draw.draw_rectangle(left, fill_ink, 1)
|
||||||
|
|
||||||
right = [x1 - r, y0, x1, y1]
|
right = [x1 - r, y0, x1, y1]
|
||||||
if corners[1]:
|
if corners[1]:
|
||||||
right[1] += r + 1
|
right[1] += r + 1
|
||||||
if corners[2]:
|
if corners[2]:
|
||||||
right[3] -= r + 1
|
right[3] -= r + 1
|
||||||
self.draw.draw_rectangle(right, fill, 1)
|
self.draw.draw_rectangle(right, fill_ink, 1)
|
||||||
if ink is not None and ink != fill and width != 0:
|
if ink is not None and ink != fill_ink and width != 0:
|
||||||
draw_corners(False)
|
draw_corners(False)
|
||||||
|
|
||||||
if not full_x:
|
if not full_x:
|
||||||
|
@ -530,10 +613,11 @@ class ImageDraw:
|
||||||
embedded_color,
|
embedded_color,
|
||||||
)
|
)
|
||||||
|
|
||||||
def getink(fill):
|
def getink(fill: _Ink | None) -> int:
|
||||||
ink, fill = self._getink(fill)
|
ink, fill_ink = self._getink(fill)
|
||||||
if ink is None:
|
if ink is None:
|
||||||
return fill
|
assert fill_ink is not None
|
||||||
|
return fill_ink
|
||||||
return ink
|
return ink
|
||||||
|
|
||||||
def draw_text(ink, stroke_width=0, stroke_offset=None) -> None:
|
def draw_text(ink, stroke_width=0, stroke_offset=None) -> None:
|
||||||
|
@ -897,13 +981,6 @@ def Draw(im: Image.Image, mode: str | None = None) -> ImageDraw:
|
||||||
return ImageDraw(im, mode)
|
return ImageDraw(im, mode)
|
||||||
|
|
||||||
|
|
||||||
# experimental access to the outline API
|
|
||||||
try:
|
|
||||||
Outline = Image.core.outline
|
|
||||||
except AttributeError:
|
|
||||||
Outline = None
|
|
||||||
|
|
||||||
|
|
||||||
def getdraw(
|
def getdraw(
|
||||||
im: Image.Image | None = None, hints: list[str] | None = None
|
im: Image.Image | None = None, hints: list[str] | None = None
|
||||||
) -> tuple[ImageDraw2.Draw | None, ModuleType]:
|
) -> tuple[ImageDraw2.Draw | None, ModuleType]:
|
||||||
|
@ -983,12 +1060,12 @@ def floodfill(
|
||||||
|
|
||||||
|
|
||||||
def _compute_regular_polygon_vertices(
|
def _compute_regular_polygon_vertices(
|
||||||
bounding_circle, n_sides, rotation
|
bounding_circle: Sequence[Sequence[float] | float], n_sides: int, rotation: float
|
||||||
) -> list[tuple[float, float]]:
|
) -> list[tuple[float, float]]:
|
||||||
"""
|
"""
|
||||||
Generate a list of vertices for a 2D regular polygon.
|
Generate a list of vertices for a 2D regular polygon.
|
||||||
|
|
||||||
:param bounding_circle: The bounding circle is a tuple defined
|
:param bounding_circle: The bounding circle is a sequence defined
|
||||||
by a point and radius. The polygon is inscribed in this circle.
|
by a point and radius. The polygon is inscribed in this circle.
|
||||||
(e.g. ``bounding_circle=(x, y, r)`` or ``((x, y), r)``)
|
(e.g. ``bounding_circle=(x, y, r)`` or ``((x, y), r)``)
|
||||||
:param n_sides: Number of sides
|
:param n_sides: Number of sides
|
||||||
|
@ -1026,7 +1103,7 @@ def _compute_regular_polygon_vertices(
|
||||||
# 1. Error Handling
|
# 1. Error Handling
|
||||||
# 1.1 Check `n_sides` has an appropriate value
|
# 1.1 Check `n_sides` has an appropriate value
|
||||||
if not isinstance(n_sides, int):
|
if not isinstance(n_sides, int):
|
||||||
msg = "n_sides should be an int"
|
msg = "n_sides should be an int" # type: ignore[unreachable]
|
||||||
raise TypeError(msg)
|
raise TypeError(msg)
|
||||||
if n_sides < 3:
|
if n_sides < 3:
|
||||||
msg = "n_sides should be an int > 2"
|
msg = "n_sides should be an int > 2"
|
||||||
|
@ -1038,9 +1115,24 @@ def _compute_regular_polygon_vertices(
|
||||||
raise TypeError(msg)
|
raise TypeError(msg)
|
||||||
|
|
||||||
if len(bounding_circle) == 3:
|
if len(bounding_circle) == 3:
|
||||||
*centroid, polygon_radius = bounding_circle
|
if not all(isinstance(i, (int, float)) for i in bounding_circle):
|
||||||
elif len(bounding_circle) == 2:
|
msg = "bounding_circle should only contain numeric data"
|
||||||
centroid, polygon_radius = bounding_circle
|
raise ValueError(msg)
|
||||||
|
|
||||||
|
*centroid, polygon_radius = cast(List[float], list(bounding_circle))
|
||||||
|
elif len(bounding_circle) == 2 and isinstance(bounding_circle[0], (list, tuple)):
|
||||||
|
if not all(
|
||||||
|
isinstance(i, (int, float)) for i in bounding_circle[0]
|
||||||
|
) or not isinstance(bounding_circle[1], (int, float)):
|
||||||
|
msg = "bounding_circle should only contain numeric data"
|
||||||
|
raise ValueError(msg)
|
||||||
|
|
||||||
|
if len(bounding_circle[0]) != 2:
|
||||||
|
msg = "bounding_circle centre should contain 2D coordinates (e.g. (x, y))"
|
||||||
|
raise ValueError(msg)
|
||||||
|
|
||||||
|
centroid = cast(List[float], list(bounding_circle[0]))
|
||||||
|
polygon_radius = cast(float, bounding_circle[1])
|
||||||
else:
|
else:
|
||||||
msg = (
|
msg = (
|
||||||
"bounding_circle should contain 2D coordinates "
|
"bounding_circle should contain 2D coordinates "
|
||||||
|
@ -1048,25 +1140,17 @@ def _compute_regular_polygon_vertices(
|
||||||
)
|
)
|
||||||
raise ValueError(msg)
|
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:
|
if polygon_radius <= 0:
|
||||||
msg = "bounding_circle radius should be > 0"
|
msg = "bounding_circle radius should be > 0"
|
||||||
raise ValueError(msg)
|
raise ValueError(msg)
|
||||||
|
|
||||||
# 1.3 Check `rotation` has an appropriate value
|
# 1.3 Check `rotation` has an appropriate value
|
||||||
if not isinstance(rotation, (int, float)):
|
if not isinstance(rotation, (int, float)):
|
||||||
msg = "rotation should be an int or float"
|
msg = "rotation should be an int or float" # type: ignore[unreachable]
|
||||||
raise ValueError(msg)
|
raise ValueError(msg)
|
||||||
|
|
||||||
# 2. Define Helper Functions
|
# 2. Define Helper Functions
|
||||||
def _apply_rotation(point: list[float], degrees: float) -> tuple[int, int]:
|
def _apply_rotation(point: list[float], degrees: float) -> tuple[float, float]:
|
||||||
return (
|
return (
|
||||||
round(
|
round(
|
||||||
point[0] * math.cos(math.radians(360 - degrees))
|
point[0] * math.cos(math.radians(360 - degrees))
|
||||||
|
@ -1082,7 +1166,7 @@ def _compute_regular_polygon_vertices(
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
def _compute_polygon_vertex(angle: float) -> tuple[int, int]:
|
def _compute_polygon_vertex(angle: float) -> tuple[float, float]:
|
||||||
start_point = [polygon_radius, 0]
|
start_point = [polygon_radius, 0]
|
||||||
return _apply_rotation(start_point, angle)
|
return _apply_rotation(start_point, angle)
|
||||||
|
|
||||||
|
|
|
@ -18,5 +18,10 @@ class ImagingDecoder:
|
||||||
class ImagingEncoder:
|
class ImagingEncoder:
|
||||||
def __getattr__(self, name: str) -> Any: ...
|
def __getattr__(self, name: str) -> Any: ...
|
||||||
|
|
||||||
|
class _Outline:
|
||||||
|
def close(self) -> None: ...
|
||||||
|
def __getattr__(self, name: str) -> Any: ...
|
||||||
|
|
||||||
def font(image: ImagingCore, glyphdata: bytes) -> ImagingFont: ...
|
def font(image: ImagingCore, glyphdata: bytes) -> ImagingFont: ...
|
||||||
|
def outline() -> _Outline: ...
|
||||||
def __getattr__(name: str) -> Any: ...
|
def __getattr__(name: str) -> Any: ...
|
||||||
|
|
Loading…
Reference in New Issue
Block a user