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