sketchingpy.pillow_util
Convienence functions for Pillow (PIL).
License:
BSD
1"""Convienence functions for Pillow (PIL). 2 3License: 4 BSD 5""" 6 7import itertools 8import math 9import typing 10 11import PIL.Image 12import PIL.ImageColor 13import PIL.ImageDraw 14 15import sketchingpy.bezier_util 16import sketchingpy.shape_struct 17import sketchingpy.state_struct 18 19COLOR_MAYBE = typing.Optional[typing.Union[ 20 typing.Tuple[int, int, int], 21 typing.Tuple[int, int, int, int] 22]] 23 24 25class PillowUtilImage: 26 """Wrapper around a native Pillow image with additional metadata.""" 27 28 def __init__(self, x: float, y: float, width: float, height: float, image: PIL.Image.Image): 29 """Create a new wrapped pillow image. 30 31 Args: 32 x: The starting x coordinate of this image in pixels. 33 y: The starting y coordinate of this image in pixels. 34 width: The current width of this image in pixels. 35 height: The current height of this image in pixels. 36 image: The image decorated. 37 """ 38 self._x = x 39 self._y = y 40 self._width = width 41 self._height = height 42 self._image = image 43 44 def get_x(self) -> float: 45 """Get the horizontal coordinate of this image (left). 46 47 Returns: 48 The starting x coordinate of this image in pixels. 49 """ 50 return self._x 51 52 def get_y(self) -> float: 53 """Get the vertical coordinate of this image (top). 54 55 Returns: 56 The starting y coordinate of this image in pixels. 57 """ 58 return self._y 59 60 def get_width(self) -> float: 61 """Get the horizontal size of this image at time of construction. 62 63 Returns: 64 Width of this image in pixels. 65 """ 66 return self._width 67 68 def get_height(self) -> float: 69 """Get the vertical size of this image at time of construction. 70 71 Returns: 72 Height of this image in pixels. 73 """ 74 return self._height 75 76 def get_image(self) -> PIL.Image.Image: 77 """Get the underlying Pillow image. 78 79 Returns: 80 The pillow image that this wraps. 81 """ 82 return self._image 83 84 85def make_arc_image(min_x: float, min_y: float, width: float, height: float, start_rad: float, 86 end_rad: float, stroke_enabled: bool, fill_enabled: bool, stroke_color: COLOR_MAYBE, 87 fill_color: COLOR_MAYBE, stroke_weight: float) -> PillowUtilImage: 88 """Draw an arc using Pillow. 89 90 Args: 91 min_x: The left coordinate. 92 min_y: The top coordinate. 93 width: Width of the arc in pixels. 94 height: Height of the arc in pixels. 95 start_rad: Starting angle (radians) of the arc. 96 end_rad: Ending angle (radians) of the arc. 97 stroke_enabled: Boolean indicating if the stroke should be drawn. 98 fill_enabled: Boolean indicating if the fill should be drawn. 99 stroke_color: The color as tuple with which the stroke should be drawn or None if no stroke. 100 fill_color: The color as tuple with which the fill should be drawn or None if no fill. 101 stroke_weight: The size of the stroke in pixels. 102 103 Returns: 104 Decorated Pillow image with the drawn arc. 105 """ 106 if stroke_enabled: 107 stroke_weight_realized = stroke_weight 108 else: 109 stroke_weight_realized = 0 110 111 width_offset = width + stroke_weight_realized 112 height_offset = height + stroke_weight_realized 113 114 size = (round(width_offset) + 1, round(height_offset) + 1) 115 target_image = PIL.Image.new('RGBA', size, (255, 255, 255, 0)) 116 target_surface = PIL.ImageDraw.Draw(target_image, 'RGBA') 117 118 bounds = ( 119 (0, 0), 120 (width, height) 121 ) 122 123 start_deg = math.degrees(start_rad) - 90 124 end_deg = math.degrees(end_rad) - 90 125 126 if fill_enabled and fill_color is not None: 127 target_surface.chord( 128 bounds, 129 start_deg, 130 end_deg, 131 fill=fill_color 132 ) 133 134 if stroke_enabled and stroke_color is not None: 135 target_surface.arc( 136 bounds, 137 start_deg, 138 end_deg, 139 fill=stroke_color, 140 width=stroke_weight_realized 141 ) 142 143 return PillowUtilImage( 144 min_x, 145 min_y, 146 width_offset, 147 height_offset, 148 target_image 149 ) 150 151 152def make_rect_image(min_x: float, min_y: float, width: float, height: float, stroke_enabled: bool, 153 fill_enabled: bool, stroke_color: COLOR_MAYBE, fill_color: COLOR_MAYBE, 154 stroke_weight: float) -> PillowUtilImage: 155 """Draw a rectangle using Pillow. 156 157 Args: 158 min_x: The left coordinate. 159 min_y: The top coordinate. 160 width: Width of the rect in pixels. 161 height: Height of the rect in pixels. 162 stroke_enabled: Boolean indicating if the stroke should be drawn. 163 fill_enabled: Boolean indicating if the fill should be drawn. 164 stroke_color: The color as tuple with which the stroke should be drawn or None if no stroke. 165 fill_color: The color as tuple with which the fill should be drawn or None if no fill. 166 stroke_weight: The size of the stroke in pixels. 167 168 Returns: 169 Decorated Pillow image with the drawn rect. 170 """ 171 if stroke_enabled: 172 stroke_weight_realized = stroke_weight 173 else: 174 stroke_weight_realized = 0 175 176 width_offset = width + math.floor(stroke_weight_realized / 2) * 2 177 height_offset = height + math.floor(stroke_weight_realized / 2) * 2 178 179 size = (round(width_offset) + 1, round(height_offset) + 1) 180 target_image = PIL.Image.new('RGBA', size, (255, 255, 255, 0)) 181 target_surface = PIL.ImageDraw.Draw(target_image, 'RGBA') 182 183 bounds = ( 184 0, 185 0, 186 width_offset, 187 height_offset 188 ) 189 target_surface.rectangle( 190 bounds, 191 fill=fill_color if fill_enabled else None, 192 outline=stroke_color if stroke_enabled else None, 193 width=stroke_weight_realized 194 ) 195 196 return PillowUtilImage( 197 min_x - round(stroke_weight_realized / 2), 198 min_y - round(stroke_weight_realized / 2), 199 width_offset, 200 height_offset, 201 target_image 202 ) 203 204 205def make_ellipse_image(min_x: float, min_y: float, width: float, height: float, 206 stroke_enabled: bool, fill_enabled: bool, stroke_color: COLOR_MAYBE, fill_color: COLOR_MAYBE, 207 stroke_weight: float) -> PillowUtilImage: 208 """Draw a ellipse using Pillow. 209 210 Args: 211 min_x: The left coordinate. 212 min_y: The top coordinate. 213 width: Width of the rect in pixels. 214 height: Height of the rect in pixels. 215 stroke_enabled: Boolean indicating if the stroke should be drawn. 216 fill_enabled: Boolean indicating if the fill should be drawn. 217 stroke_color: The color as tuple with which the stroke should be drawn or None if no stroke. 218 fill_color: The color as tuple with which the fill should be drawn or None if no fill. 219 stroke_weight: The size of the stroke in pixels. 220 221 Returns: 222 Decorated Pillow image with the drawn ellipse. 223 """ 224 if stroke_enabled: 225 stroke_weight_realized = stroke_weight 226 else: 227 stroke_weight_realized = 0 228 229 width_offset = width + math.floor(stroke_weight_realized / 2) * 2 230 height_offset = height + math.floor(stroke_weight_realized / 2) * 2 231 232 size = (round(width_offset) + 1, round(height_offset) + 1) 233 target_image = PIL.Image.new('RGBA', size, (255, 255, 255, 0)) 234 target_surface = PIL.ImageDraw.Draw(target_image, 'RGBA') 235 236 bounds = ( 237 0, 238 0, 239 width_offset, 240 height_offset 241 ) 242 target_surface.ellipse( 243 bounds, 244 fill=fill_color if fill_enabled else None, 245 outline=stroke_color if stroke_enabled else None, 246 width=stroke_weight_realized 247 ) 248 249 return PillowUtilImage( 250 min_x - round(stroke_weight_realized / 2), 251 min_y - round(stroke_weight_realized / 2), 252 width_offset, 253 height_offset, 254 target_image 255 ) 256 257 258class SegmentSimplifier: 259 """Utility to help draw shapes' segments in Pillow.""" 260 261 def __init__(self, start_x: float, start_y: float): 262 """Create a new simplifier. 263 264 Args: 265 start_x: The starting x coordinate of the shape. 266 start_y: The starting y coordinate of the shape. 267 """ 268 self._previous_x = start_x 269 self._previous_y = start_y 270 271 def simplify(self, 272 segment: sketchingpy.shape_struct.Line) -> typing.Iterable[typing.Iterable[float]]: 273 """Turn a segment into a series of coordinates. 274 275 Simplify a segment into a simple series of x, y coordinates which approximate the underlying 276 shape using a series of straight lines. 277 278 Args: 279 segment: The segment to simplify. 280 281 Returns: 282 Collection of x, y coordinates. 283 """ 284 ret_vals: typing.Iterable[typing.Tuple[float, float]] = [] 285 286 strategy = segment.get_strategy() 287 if strategy == 'straight': 288 ret_vals = ((segment.get_destination_x(), segment.get_destination_y()),) 289 elif strategy == 'bezier': 290 change_y = abs(segment.get_control_y2() - segment.get_control_y1()) 291 change_x = abs(segment.get_control_x2() - segment.get_control_x1()) 292 293 num_segs = (change_y**2 + change_x**2) ** 0.5 / 10 294 num_segs_int = int(num_segs) 295 296 bezier_maker = sketchingpy.bezier_util.BezierMaker() 297 bezier_maker.add_point(self._previous_x, self._previous_y) 298 bezier_maker.add_point(segment.get_control_x1(), segment.get_control_y1()) 299 bezier_maker.add_point(segment.get_control_x2(), segment.get_control_y2()) 300 bezier_maker.add_point(segment.get_destination_x(), segment.get_destination_y()) 301 302 ret_vals = bezier_maker.get_points(num_segs_int) 303 else: 304 raise RuntimeError('Unknown segment strategy: ' + strategy) 305 306 self._previous_x = segment.get_destination_x() 307 self._previous_y = segment.get_destination_y() 308 309 return ret_vals 310 311 312def make_shape_image(shape: sketchingpy.shape_struct.Shape, stroke_enabled: bool, 313 fill_enabled: bool, stroke_color: COLOR_MAYBE, fill_color: COLOR_MAYBE, 314 stroke_weight: float) -> PillowUtilImage: 315 """Draw a Sketchingpy shape into a pillow image. 316 317 Args: 318 shape: The shape to be drawn. 319 stroke_enabled: Boolean indicating if the stroke should be drawn. 320 fill_enabled: Boolean indicating if the fill should be drawn. 321 stroke_color: The color as tuple with which the stroke should be drawn or None if no stroke. 322 fill_color: The color as tuple with which the fill should be drawn or None if no fill. 323 stroke_weight: The size of the stroke in pixels. 324 325 Returns: 326 Decorated Pillow image with the drawn shape. 327 """ 328 329 if not shape.get_is_finished(): 330 raise RuntimeError('Finish shape before drawing.') 331 332 min_x = shape.get_min_x() 333 max_x = shape.get_max_x() 334 min_y = shape.get_min_y() 335 max_y = shape.get_max_y() 336 337 width = max_x - min_x 338 height = max_y - min_y 339 width_offset = width + stroke_weight * 2 340 height_offset = height + stroke_weight * 2 341 342 size = (round(width_offset) + 1, round(height_offset) + 1) 343 target_image = PIL.Image.new('RGBA', size, (255, 255, 255, 0)) 344 target_surface = PIL.ImageDraw.Draw(target_image, 'RGBA') 345 346 def adjust_coord(coord): 347 return ( 348 coord[0] - min_x + stroke_weight, 349 coord[1] - min_y + stroke_weight 350 ) 351 352 start_x = shape.get_start_x() 353 start_y = shape.get_start_y() 354 start_coords = [(start_x, start_y)] 355 356 simplified_segements = [] 357 simplifier = SegmentSimplifier(start_x, start_y) 358 for segment in shape.get_segments(): 359 simplified_segements.append(simplifier.simplify(segment)) 360 361 later_coords = itertools.chain(*simplified_segements) 362 all_coords = itertools.chain(start_coords, later_coords) 363 coords = [adjust_coord(x) for x in all_coords] 364 365 if shape.get_is_closed(): 366 target_surface.polygon(coords, fill=fill_color, outline=stroke_color, width=stroke_weight) 367 else: 368 target_surface.line(coords, fill=stroke_color, width=stroke_weight, joint='curve') 369 370 return PillowUtilImage( 371 min_x - stroke_weight, 372 min_y - stroke_weight, 373 width_offset, 374 height_offset, 375 target_image 376 ) 377 378 379def make_text_image(x: float, y: float, content: str, font: PIL.ImageFont.ImageFont, 380 stroke_enabled: bool, fill_enabled: bool, stroke: COLOR_MAYBE, fill: COLOR_MAYBE, 381 stroke_weight: float, anchor: str): 382 """Draw text into a pillow image. 383 384 Args: 385 x: The x coordinate of the anchor. 386 y: The y coordinate of the anchor. 387 font: The font (PIL native) to use in drawing the text. 388 stroke_enabled: Boolean indicating if the stroke should be drawn. 389 fill_enabled: Boolean indicating if the fill should be drawn. 390 stroke: The color as tuple with which the stroke should be drawn or None if no stroke. 391 fill: The color as tuple with which the fill should be drawn or None if no fill. 392 stroke_weight: The size of the stroke in pixels. 393 anchor: Anchor string describing vertical and horizontal alignment. 394 395 Returns: 396 Decorated Pillow image with the drawn text. 397 """ 398 399 temp_image = PIL.Image.new('RGBA', (1, 1), (255, 255, 255, 0)) 400 temp_surface = PIL.ImageDraw.Draw(temp_image, 'RGBA') 401 stroke_weight_int = round(stroke_weight) 402 bounding_box = temp_surface.textbbox( 403 (stroke_weight_int, stroke_weight_int), 404 content, 405 font=font, 406 anchor=anchor, 407 stroke_width=stroke_weight_int 408 ) 409 410 start_x = bounding_box[0] 411 end_x = bounding_box[2] 412 413 start_y = bounding_box[1] 414 end_y = bounding_box[3] 415 416 width = end_x - start_x 417 height = end_y - start_y 418 419 width_offset = width + stroke_weight * 2 420 height_offset = height + stroke_weight * 2 421 422 size = (round(width_offset) + 2, round(height_offset) + 1) 423 target_image = PIL.Image.new('RGBA', size, (255, 255, 255, 0)) 424 target_surface = PIL.ImageDraw.Draw(target_image, 'RGBA') 425 426 if stroke_enabled: 427 target_surface.text( 428 ( 429 round(-1 * start_x + stroke_weight + 1), 430 round(-1 * start_y + stroke_weight) 431 ), 432 content, 433 font=font, 434 anchor=anchor, 435 stroke_width=round(stroke_weight), 436 stroke_fill=stroke, 437 fill=(0, 0, 0, 0) 438 ) 439 440 if fill_enabled: 441 target_surface.text( 442 ( 443 -1 * start_x + stroke_weight + 1, 444 -1 * start_y + stroke_weight 445 ), 446 content, 447 font=font, 448 anchor=anchor, 449 fill=fill 450 ) 451 452 return PillowUtilImage( 453 start_x - stroke_weight + x, 454 start_y - stroke_weight + y, 455 width_offset, 456 height_offset, 457 target_image 458 )
26class PillowUtilImage: 27 """Wrapper around a native Pillow image with additional metadata.""" 28 29 def __init__(self, x: float, y: float, width: float, height: float, image: PIL.Image.Image): 30 """Create a new wrapped pillow image. 31 32 Args: 33 x: The starting x coordinate of this image in pixels. 34 y: The starting y coordinate of this image in pixels. 35 width: The current width of this image in pixels. 36 height: The current height of this image in pixels. 37 image: The image decorated. 38 """ 39 self._x = x 40 self._y = y 41 self._width = width 42 self._height = height 43 self._image = image 44 45 def get_x(self) -> float: 46 """Get the horizontal coordinate of this image (left). 47 48 Returns: 49 The starting x coordinate of this image in pixels. 50 """ 51 return self._x 52 53 def get_y(self) -> float: 54 """Get the vertical coordinate of this image (top). 55 56 Returns: 57 The starting y coordinate of this image in pixels. 58 """ 59 return self._y 60 61 def get_width(self) -> float: 62 """Get the horizontal size of this image at time of construction. 63 64 Returns: 65 Width of this image in pixels. 66 """ 67 return self._width 68 69 def get_height(self) -> float: 70 """Get the vertical size of this image at time of construction. 71 72 Returns: 73 Height of this image in pixels. 74 """ 75 return self._height 76 77 def get_image(self) -> PIL.Image.Image: 78 """Get the underlying Pillow image. 79 80 Returns: 81 The pillow image that this wraps. 82 """ 83 return self._image
Wrapper around a native Pillow image with additional metadata.
29 def __init__(self, x: float, y: float, width: float, height: float, image: PIL.Image.Image): 30 """Create a new wrapped pillow image. 31 32 Args: 33 x: The starting x coordinate of this image in pixels. 34 y: The starting y coordinate of this image in pixels. 35 width: The current width of this image in pixels. 36 height: The current height of this image in pixels. 37 image: The image decorated. 38 """ 39 self._x = x 40 self._y = y 41 self._width = width 42 self._height = height 43 self._image = image
Create a new wrapped pillow image.
Arguments:
- x: The starting x coordinate of this image in pixels.
- y: The starting y coordinate of this image in pixels.
- width: The current width of this image in pixels.
- height: The current height of this image in pixels.
- image: The image decorated.
45 def get_x(self) -> float: 46 """Get the horizontal coordinate of this image (left). 47 48 Returns: 49 The starting x coordinate of this image in pixels. 50 """ 51 return self._x
Get the horizontal coordinate of this image (left).
Returns:
The starting x coordinate of this image in pixels.
53 def get_y(self) -> float: 54 """Get the vertical coordinate of this image (top). 55 56 Returns: 57 The starting y coordinate of this image in pixels. 58 """ 59 return self._y
Get the vertical coordinate of this image (top).
Returns:
The starting y coordinate of this image in pixels.
61 def get_width(self) -> float: 62 """Get the horizontal size of this image at time of construction. 63 64 Returns: 65 Width of this image in pixels. 66 """ 67 return self._width
Get the horizontal size of this image at time of construction.
Returns:
Width of this image in pixels.
69 def get_height(self) -> float: 70 """Get the vertical size of this image at time of construction. 71 72 Returns: 73 Height of this image in pixels. 74 """ 75 return self._height
Get the vertical size of this image at time of construction.
Returns:
Height of this image in pixels.
86def make_arc_image(min_x: float, min_y: float, width: float, height: float, start_rad: float, 87 end_rad: float, stroke_enabled: bool, fill_enabled: bool, stroke_color: COLOR_MAYBE, 88 fill_color: COLOR_MAYBE, stroke_weight: float) -> PillowUtilImage: 89 """Draw an arc using Pillow. 90 91 Args: 92 min_x: The left coordinate. 93 min_y: The top coordinate. 94 width: Width of the arc in pixels. 95 height: Height of the arc in pixels. 96 start_rad: Starting angle (radians) of the arc. 97 end_rad: Ending angle (radians) of the arc. 98 stroke_enabled: Boolean indicating if the stroke should be drawn. 99 fill_enabled: Boolean indicating if the fill should be drawn. 100 stroke_color: The color as tuple with which the stroke should be drawn or None if no stroke. 101 fill_color: The color as tuple with which the fill should be drawn or None if no fill. 102 stroke_weight: The size of the stroke in pixels. 103 104 Returns: 105 Decorated Pillow image with the drawn arc. 106 """ 107 if stroke_enabled: 108 stroke_weight_realized = stroke_weight 109 else: 110 stroke_weight_realized = 0 111 112 width_offset = width + stroke_weight_realized 113 height_offset = height + stroke_weight_realized 114 115 size = (round(width_offset) + 1, round(height_offset) + 1) 116 target_image = PIL.Image.new('RGBA', size, (255, 255, 255, 0)) 117 target_surface = PIL.ImageDraw.Draw(target_image, 'RGBA') 118 119 bounds = ( 120 (0, 0), 121 (width, height) 122 ) 123 124 start_deg = math.degrees(start_rad) - 90 125 end_deg = math.degrees(end_rad) - 90 126 127 if fill_enabled and fill_color is not None: 128 target_surface.chord( 129 bounds, 130 start_deg, 131 end_deg, 132 fill=fill_color 133 ) 134 135 if stroke_enabled and stroke_color is not None: 136 target_surface.arc( 137 bounds, 138 start_deg, 139 end_deg, 140 fill=stroke_color, 141 width=stroke_weight_realized 142 ) 143 144 return PillowUtilImage( 145 min_x, 146 min_y, 147 width_offset, 148 height_offset, 149 target_image 150 )
Draw an arc using Pillow.
Arguments:
- min_x: The left coordinate.
- min_y: The top coordinate.
- width: Width of the arc in pixels.
- height: Height of the arc in pixels.
- start_rad: Starting angle (radians) of the arc.
- end_rad: Ending angle (radians) of the arc.
- stroke_enabled: Boolean indicating if the stroke should be drawn.
- fill_enabled: Boolean indicating if the fill should be drawn.
- stroke_color: The color as tuple with which the stroke should be drawn or None if no stroke.
- fill_color: The color as tuple with which the fill should be drawn or None if no fill.
- stroke_weight: The size of the stroke in pixels.
Returns:
Decorated Pillow image with the drawn arc.
153def make_rect_image(min_x: float, min_y: float, width: float, height: float, stroke_enabled: bool, 154 fill_enabled: bool, stroke_color: COLOR_MAYBE, fill_color: COLOR_MAYBE, 155 stroke_weight: float) -> PillowUtilImage: 156 """Draw a rectangle using Pillow. 157 158 Args: 159 min_x: The left coordinate. 160 min_y: The top coordinate. 161 width: Width of the rect in pixels. 162 height: Height of the rect in pixels. 163 stroke_enabled: Boolean indicating if the stroke should be drawn. 164 fill_enabled: Boolean indicating if the fill should be drawn. 165 stroke_color: The color as tuple with which the stroke should be drawn or None if no stroke. 166 fill_color: The color as tuple with which the fill should be drawn or None if no fill. 167 stroke_weight: The size of the stroke in pixels. 168 169 Returns: 170 Decorated Pillow image with the drawn rect. 171 """ 172 if stroke_enabled: 173 stroke_weight_realized = stroke_weight 174 else: 175 stroke_weight_realized = 0 176 177 width_offset = width + math.floor(stroke_weight_realized / 2) * 2 178 height_offset = height + math.floor(stroke_weight_realized / 2) * 2 179 180 size = (round(width_offset) + 1, round(height_offset) + 1) 181 target_image = PIL.Image.new('RGBA', size, (255, 255, 255, 0)) 182 target_surface = PIL.ImageDraw.Draw(target_image, 'RGBA') 183 184 bounds = ( 185 0, 186 0, 187 width_offset, 188 height_offset 189 ) 190 target_surface.rectangle( 191 bounds, 192 fill=fill_color if fill_enabled else None, 193 outline=stroke_color if stroke_enabled else None, 194 width=stroke_weight_realized 195 ) 196 197 return PillowUtilImage( 198 min_x - round(stroke_weight_realized / 2), 199 min_y - round(stroke_weight_realized / 2), 200 width_offset, 201 height_offset, 202 target_image 203 )
Draw a rectangle using Pillow.
Arguments:
- min_x: The left coordinate.
- min_y: The top coordinate.
- width: Width of the rect in pixels.
- height: Height of the rect in pixels.
- stroke_enabled: Boolean indicating if the stroke should be drawn.
- fill_enabled: Boolean indicating if the fill should be drawn.
- stroke_color: The color as tuple with which the stroke should be drawn or None if no stroke.
- fill_color: The color as tuple with which the fill should be drawn or None if no fill.
- stroke_weight: The size of the stroke in pixels.
Returns:
Decorated Pillow image with the drawn rect.
206def make_ellipse_image(min_x: float, min_y: float, width: float, height: float, 207 stroke_enabled: bool, fill_enabled: bool, stroke_color: COLOR_MAYBE, fill_color: COLOR_MAYBE, 208 stroke_weight: float) -> PillowUtilImage: 209 """Draw a ellipse using Pillow. 210 211 Args: 212 min_x: The left coordinate. 213 min_y: The top coordinate. 214 width: Width of the rect in pixels. 215 height: Height of the rect in pixels. 216 stroke_enabled: Boolean indicating if the stroke should be drawn. 217 fill_enabled: Boolean indicating if the fill should be drawn. 218 stroke_color: The color as tuple with which the stroke should be drawn or None if no stroke. 219 fill_color: The color as tuple with which the fill should be drawn or None if no fill. 220 stroke_weight: The size of the stroke in pixels. 221 222 Returns: 223 Decorated Pillow image with the drawn ellipse. 224 """ 225 if stroke_enabled: 226 stroke_weight_realized = stroke_weight 227 else: 228 stroke_weight_realized = 0 229 230 width_offset = width + math.floor(stroke_weight_realized / 2) * 2 231 height_offset = height + math.floor(stroke_weight_realized / 2) * 2 232 233 size = (round(width_offset) + 1, round(height_offset) + 1) 234 target_image = PIL.Image.new('RGBA', size, (255, 255, 255, 0)) 235 target_surface = PIL.ImageDraw.Draw(target_image, 'RGBA') 236 237 bounds = ( 238 0, 239 0, 240 width_offset, 241 height_offset 242 ) 243 target_surface.ellipse( 244 bounds, 245 fill=fill_color if fill_enabled else None, 246 outline=stroke_color if stroke_enabled else None, 247 width=stroke_weight_realized 248 ) 249 250 return PillowUtilImage( 251 min_x - round(stroke_weight_realized / 2), 252 min_y - round(stroke_weight_realized / 2), 253 width_offset, 254 height_offset, 255 target_image 256 )
Draw a ellipse using Pillow.
Arguments:
- min_x: The left coordinate.
- min_y: The top coordinate.
- width: Width of the rect in pixels.
- height: Height of the rect in pixels.
- stroke_enabled: Boolean indicating if the stroke should be drawn.
- fill_enabled: Boolean indicating if the fill should be drawn.
- stroke_color: The color as tuple with which the stroke should be drawn or None if no stroke.
- fill_color: The color as tuple with which the fill should be drawn or None if no fill.
- stroke_weight: The size of the stroke in pixels.
Returns:
Decorated Pillow image with the drawn ellipse.
259class SegmentSimplifier: 260 """Utility to help draw shapes' segments in Pillow.""" 261 262 def __init__(self, start_x: float, start_y: float): 263 """Create a new simplifier. 264 265 Args: 266 start_x: The starting x coordinate of the shape. 267 start_y: The starting y coordinate of the shape. 268 """ 269 self._previous_x = start_x 270 self._previous_y = start_y 271 272 def simplify(self, 273 segment: sketchingpy.shape_struct.Line) -> typing.Iterable[typing.Iterable[float]]: 274 """Turn a segment into a series of coordinates. 275 276 Simplify a segment into a simple series of x, y coordinates which approximate the underlying 277 shape using a series of straight lines. 278 279 Args: 280 segment: The segment to simplify. 281 282 Returns: 283 Collection of x, y coordinates. 284 """ 285 ret_vals: typing.Iterable[typing.Tuple[float, float]] = [] 286 287 strategy = segment.get_strategy() 288 if strategy == 'straight': 289 ret_vals = ((segment.get_destination_x(), segment.get_destination_y()),) 290 elif strategy == 'bezier': 291 change_y = abs(segment.get_control_y2() - segment.get_control_y1()) 292 change_x = abs(segment.get_control_x2() - segment.get_control_x1()) 293 294 num_segs = (change_y**2 + change_x**2) ** 0.5 / 10 295 num_segs_int = int(num_segs) 296 297 bezier_maker = sketchingpy.bezier_util.BezierMaker() 298 bezier_maker.add_point(self._previous_x, self._previous_y) 299 bezier_maker.add_point(segment.get_control_x1(), segment.get_control_y1()) 300 bezier_maker.add_point(segment.get_control_x2(), segment.get_control_y2()) 301 bezier_maker.add_point(segment.get_destination_x(), segment.get_destination_y()) 302 303 ret_vals = bezier_maker.get_points(num_segs_int) 304 else: 305 raise RuntimeError('Unknown segment strategy: ' + strategy) 306 307 self._previous_x = segment.get_destination_x() 308 self._previous_y = segment.get_destination_y() 309 310 return ret_vals
Utility to help draw shapes' segments in Pillow.
262 def __init__(self, start_x: float, start_y: float): 263 """Create a new simplifier. 264 265 Args: 266 start_x: The starting x coordinate of the shape. 267 start_y: The starting y coordinate of the shape. 268 """ 269 self._previous_x = start_x 270 self._previous_y = start_y
Create a new simplifier.
Arguments:
- start_x: The starting x coordinate of the shape.
- start_y: The starting y coordinate of the shape.
272 def simplify(self, 273 segment: sketchingpy.shape_struct.Line) -> typing.Iterable[typing.Iterable[float]]: 274 """Turn a segment into a series of coordinates. 275 276 Simplify a segment into a simple series of x, y coordinates which approximate the underlying 277 shape using a series of straight lines. 278 279 Args: 280 segment: The segment to simplify. 281 282 Returns: 283 Collection of x, y coordinates. 284 """ 285 ret_vals: typing.Iterable[typing.Tuple[float, float]] = [] 286 287 strategy = segment.get_strategy() 288 if strategy == 'straight': 289 ret_vals = ((segment.get_destination_x(), segment.get_destination_y()),) 290 elif strategy == 'bezier': 291 change_y = abs(segment.get_control_y2() - segment.get_control_y1()) 292 change_x = abs(segment.get_control_x2() - segment.get_control_x1()) 293 294 num_segs = (change_y**2 + change_x**2) ** 0.5 / 10 295 num_segs_int = int(num_segs) 296 297 bezier_maker = sketchingpy.bezier_util.BezierMaker() 298 bezier_maker.add_point(self._previous_x, self._previous_y) 299 bezier_maker.add_point(segment.get_control_x1(), segment.get_control_y1()) 300 bezier_maker.add_point(segment.get_control_x2(), segment.get_control_y2()) 301 bezier_maker.add_point(segment.get_destination_x(), segment.get_destination_y()) 302 303 ret_vals = bezier_maker.get_points(num_segs_int) 304 else: 305 raise RuntimeError('Unknown segment strategy: ' + strategy) 306 307 self._previous_x = segment.get_destination_x() 308 self._previous_y = segment.get_destination_y() 309 310 return ret_vals
Turn a segment into a series of coordinates.
Simplify a segment into a simple series of x, y coordinates which approximate the underlying shape using a series of straight lines.
Arguments:
- segment: The segment to simplify.
Returns:
Collection of x, y coordinates.
313def make_shape_image(shape: sketchingpy.shape_struct.Shape, stroke_enabled: bool, 314 fill_enabled: bool, stroke_color: COLOR_MAYBE, fill_color: COLOR_MAYBE, 315 stroke_weight: float) -> PillowUtilImage: 316 """Draw a Sketchingpy shape into a pillow image. 317 318 Args: 319 shape: The shape to be drawn. 320 stroke_enabled: Boolean indicating if the stroke should be drawn. 321 fill_enabled: Boolean indicating if the fill should be drawn. 322 stroke_color: The color as tuple with which the stroke should be drawn or None if no stroke. 323 fill_color: The color as tuple with which the fill should be drawn or None if no fill. 324 stroke_weight: The size of the stroke in pixels. 325 326 Returns: 327 Decorated Pillow image with the drawn shape. 328 """ 329 330 if not shape.get_is_finished(): 331 raise RuntimeError('Finish shape before drawing.') 332 333 min_x = shape.get_min_x() 334 max_x = shape.get_max_x() 335 min_y = shape.get_min_y() 336 max_y = shape.get_max_y() 337 338 width = max_x - min_x 339 height = max_y - min_y 340 width_offset = width + stroke_weight * 2 341 height_offset = height + stroke_weight * 2 342 343 size = (round(width_offset) + 1, round(height_offset) + 1) 344 target_image = PIL.Image.new('RGBA', size, (255, 255, 255, 0)) 345 target_surface = PIL.ImageDraw.Draw(target_image, 'RGBA') 346 347 def adjust_coord(coord): 348 return ( 349 coord[0] - min_x + stroke_weight, 350 coord[1] - min_y + stroke_weight 351 ) 352 353 start_x = shape.get_start_x() 354 start_y = shape.get_start_y() 355 start_coords = [(start_x, start_y)] 356 357 simplified_segements = [] 358 simplifier = SegmentSimplifier(start_x, start_y) 359 for segment in shape.get_segments(): 360 simplified_segements.append(simplifier.simplify(segment)) 361 362 later_coords = itertools.chain(*simplified_segements) 363 all_coords = itertools.chain(start_coords, later_coords) 364 coords = [adjust_coord(x) for x in all_coords] 365 366 if shape.get_is_closed(): 367 target_surface.polygon(coords, fill=fill_color, outline=stroke_color, width=stroke_weight) 368 else: 369 target_surface.line(coords, fill=stroke_color, width=stroke_weight, joint='curve') 370 371 return PillowUtilImage( 372 min_x - stroke_weight, 373 min_y - stroke_weight, 374 width_offset, 375 height_offset, 376 target_image 377 )
Draw a Sketchingpy shape into a pillow image.
Arguments:
- shape: The shape to be drawn.
- stroke_enabled: Boolean indicating if the stroke should be drawn.
- fill_enabled: Boolean indicating if the fill should be drawn.
- stroke_color: The color as tuple with which the stroke should be drawn or None if no stroke.
- fill_color: The color as tuple with which the fill should be drawn or None if no fill.
- stroke_weight: The size of the stroke in pixels.
Returns:
Decorated Pillow image with the drawn shape.
380def make_text_image(x: float, y: float, content: str, font: PIL.ImageFont.ImageFont, 381 stroke_enabled: bool, fill_enabled: bool, stroke: COLOR_MAYBE, fill: COLOR_MAYBE, 382 stroke_weight: float, anchor: str): 383 """Draw text into a pillow image. 384 385 Args: 386 x: The x coordinate of the anchor. 387 y: The y coordinate of the anchor. 388 font: The font (PIL native) to use in drawing the text. 389 stroke_enabled: Boolean indicating if the stroke should be drawn. 390 fill_enabled: Boolean indicating if the fill should be drawn. 391 stroke: The color as tuple with which the stroke should be drawn or None if no stroke. 392 fill: The color as tuple with which the fill should be drawn or None if no fill. 393 stroke_weight: The size of the stroke in pixels. 394 anchor: Anchor string describing vertical and horizontal alignment. 395 396 Returns: 397 Decorated Pillow image with the drawn text. 398 """ 399 400 temp_image = PIL.Image.new('RGBA', (1, 1), (255, 255, 255, 0)) 401 temp_surface = PIL.ImageDraw.Draw(temp_image, 'RGBA') 402 stroke_weight_int = round(stroke_weight) 403 bounding_box = temp_surface.textbbox( 404 (stroke_weight_int, stroke_weight_int), 405 content, 406 font=font, 407 anchor=anchor, 408 stroke_width=stroke_weight_int 409 ) 410 411 start_x = bounding_box[0] 412 end_x = bounding_box[2] 413 414 start_y = bounding_box[1] 415 end_y = bounding_box[3] 416 417 width = end_x - start_x 418 height = end_y - start_y 419 420 width_offset = width + stroke_weight * 2 421 height_offset = height + stroke_weight * 2 422 423 size = (round(width_offset) + 2, round(height_offset) + 1) 424 target_image = PIL.Image.new('RGBA', size, (255, 255, 255, 0)) 425 target_surface = PIL.ImageDraw.Draw(target_image, 'RGBA') 426 427 if stroke_enabled: 428 target_surface.text( 429 ( 430 round(-1 * start_x + stroke_weight + 1), 431 round(-1 * start_y + stroke_weight) 432 ), 433 content, 434 font=font, 435 anchor=anchor, 436 stroke_width=round(stroke_weight), 437 stroke_fill=stroke, 438 fill=(0, 0, 0, 0) 439 ) 440 441 if fill_enabled: 442 target_surface.text( 443 ( 444 -1 * start_x + stroke_weight + 1, 445 -1 * start_y + stroke_weight 446 ), 447 content, 448 font=font, 449 anchor=anchor, 450 fill=fill 451 ) 452 453 return PillowUtilImage( 454 start_x - stroke_weight + x, 455 start_y - stroke_weight + y, 456 width_offset, 457 height_offset, 458 target_image 459 )
Draw text into a pillow image.
Arguments:
- x: The x coordinate of the anchor.
- y: The y coordinate of the anchor.
- font: The font (PIL native) to use in drawing the text.
- stroke_enabled: Boolean indicating if the stroke should be drawn.
- fill_enabled: Boolean indicating if the fill should be drawn.
- stroke: The color as tuple with which the stroke should be drawn or None if no stroke.
- fill: The color as tuple with which the fill should be drawn or None if no fill.
- stroke_weight: The size of the stroke in pixels.
- anchor: Anchor string describing vertical and horizontal alignment.
Returns:
Decorated Pillow image with the drawn text.