CoolFace
Apppublic

Aluode/PerceptionLabPortable

sourceHugging Faceupdated 9mo agoView on Hugging Face
0likes
ImageDraw2.py244 linesDownload Raw Back to PIL
1#
2# The Python Imaging Library
3# $Id$
4#
5# WCK-style drawing interface operations
6#
7# History:
8# 2003-12-07 fl   created
9# 2005-05-15 fl   updated; added to PIL as ImageDraw2
10# 2005-05-15 fl   added text support
11# 2005-05-20 fl   added arc/chord/pieslice support
12#
13# Copyright (c) 2003-2005 by Secret Labs AB
14# Copyright (c) 2003-2005 by Fredrik Lundh
15#
16# See the README file for information on usage and redistribution.
17#
18
19
20"""
21(Experimental) WCK-style drawing interface operations
22
23.. seealso:: :py:mod:`PIL.ImageDraw`
24"""
25from __future__ import annotations
26
27from typing import Any, AnyStr, BinaryIO
28
29from . import Image, ImageColor, ImageDraw, ImageFont, ImagePath
30from ._typing import Coords, StrOrBytesPath
31
32
33class Pen:
34    """Stores an outline color and width."""
35
36    def __init__(self, color: str, width: int = 1, opacity: int = 255) -> None:
37        self.color = ImageColor.getrgb(color)
38        self.width = width
39
40
41class Brush:
42    """Stores a fill color"""
43
44    def __init__(self, color: str, opacity: int = 255) -> None:
45        self.color = ImageColor.getrgb(color)
46
47
48class Font:
49    """Stores a TrueType font and color"""
50
51    def __init__(
52        self, color: str, file: StrOrBytesPath | BinaryIO, size: float = 12
53    ) -> None:
54        # FIXME: add support for bitmap fonts
55        self.color = ImageColor.getrgb(color)
56        self.font = ImageFont.truetype(file, size)
57
58
59class Draw:
60    """
61    (Experimental) WCK-style drawing interface
62    """
63
64    def __init__(
65        self,
66        image: Image.Image | str,
67        size: tuple[int, int] | list[int] | None = None,
68        color: float | tuple[float, ...] | str | None = None,
69    ) -> None:
70        if isinstance(image, str):
71            if size is None:
72                msg = "If image argument is mode string, size must be a list or tuple"
73                raise ValueError(msg)
74            image = Image.new(image, size, color)
75        self.draw = ImageDraw.Draw(image)
76        self.image = image
77        self.transform: tuple[float, float, float, float, float, float] | None = None
78
79    def flush(self) -> Image.Image:
80        return self.image
81
82    def render(
83        self,
84        op: str,
85        xy: Coords,
86        pen: Pen | Brush | None,
87        brush: Brush | Pen | None = None,
88        **kwargs: Any,
89    ) -> None:
90        # handle color arguments
91        outline = fill = None
92        width = 1
93        if isinstance(pen, Pen):
94            outline = pen.color
95            width = pen.width
96        elif isinstance(brush, Pen):
97            outline = brush.color
98            width = brush.width
99        if isinstance(brush, Brush):
100            fill = brush.color
101        elif isinstance(pen, Brush):
102            fill = pen.color
103        # handle transformation
104        if self.transform:
105            path = ImagePath.Path(xy)
106            path.transform(self.transform)
107            xy = path
108        # render the item
109        if op in ("arc", "line"):
110            kwargs.setdefault("fill", outline)
111        else:
112            kwargs.setdefault("fill", fill)
113            kwargs.setdefault("outline", outline)
114        if op == "line":
115            kwargs.setdefault("width", width)
116        getattr(self.draw, op)(xy, **kwargs)
117
118    def settransform(self, offset: tuple[float, float]) -> None:
119        """Sets a transformation offset."""
120        (xoffset, yoffset) = offset
121        self.transform = (1, 0, xoffset, 0, 1, yoffset)
122
123    def arc(
124        self,
125        xy: Coords,
126        pen: Pen | Brush | None,
127        start: float,
128        end: float,
129        *options: Any,
130    ) -> None:
131        """
132        Draws an arc (a portion of a circle outline) between the start and end
133        angles, inside the given bounding box.
134
135        .. seealso:: :py:meth:`PIL.ImageDraw.ImageDraw.arc`
136        """
137        self.render("arc", xy, pen, *options, start=start, end=end)
138
139    def chord(
140        self,
141        xy: Coords,
142        pen: Pen | Brush | None,
143        start: float,
144        end: float,
145        *options: Any,
146    ) -> None:
147        """
148        Same as :py:meth:`~PIL.ImageDraw2.Draw.arc`, but connects the end points
149        with a straight line.
150
151        .. seealso:: :py:meth:`PIL.ImageDraw.ImageDraw.chord`
152        """
153        self.render("chord", xy, pen, *options, start=start, end=end)
154
155    def ellipse(self, xy: Coords, pen: Pen | Brush | None, *options: Any) -> None:
156        """
157        Draws an ellipse inside the given bounding box.
158
159        .. seealso:: :py:meth:`PIL.ImageDraw.ImageDraw.ellipse`
160        """
161        self.render("ellipse", xy, pen, *options)
162
163    def line(self, xy: Coords, pen: Pen | Brush | None, *options: Any) -> None:
164        """
165        Draws a line between the coordinates in the ``xy`` list.
166
167        .. seealso:: :py:meth:`PIL.ImageDraw.ImageDraw.line`
168        """
169        self.render("line", xy, pen, *options)
170
171    def pieslice(
172        self,
173        xy: Coords,
174        pen: Pen | Brush | None,
175        start: float,
176        end: float,
177        *options: Any,
178    ) -> None:
179        """
180        Same as arc, but also draws straight lines between the end points and the
181        center of the bounding box.
182
183        .. seealso:: :py:meth:`PIL.ImageDraw.ImageDraw.pieslice`
184        """
185        self.render("pieslice", xy, pen, *options, start=start, end=end)
186
187    def polygon(self, xy: Coords, pen: Pen | Brush | None, *options: Any) -> None:
188        """
189        Draws a polygon.
190
191        The polygon outline consists of straight lines between the given
192        coordinates, plus a straight line between the last and the first
193        coordinate.
194
195
196        .. seealso:: :py:meth:`PIL.ImageDraw.ImageDraw.polygon`
197        """
198        self.render("polygon", xy, pen, *options)
199
200    def rectangle(self, xy: Coords, pen: Pen | Brush | None, *options: Any) -> None:
201        """
202        Draws a rectangle.
203
204        .. seealso:: :py:meth:`PIL.ImageDraw.ImageDraw.rectangle`
205        """
206        self.render("rectangle", xy, pen, *options)
207
208    def text(self, xy: tuple[float, float], text: AnyStr, font: Font) -> None:
209        """
210        Draws the string at the given position.
211
212        .. seealso:: :py:meth:`PIL.ImageDraw.ImageDraw.text`
213        """
214        if self.transform:
215            path = ImagePath.Path(xy)
216            path.transform(self.transform)
217            xy = path
218        self.draw.text(xy, text, font=font.font, fill=font.color)
219
220    def textbbox(
221        self, xy: tuple[float, float], text: AnyStr, font: Font
222    ) -> tuple[float, float, float, float]:
223        """
224        Returns bounding box (in pixels) of given text.
225
226        :return: ``(left, top, right, bottom)`` bounding box
227
228        .. seealso:: :py:meth:`PIL.ImageDraw.ImageDraw.textbbox`
229        """
230        if self.transform:
231            path = ImagePath.Path(xy)
232            path.transform(self.transform)
233            xy = path
234        return self.draw.textbbox(xy, text, font=font.font)
235
236    def textlength(self, text: AnyStr, font: Font) -> float:
237        """
238        Returns length (in pixels) of given text.
239        This is the amount by which following text should be offset.
240
241        .. seealso:: :py:meth:`PIL.ImageDraw.ImageDraw.textlength`
242        """
243        return self.draw.textlength(text, font=font.font)
244 
Aluode/PerceptionLabPortable · CoolFace