Aluode/PerceptionLabPortable
0
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 