sketchingpy.sketch2dstatic
Pillow-based renderer for Sketchingpy.
License:
BSD
1"""Pillow-based renderer for Sketchingpy. 2 3License: 4 BSD 5""" 6 7import copy 8import typing 9import PIL.Image 10import PIL.ImageColor 11import PIL.ImageFont 12 13has_matplot_lib = False 14try: 15 import matplotlib.pyplot # type: ignore 16 has_matplot_lib = True 17except: 18 pass 19 20 21has_numpy_lib = False 22try: 23 import numpy 24 has_numpy_lib = True 25except: 26 pass 27 28import sketchingpy.abstracted 29import sketchingpy.const 30import sketchingpy.control_struct 31import sketchingpy.data_struct 32import sketchingpy.local_data_struct 33import sketchingpy.pillow_struct 34import sketchingpy.pillow_util 35import sketchingpy.state_struct 36import sketchingpy.transform 37 38DEFAULT_FPS = 20 39MANUAL_OFFSET = False 40TRANSFORMED_WRITABLE = sketchingpy.pillow_struct.TransformedWritable 41 42 43class Sketch2DStatic(sketchingpy.abstracted.Sketch): 44 """Pillow-based Sketch renderer.""" 45 46 def __init__(self, width: int, height: int, title: typing.Optional[str] = None, 47 loading_src: typing.Optional[str] = None): 48 """Create a new Pillow-based sketch. 49 50 Args: 51 width: The width of the sketch in pixels. This will be used as the horizontal image 52 size. 53 height: The height of the sketch in pixels. This will be used as the vertical image 54 size. 55 title: Title for the sketch. Ignored, reserved for future use. 56 loading_src: ID for loading screen. Ignored, reserved for future use. 57 """ 58 super().__init__() 59 60 # System params 61 self._width = width 62 self._height = height 63 64 # Internal image 65 native_size = (self._width, self._height) 66 target_image = PIL.Image.new('RGB', native_size) 67 target_draw = PIL.ImageDraw.Draw(target_image, 'RGBA') 68 69 self._target_writable = sketchingpy.pillow_struct.WritableImage(target_image, target_draw) 70 self._base_writable = self._target_writable 71 self._buffers: typing.Dict[str, sketchingpy.pillow_struct.WritableImage] = {} 72 73 self._target_macro: typing.Optional[sketchingpy.pillow_struct.Macro] = None 74 self._macros: typing.Dict[str, sketchingpy.pillow_struct.Macro] = {} 75 self._in_macro = False 76 77 # Other internals 78 self._transformer = sketchingpy.transform.Transformer() 79 self._transformer_stack: typing.List[sketchingpy.transform.Transformer] = [] 80 81 ########## 82 # Buffer # 83 ########## 84 85 def create_buffer(self, name: str, width: int, height: int, 86 background: typing.Optional[str] = None): 87 if name in self._buffers: 88 del self._buffers[name] 89 90 if name in self._macros: 91 del self._macros[name] 92 93 has_alpha = self._get_is_color_transparent(background) 94 if has_alpha: 95 self._macros[name] = sketchingpy.pillow_struct.Macro(width, height) 96 else: 97 self._buffers[name] = self._make_buffer_surface( 98 sketchingpy.pillow_struct.Rect(0, 0, width, height) 99 ) 100 rect = (0, 0, width, height) 101 self._buffers[name].get_drawable().rectangle(rect, fill=background, width=0) 102 103 def enter_buffer(self, name: str): 104 if name in self._buffers: 105 self._target_writable = self._buffers[name] 106 self._in_macro = False 107 else: 108 self._target_macro = self._macros[name] 109 self._in_macro = True 110 111 def exit_buffer(self): 112 self._target_writable = self._base_writable 113 self._in_macro = False 114 115 def draw_buffer(self, x: float, y: float, name: str): 116 if name in self._buffers: 117 subject = self._buffers[name] 118 transformed = self._get_transformed(subject.get_image(), x, y) 119 self._draw_or_queue_transformed(transformed) 120 elif name in self._macros: 121 compiled = self._macros[name].get() 122 moved = map(lambda piece: piece.get_with_offset(x, y), compiled) 123 if self._in_macro: 124 for piece in moved: 125 self._get_target_marco().append(piece) 126 else: 127 for piece in moved: 128 self._get_retransformed(piece).draw(self._target_writable) # type: ignore 129 else: 130 raise RuntimeError('Unknown buffer: ' + name) 131 132 ############ 133 # Controls # 134 ############ 135 136 def get_keyboard(self) -> typing.Optional[sketchingpy.control_struct.Keyboard]: 137 return None 138 139 def get_mouse(self) -> typing.Optional[sketchingpy.control_struct.Mouse]: 140 return None 141 142 ######## 143 # Data # 144 ######## 145 146 def get_data_layer(self) -> typing.Optional[sketchingpy.data_struct.DataLayer]: 147 return sketchingpy.local_data_struct.LocalDataLayer() 148 149 ########### 150 # Dialogs # 151 ########### 152 153 def get_dialog_layer(self) -> typing.Optional[sketchingpy.dialog_struct.DialogLayer]: 154 return None 155 156 ########### 157 # Drawing # 158 ########### 159 160 def clear(self, color_hex: str): 161 color = PIL.ImageColor.getrgb(color_hex) 162 transformed = sketchingpy.pillow_struct.TransformedClear(color) 163 self._draw_or_queue_transformed(transformed) 164 165 def draw_arc(self, x1: float, y1: float, x2: float, y2: float, a1: float, 166 a2: float): 167 state_machine = self._get_current_state_machine() 168 169 stroke_enabled = state_machine.get_stroke_enabled() 170 fill_enabled = state_machine.get_fill_enabled() 171 stroke_native = state_machine.get_stroke_native() 172 fill_native = state_machine.get_fill_native() 173 stroke_weight = state_machine.get_stroke_weight() 174 175 mode_native = state_machine.get_arc_mode_native() 176 rect = self._build_rect_with_mode(x1, y1, x2, y2, mode_native) 177 178 a1_rad = self._convert_to_radians(a1) 179 a2_rad = self._convert_to_radians(a2) 180 181 pillow_util_image = sketchingpy.pillow_util.make_arc_image( 182 rect.get_x(), 183 rect.get_y(), 184 rect.get_width(), 185 rect.get_height(), 186 a1_rad, 187 a2_rad, 188 stroke_enabled, 189 fill_enabled, 190 stroke_native if stroke_enabled else None, 191 fill_native if fill_enabled else None, 192 stroke_weight 193 ) 194 195 transformed = self._get_transformed( 196 pillow_util_image.get_image(), 197 pillow_util_image.get_x(), 198 pillow_util_image.get_y() 199 ) 200 201 self._draw_or_queue_transformed(transformed) 202 203 def draw_ellipse(self, x1: float, y1: float, x2: float, y2: float): 204 state_machine = self._get_current_state_machine() 205 206 stroke_enabled = state_machine.get_stroke_enabled() 207 fill_enabled = state_machine.get_fill_enabled() 208 stroke_native = state_machine.get_stroke_native() 209 fill_native = state_machine.get_fill_native() 210 stroke_weight = state_machine.get_stroke_weight() 211 212 mode_native = state_machine.get_ellipse_mode_native() 213 rect = self._build_rect_with_mode(x1, y1, x2, y2, mode_native) 214 215 pillow_util_image = sketchingpy.pillow_util.make_ellipse_image( 216 rect.get_x(), 217 rect.get_y(), 218 rect.get_width(), 219 rect.get_height(), 220 stroke_enabled, 221 fill_enabled, 222 stroke_native, 223 fill_native, 224 stroke_weight 225 ) 226 227 transformed = self._get_transformed( 228 pillow_util_image.get_image(), 229 pillow_util_image.get_x(), 230 pillow_util_image.get_y() 231 ) 232 233 self._draw_or_queue_transformed(transformed) 234 235 def draw_line(self, x1: float, y1: float, x2: float, y2: float): 236 state_machine = self._get_current_state_machine() 237 if not state_machine.get_stroke_enabled(): 238 return 239 240 stroke_color = state_machine.get_stroke_native() 241 stroke_weight = state_machine.get_stroke_weight_native() 242 243 point_1 = self._transformer.transform(x1, y1) 244 point_2 = self._transformer.transform(x2, y2) 245 246 transformed = sketchingpy.pillow_struct.TransformedLine( 247 point_1.get_x(), 248 point_1.get_y(), 249 point_2.get_x(), 250 point_2.get_y(), 251 stroke_color, 252 stroke_weight * point_1.get_scale() 253 ) 254 self._draw_or_queue_transformed(transformed) 255 256 def draw_rect(self, x1: float, y1: float, x2: float, y2: float): 257 state_machine = self._get_current_state_machine() 258 259 stroke_enabled = state_machine.get_stroke_enabled() 260 fill_enabled = state_machine.get_fill_enabled() 261 stroke_native = state_machine.get_stroke_native() 262 fill_native = state_machine.get_fill_native() 263 stroke_weight = state_machine.get_stroke_weight() 264 265 mode_native = state_machine.get_rect_mode_native() 266 rect = self._build_rect_with_mode(x1, y1, x2, y2, mode_native) 267 268 pillow_util_image = sketchingpy.pillow_util.make_rect_image( 269 rect.get_x(), 270 rect.get_y(), 271 rect.get_width(), 272 rect.get_height(), 273 stroke_enabled, 274 fill_enabled, 275 stroke_native, 276 fill_native, 277 stroke_weight 278 ) 279 280 transformed = self._get_transformed( 281 pillow_util_image.get_image(), 282 pillow_util_image.get_x(), 283 pillow_util_image.get_y() 284 ) 285 286 self._draw_or_queue_transformed(transformed) 287 288 def draw_shape(self, shape: sketchingpy.shape_struct.Shape): 289 if not shape.get_is_finished(): 290 raise RuntimeError('Finish your shape before drawing.') 291 292 state_machine = self._get_current_state_machine() 293 294 stroke_enabled = state_machine.get_stroke_enabled() 295 fill_enabled = state_machine.get_fill_enabled() 296 stroke_native = state_machine.get_stroke_native() 297 fill_native = state_machine.get_fill_native() 298 stroke_weight = state_machine.get_stroke_weight() 299 300 pillow_util_image = sketchingpy.pillow_util.make_shape_image( 301 shape, 302 stroke_enabled, 303 fill_enabled, 304 stroke_native if stroke_enabled else None, 305 fill_native if fill_enabled else None, 306 stroke_weight 307 ) 308 309 transformed = self._get_transformed( 310 pillow_util_image.get_image(), 311 pillow_util_image.get_x(), 312 pillow_util_image.get_y() 313 ) 314 self._draw_or_queue_transformed(transformed) 315 316 def draw_text(self, x: float, y: float, content: str): 317 content = str(content) 318 state_machine = self._get_current_state_machine() 319 320 y = y + 1 321 322 stroke_enabled = state_machine.get_stroke_enabled() 323 fill_enabled = state_machine.get_fill_enabled() 324 stroke_native = state_machine.get_stroke_native() 325 fill_native = state_machine.get_fill_native() 326 stroke_weight = state_machine.get_stroke_weight() 327 328 text_font = state_machine.get_text_font_native() 329 330 align_info = state_machine.get_text_align_native() 331 anchor_str = align_info.get_horizontal_align() + align_info.get_vertical_align() 332 333 pillow_util_image = sketchingpy.pillow_util.make_text_image( 334 x, 335 y, 336 content, 337 text_font, 338 stroke_enabled, 339 fill_enabled, 340 stroke_native, 341 fill_native, 342 stroke_weight, 343 anchor_str 344 ) 345 346 transformed = self._get_transformed( 347 pillow_util_image.get_image(), 348 pillow_util_image.get_x(), 349 pillow_util_image.get_y() 350 ) 351 self._draw_or_queue_transformed(transformed) 352 353 ########## 354 # Events # 355 ########## 356 357 def on_step(self, callback: sketchingpy.abstracted.StepCallback): 358 pass 359 360 def on_quit(self, callback: sketchingpy.abstracted.QuitCallback): 361 pass 362 363 ######### 364 # Image # 365 ######### 366 367 def get_image(self, src: str) -> sketchingpy.abstracted.Image: 368 return PillowImage(src) 369 370 def draw_image(self, x: float, y: float, image: sketchingpy.abstracted.Image): 371 if not image.get_is_loaded(): 372 return 373 374 image_mode_native = self._get_current_state_machine().get_image_mode_native() 375 376 rect = self._build_rect_with_mode( 377 x, 378 y, 379 image.get_width(), 380 image.get_height(), 381 image_mode_native 382 ) 383 384 surface = image.get_native() 385 386 transformed = self._get_transformed(surface, rect.get_x(), rect.get_y()) 387 self._draw_or_queue_transformed(transformed) 388 389 def save_image(self, path: str): 390 if self._in_macro: 391 width = self._get_target_marco().get_width() 392 height = self._get_target_marco().get_height() 393 target = self._make_buffer_surface(sketchingpy.pillow_struct.Rect(0, 0, width, height)) 394 395 for compiled in self._get_target_marco().get(): 396 compiled.draw(target) 397 398 target.get_image().save(path) 399 else: 400 self._target_writable.get_image().save(path) 401 402 ######### 403 # State # 404 ######### 405 406 def push_transform(self): 407 self._transformer_stack.append(copy.deepcopy(self._transformer)) 408 409 def pop_transform(self): 410 if len(self._transformer_stack) == 0: 411 raise RuntimeError('Transformation stack empty.') 412 413 self._transformer = self._transformer_stack.pop() 414 415 ########## 416 # System # 417 ########## 418 419 def get_millis_shown(self): 420 return 0 421 422 def get_native(self): 423 return self._target_writable 424 425 def set_fps(self, rate: int): 426 pass 427 428 def set_title(self, title: str): 429 pass 430 431 def quit(self): 432 pass 433 434 def show(self, ax=None): 435 if has_matplot_lib and has_numpy_lib: 436 if ax is None: 437 ax = matplotlib.pyplot.subplot(111) 438 ax.axis('off') 439 440 ax.imshow(numpy.asarray(self._target_writable.get_image())) 441 else: 442 raise RuntimeError('Install matplotlib and numpy or use save instead.') 443 444 def show_and_quit(self, ax=None): 445 pass 446 447 ############# 448 # Transform # 449 ############# 450 451 def translate(self, x: float, y: float): 452 self._transformer.translate(x, y) 453 454 def rotate(self, angle_mirror: float): 455 angle = -1 * angle_mirror 456 angle_rad = self._convert_to_radians(angle) 457 self._transformer.rotate(angle_rad) 458 459 def scale(self, scale: float): 460 self._transformer.scale(scale) 461 462 ########### 463 # Support # 464 ########### 465 466 def _get_target_marco(self) -> sketchingpy.pillow_struct.Macro: 467 assert self._target_macro is not None 468 return self._target_macro 469 470 def _draw_or_queue_transformed(self, 471 transformed: sketchingpy.pillow_struct.TransformedDrawable): 472 if self._in_macro: 473 self._get_target_marco().append(transformed) 474 else: 475 transformed.draw(self._target_writable) 476 477 def _create_state_machine(self) -> sketchingpy.state_struct.SketchStateMachine: 478 return PillowSketchStateMachine() 479 480 def _make_buffer_surface(self, 481 rect: sketchingpy.pillow_struct.Rect) -> sketchingpy.pillow_struct.WritableImage: 482 native_size = (round(rect.get_width()), round(rect.get_height())) 483 target_image = PIL.Image.new('RGB', native_size, (255, 255, 255, 0)) 484 target_draw = PIL.ImageDraw.Draw(target_image, 'RGBA') 485 return sketchingpy.pillow_struct.WritableImage(target_image, target_draw) 486 487 def _offset_stroke_weight(self, rect: sketchingpy.pillow_struct.Rect, 488 stroke_weight: float) -> sketchingpy.pillow_struct.Rect: 489 if not MANUAL_OFFSET: 490 return rect 491 492 half_weight = stroke_weight / 2 493 return sketchingpy.pillow_struct.Rect( 494 rect.get_x() - half_weight, 495 rect.get_y() - half_weight, 496 rect.get_width() + half_weight * 2, 497 rect.get_height() + half_weight * 2 498 ) 499 500 def _offset_fill_weight(self, rect: sketchingpy.pillow_struct.Rect, 501 stroke_weight: float) -> sketchingpy.pillow_struct.Rect: 502 if not MANUAL_OFFSET: 503 return rect 504 505 half_weight = stroke_weight / 2 506 return sketchingpy.pillow_struct.Rect( 507 rect.get_x() + half_weight, 508 rect.get_y() + half_weight, 509 rect.get_width() - half_weight * 2, 510 rect.get_height() - half_weight * 2 511 ) 512 513 def _zero_rect(self, rect: sketchingpy.pillow_struct.Rect) -> sketchingpy.pillow_struct.Rect: 514 return sketchingpy.pillow_struct.zero_rect(rect) 515 516 def _build_rect_with_mode(self, x1: float, y1: float, x2: float, y2: float, 517 native_mode: int) -> sketchingpy.pillow_struct.Rect: 518 return sketchingpy.pillow_struct.build_rect_with_mode(x1, y1, x2, y2, native_mode) 519 520 def _get_transformed(self, surface: PIL.Image.Image, x: float, 521 y: float) -> sketchingpy.pillow_struct.TransformedWritable: 522 return sketchingpy.pillow_struct.get_transformed( 523 self._transformer, 524 surface, 525 x, 526 y 527 ) 528 529 def _get_retransformed(self, target: TRANSFORMED_WRITABLE) -> TRANSFORMED_WRITABLE: 530 return sketchingpy.pillow_struct.get_retransformed( 531 self._transformer, 532 target 533 ) 534 535 536class PillowSketchStateMachine(sketchingpy.state_struct.SketchStateMachine): 537 538 def __init__(self): 539 super().__init__() 540 self._fill_native = self._convert_color(super().get_fill()) 541 self._stroke_native = self._convert_color(super().get_stroke()) 542 self._font_cache = {} 543 self._text_align_native = self._transform_text_align(super().get_text_align_native()) 544 545 def set_fill(self, fill: str): 546 super().set_fill(fill) 547 self._fill_native = self._convert_color(super().get_fill()) 548 549 def get_fill_native(self): 550 return self._fill_native 551 552 def set_stroke(self, stroke: str): 553 super().set_stroke(stroke) 554 self._stroke_native = self._convert_color(super().get_stroke()) 555 556 def get_stroke_native(self): 557 return self._stroke_native 558 559 def get_text_font_native(self): 560 font = self.get_text_font() 561 key = '%s.%d' % (font.get_identifier(), font.get_size()) 562 563 if key not in self._font_cache: 564 new_font = PIL.ImageFont.truetype(font.get_identifier(), font.get_size()) 565 self._font_cache[key] = new_font 566 567 return self._font_cache[key] 568 569 def set_text_align(self, text_align: sketchingpy.state_struct.TextAlign): 570 super().set_text_align(text_align) 571 self._text_align_native = self._transform_text_align(super().get_text_align_native()) 572 573 def get_text_align_native(self): 574 return self._text_align_native 575 576 def _transform_text_align(self, 577 text_align: sketchingpy.state_struct.TextAlign) -> sketchingpy.state_struct.TextAlign: 578 579 HORIZONTAL_ALIGNS = { 580 sketchingpy.const.LEFT: 'l', 581 sketchingpy.const.CENTER: 'm', 582 sketchingpy.const.RIGHT: 'r' 583 } 584 585 VERTICAL_ALIGNS = { 586 sketchingpy.const.TOP: 't', 587 sketchingpy.const.CENTER: 'm', 588 sketchingpy.const.BASELINE: 's', 589 sketchingpy.const.BOTTOM: 'b' 590 } 591 592 return sketchingpy.state_struct.TextAlign( 593 HORIZONTAL_ALIGNS[text_align.get_horizontal_align()], 594 VERTICAL_ALIGNS[text_align.get_vertical_align()] 595 ) 596 597 def _convert_color(self, target: str) -> sketchingpy.pillow_struct.COLOR_TUPLE: 598 return PIL.ImageColor.getrgb(target) 599 600 601class PillowImage(sketchingpy.abstracted.Image): 602 603 def __init__(self, src: str): 604 super().__init__(src) 605 self._native = PIL.Image.open(src) 606 607 def get_width(self) -> float: 608 return self._native.width 609 610 def get_height(self) -> float: 611 return self._native.height 612 613 def resize(self, width: float, height: float): 614 self._native = self._native.resize((int(width), int(height))) 615 616 def get_native(self): 617 return self._native 618 619 def get_is_loaded(self): 620 return True
44class Sketch2DStatic(sketchingpy.abstracted.Sketch): 45 """Pillow-based Sketch renderer.""" 46 47 def __init__(self, width: int, height: int, title: typing.Optional[str] = None, 48 loading_src: typing.Optional[str] = None): 49 """Create a new Pillow-based sketch. 50 51 Args: 52 width: The width of the sketch in pixels. This will be used as the horizontal image 53 size. 54 height: The height of the sketch in pixels. This will be used as the vertical image 55 size. 56 title: Title for the sketch. Ignored, reserved for future use. 57 loading_src: ID for loading screen. Ignored, reserved for future use. 58 """ 59 super().__init__() 60 61 # System params 62 self._width = width 63 self._height = height 64 65 # Internal image 66 native_size = (self._width, self._height) 67 target_image = PIL.Image.new('RGB', native_size) 68 target_draw = PIL.ImageDraw.Draw(target_image, 'RGBA') 69 70 self._target_writable = sketchingpy.pillow_struct.WritableImage(target_image, target_draw) 71 self._base_writable = self._target_writable 72 self._buffers: typing.Dict[str, sketchingpy.pillow_struct.WritableImage] = {} 73 74 self._target_macro: typing.Optional[sketchingpy.pillow_struct.Macro] = None 75 self._macros: typing.Dict[str, sketchingpy.pillow_struct.Macro] = {} 76 self._in_macro = False 77 78 # Other internals 79 self._transformer = sketchingpy.transform.Transformer() 80 self._transformer_stack: typing.List[sketchingpy.transform.Transformer] = [] 81 82 ########## 83 # Buffer # 84 ########## 85 86 def create_buffer(self, name: str, width: int, height: int, 87 background: typing.Optional[str] = None): 88 if name in self._buffers: 89 del self._buffers[name] 90 91 if name in self._macros: 92 del self._macros[name] 93 94 has_alpha = self._get_is_color_transparent(background) 95 if has_alpha: 96 self._macros[name] = sketchingpy.pillow_struct.Macro(width, height) 97 else: 98 self._buffers[name] = self._make_buffer_surface( 99 sketchingpy.pillow_struct.Rect(0, 0, width, height) 100 ) 101 rect = (0, 0, width, height) 102 self._buffers[name].get_drawable().rectangle(rect, fill=background, width=0) 103 104 def enter_buffer(self, name: str): 105 if name in self._buffers: 106 self._target_writable = self._buffers[name] 107 self._in_macro = False 108 else: 109 self._target_macro = self._macros[name] 110 self._in_macro = True 111 112 def exit_buffer(self): 113 self._target_writable = self._base_writable 114 self._in_macro = False 115 116 def draw_buffer(self, x: float, y: float, name: str): 117 if name in self._buffers: 118 subject = self._buffers[name] 119 transformed = self._get_transformed(subject.get_image(), x, y) 120 self._draw_or_queue_transformed(transformed) 121 elif name in self._macros: 122 compiled = self._macros[name].get() 123 moved = map(lambda piece: piece.get_with_offset(x, y), compiled) 124 if self._in_macro: 125 for piece in moved: 126 self._get_target_marco().append(piece) 127 else: 128 for piece in moved: 129 self._get_retransformed(piece).draw(self._target_writable) # type: ignore 130 else: 131 raise RuntimeError('Unknown buffer: ' + name) 132 133 ############ 134 # Controls # 135 ############ 136 137 def get_keyboard(self) -> typing.Optional[sketchingpy.control_struct.Keyboard]: 138 return None 139 140 def get_mouse(self) -> typing.Optional[sketchingpy.control_struct.Mouse]: 141 return None 142 143 ######## 144 # Data # 145 ######## 146 147 def get_data_layer(self) -> typing.Optional[sketchingpy.data_struct.DataLayer]: 148 return sketchingpy.local_data_struct.LocalDataLayer() 149 150 ########### 151 # Dialogs # 152 ########### 153 154 def get_dialog_layer(self) -> typing.Optional[sketchingpy.dialog_struct.DialogLayer]: 155 return None 156 157 ########### 158 # Drawing # 159 ########### 160 161 def clear(self, color_hex: str): 162 color = PIL.ImageColor.getrgb(color_hex) 163 transformed = sketchingpy.pillow_struct.TransformedClear(color) 164 self._draw_or_queue_transformed(transformed) 165 166 def draw_arc(self, x1: float, y1: float, x2: float, y2: float, a1: float, 167 a2: float): 168 state_machine = self._get_current_state_machine() 169 170 stroke_enabled = state_machine.get_stroke_enabled() 171 fill_enabled = state_machine.get_fill_enabled() 172 stroke_native = state_machine.get_stroke_native() 173 fill_native = state_machine.get_fill_native() 174 stroke_weight = state_machine.get_stroke_weight() 175 176 mode_native = state_machine.get_arc_mode_native() 177 rect = self._build_rect_with_mode(x1, y1, x2, y2, mode_native) 178 179 a1_rad = self._convert_to_radians(a1) 180 a2_rad = self._convert_to_radians(a2) 181 182 pillow_util_image = sketchingpy.pillow_util.make_arc_image( 183 rect.get_x(), 184 rect.get_y(), 185 rect.get_width(), 186 rect.get_height(), 187 a1_rad, 188 a2_rad, 189 stroke_enabled, 190 fill_enabled, 191 stroke_native if stroke_enabled else None, 192 fill_native if fill_enabled else None, 193 stroke_weight 194 ) 195 196 transformed = self._get_transformed( 197 pillow_util_image.get_image(), 198 pillow_util_image.get_x(), 199 pillow_util_image.get_y() 200 ) 201 202 self._draw_or_queue_transformed(transformed) 203 204 def draw_ellipse(self, x1: float, y1: float, x2: float, y2: float): 205 state_machine = self._get_current_state_machine() 206 207 stroke_enabled = state_machine.get_stroke_enabled() 208 fill_enabled = state_machine.get_fill_enabled() 209 stroke_native = state_machine.get_stroke_native() 210 fill_native = state_machine.get_fill_native() 211 stroke_weight = state_machine.get_stroke_weight() 212 213 mode_native = state_machine.get_ellipse_mode_native() 214 rect = self._build_rect_with_mode(x1, y1, x2, y2, mode_native) 215 216 pillow_util_image = sketchingpy.pillow_util.make_ellipse_image( 217 rect.get_x(), 218 rect.get_y(), 219 rect.get_width(), 220 rect.get_height(), 221 stroke_enabled, 222 fill_enabled, 223 stroke_native, 224 fill_native, 225 stroke_weight 226 ) 227 228 transformed = self._get_transformed( 229 pillow_util_image.get_image(), 230 pillow_util_image.get_x(), 231 pillow_util_image.get_y() 232 ) 233 234 self._draw_or_queue_transformed(transformed) 235 236 def draw_line(self, x1: float, y1: float, x2: float, y2: float): 237 state_machine = self._get_current_state_machine() 238 if not state_machine.get_stroke_enabled(): 239 return 240 241 stroke_color = state_machine.get_stroke_native() 242 stroke_weight = state_machine.get_stroke_weight_native() 243 244 point_1 = self._transformer.transform(x1, y1) 245 point_2 = self._transformer.transform(x2, y2) 246 247 transformed = sketchingpy.pillow_struct.TransformedLine( 248 point_1.get_x(), 249 point_1.get_y(), 250 point_2.get_x(), 251 point_2.get_y(), 252 stroke_color, 253 stroke_weight * point_1.get_scale() 254 ) 255 self._draw_or_queue_transformed(transformed) 256 257 def draw_rect(self, x1: float, y1: float, x2: float, y2: float): 258 state_machine = self._get_current_state_machine() 259 260 stroke_enabled = state_machine.get_stroke_enabled() 261 fill_enabled = state_machine.get_fill_enabled() 262 stroke_native = state_machine.get_stroke_native() 263 fill_native = state_machine.get_fill_native() 264 stroke_weight = state_machine.get_stroke_weight() 265 266 mode_native = state_machine.get_rect_mode_native() 267 rect = self._build_rect_with_mode(x1, y1, x2, y2, mode_native) 268 269 pillow_util_image = sketchingpy.pillow_util.make_rect_image( 270 rect.get_x(), 271 rect.get_y(), 272 rect.get_width(), 273 rect.get_height(), 274 stroke_enabled, 275 fill_enabled, 276 stroke_native, 277 fill_native, 278 stroke_weight 279 ) 280 281 transformed = self._get_transformed( 282 pillow_util_image.get_image(), 283 pillow_util_image.get_x(), 284 pillow_util_image.get_y() 285 ) 286 287 self._draw_or_queue_transformed(transformed) 288 289 def draw_shape(self, shape: sketchingpy.shape_struct.Shape): 290 if not shape.get_is_finished(): 291 raise RuntimeError('Finish your shape before drawing.') 292 293 state_machine = self._get_current_state_machine() 294 295 stroke_enabled = state_machine.get_stroke_enabled() 296 fill_enabled = state_machine.get_fill_enabled() 297 stroke_native = state_machine.get_stroke_native() 298 fill_native = state_machine.get_fill_native() 299 stroke_weight = state_machine.get_stroke_weight() 300 301 pillow_util_image = sketchingpy.pillow_util.make_shape_image( 302 shape, 303 stroke_enabled, 304 fill_enabled, 305 stroke_native if stroke_enabled else None, 306 fill_native if fill_enabled else None, 307 stroke_weight 308 ) 309 310 transformed = self._get_transformed( 311 pillow_util_image.get_image(), 312 pillow_util_image.get_x(), 313 pillow_util_image.get_y() 314 ) 315 self._draw_or_queue_transformed(transformed) 316 317 def draw_text(self, x: float, y: float, content: str): 318 content = str(content) 319 state_machine = self._get_current_state_machine() 320 321 y = y + 1 322 323 stroke_enabled = state_machine.get_stroke_enabled() 324 fill_enabled = state_machine.get_fill_enabled() 325 stroke_native = state_machine.get_stroke_native() 326 fill_native = state_machine.get_fill_native() 327 stroke_weight = state_machine.get_stroke_weight() 328 329 text_font = state_machine.get_text_font_native() 330 331 align_info = state_machine.get_text_align_native() 332 anchor_str = align_info.get_horizontal_align() + align_info.get_vertical_align() 333 334 pillow_util_image = sketchingpy.pillow_util.make_text_image( 335 x, 336 y, 337 content, 338 text_font, 339 stroke_enabled, 340 fill_enabled, 341 stroke_native, 342 fill_native, 343 stroke_weight, 344 anchor_str 345 ) 346 347 transformed = self._get_transformed( 348 pillow_util_image.get_image(), 349 pillow_util_image.get_x(), 350 pillow_util_image.get_y() 351 ) 352 self._draw_or_queue_transformed(transformed) 353 354 ########## 355 # Events # 356 ########## 357 358 def on_step(self, callback: sketchingpy.abstracted.StepCallback): 359 pass 360 361 def on_quit(self, callback: sketchingpy.abstracted.QuitCallback): 362 pass 363 364 ######### 365 # Image # 366 ######### 367 368 def get_image(self, src: str) -> sketchingpy.abstracted.Image: 369 return PillowImage(src) 370 371 def draw_image(self, x: float, y: float, image: sketchingpy.abstracted.Image): 372 if not image.get_is_loaded(): 373 return 374 375 image_mode_native = self._get_current_state_machine().get_image_mode_native() 376 377 rect = self._build_rect_with_mode( 378 x, 379 y, 380 image.get_width(), 381 image.get_height(), 382 image_mode_native 383 ) 384 385 surface = image.get_native() 386 387 transformed = self._get_transformed(surface, rect.get_x(), rect.get_y()) 388 self._draw_or_queue_transformed(transformed) 389 390 def save_image(self, path: str): 391 if self._in_macro: 392 width = self._get_target_marco().get_width() 393 height = self._get_target_marco().get_height() 394 target = self._make_buffer_surface(sketchingpy.pillow_struct.Rect(0, 0, width, height)) 395 396 for compiled in self._get_target_marco().get(): 397 compiled.draw(target) 398 399 target.get_image().save(path) 400 else: 401 self._target_writable.get_image().save(path) 402 403 ######### 404 # State # 405 ######### 406 407 def push_transform(self): 408 self._transformer_stack.append(copy.deepcopy(self._transformer)) 409 410 def pop_transform(self): 411 if len(self._transformer_stack) == 0: 412 raise RuntimeError('Transformation stack empty.') 413 414 self._transformer = self._transformer_stack.pop() 415 416 ########## 417 # System # 418 ########## 419 420 def get_millis_shown(self): 421 return 0 422 423 def get_native(self): 424 return self._target_writable 425 426 def set_fps(self, rate: int): 427 pass 428 429 def set_title(self, title: str): 430 pass 431 432 def quit(self): 433 pass 434 435 def show(self, ax=None): 436 if has_matplot_lib and has_numpy_lib: 437 if ax is None: 438 ax = matplotlib.pyplot.subplot(111) 439 ax.axis('off') 440 441 ax.imshow(numpy.asarray(self._target_writable.get_image())) 442 else: 443 raise RuntimeError('Install matplotlib and numpy or use save instead.') 444 445 def show_and_quit(self, ax=None): 446 pass 447 448 ############# 449 # Transform # 450 ############# 451 452 def translate(self, x: float, y: float): 453 self._transformer.translate(x, y) 454 455 def rotate(self, angle_mirror: float): 456 angle = -1 * angle_mirror 457 angle_rad = self._convert_to_radians(angle) 458 self._transformer.rotate(angle_rad) 459 460 def scale(self, scale: float): 461 self._transformer.scale(scale) 462 463 ########### 464 # Support # 465 ########### 466 467 def _get_target_marco(self) -> sketchingpy.pillow_struct.Macro: 468 assert self._target_macro is not None 469 return self._target_macro 470 471 def _draw_or_queue_transformed(self, 472 transformed: sketchingpy.pillow_struct.TransformedDrawable): 473 if self._in_macro: 474 self._get_target_marco().append(transformed) 475 else: 476 transformed.draw(self._target_writable) 477 478 def _create_state_machine(self) -> sketchingpy.state_struct.SketchStateMachine: 479 return PillowSketchStateMachine() 480 481 def _make_buffer_surface(self, 482 rect: sketchingpy.pillow_struct.Rect) -> sketchingpy.pillow_struct.WritableImage: 483 native_size = (round(rect.get_width()), round(rect.get_height())) 484 target_image = PIL.Image.new('RGB', native_size, (255, 255, 255, 0)) 485 target_draw = PIL.ImageDraw.Draw(target_image, 'RGBA') 486 return sketchingpy.pillow_struct.WritableImage(target_image, target_draw) 487 488 def _offset_stroke_weight(self, rect: sketchingpy.pillow_struct.Rect, 489 stroke_weight: float) -> sketchingpy.pillow_struct.Rect: 490 if not MANUAL_OFFSET: 491 return rect 492 493 half_weight = stroke_weight / 2 494 return sketchingpy.pillow_struct.Rect( 495 rect.get_x() - half_weight, 496 rect.get_y() - half_weight, 497 rect.get_width() + half_weight * 2, 498 rect.get_height() + half_weight * 2 499 ) 500 501 def _offset_fill_weight(self, rect: sketchingpy.pillow_struct.Rect, 502 stroke_weight: float) -> sketchingpy.pillow_struct.Rect: 503 if not MANUAL_OFFSET: 504 return rect 505 506 half_weight = stroke_weight / 2 507 return sketchingpy.pillow_struct.Rect( 508 rect.get_x() + half_weight, 509 rect.get_y() + half_weight, 510 rect.get_width() - half_weight * 2, 511 rect.get_height() - half_weight * 2 512 ) 513 514 def _zero_rect(self, rect: sketchingpy.pillow_struct.Rect) -> sketchingpy.pillow_struct.Rect: 515 return sketchingpy.pillow_struct.zero_rect(rect) 516 517 def _build_rect_with_mode(self, x1: float, y1: float, x2: float, y2: float, 518 native_mode: int) -> sketchingpy.pillow_struct.Rect: 519 return sketchingpy.pillow_struct.build_rect_with_mode(x1, y1, x2, y2, native_mode) 520 521 def _get_transformed(self, surface: PIL.Image.Image, x: float, 522 y: float) -> sketchingpy.pillow_struct.TransformedWritable: 523 return sketchingpy.pillow_struct.get_transformed( 524 self._transformer, 525 surface, 526 x, 527 y 528 ) 529 530 def _get_retransformed(self, target: TRANSFORMED_WRITABLE) -> TRANSFORMED_WRITABLE: 531 return sketchingpy.pillow_struct.get_retransformed( 532 self._transformer, 533 target 534 )
Pillow-based Sketch renderer.
47 def __init__(self, width: int, height: int, title: typing.Optional[str] = None, 48 loading_src: typing.Optional[str] = None): 49 """Create a new Pillow-based sketch. 50 51 Args: 52 width: The width of the sketch in pixels. This will be used as the horizontal image 53 size. 54 height: The height of the sketch in pixels. This will be used as the vertical image 55 size. 56 title: Title for the sketch. Ignored, reserved for future use. 57 loading_src: ID for loading screen. Ignored, reserved for future use. 58 """ 59 super().__init__() 60 61 # System params 62 self._width = width 63 self._height = height 64 65 # Internal image 66 native_size = (self._width, self._height) 67 target_image = PIL.Image.new('RGB', native_size) 68 target_draw = PIL.ImageDraw.Draw(target_image, 'RGBA') 69 70 self._target_writable = sketchingpy.pillow_struct.WritableImage(target_image, target_draw) 71 self._base_writable = self._target_writable 72 self._buffers: typing.Dict[str, sketchingpy.pillow_struct.WritableImage] = {} 73 74 self._target_macro: typing.Optional[sketchingpy.pillow_struct.Macro] = None 75 self._macros: typing.Dict[str, sketchingpy.pillow_struct.Macro] = {} 76 self._in_macro = False 77 78 # Other internals 79 self._transformer = sketchingpy.transform.Transformer() 80 self._transformer_stack: typing.List[sketchingpy.transform.Transformer] = []
Create a new Pillow-based sketch.
Arguments:
- width: The width of the sketch in pixels. This will be used as the horizontal image size.
- height: The height of the sketch in pixels. This will be used as the vertical image size.
- title: Title for the sketch. Ignored, reserved for future use.
- loading_src: ID for loading screen. Ignored, reserved for future use.
86 def create_buffer(self, name: str, width: int, height: int, 87 background: typing.Optional[str] = None): 88 if name in self._buffers: 89 del self._buffers[name] 90 91 if name in self._macros: 92 del self._macros[name] 93 94 has_alpha = self._get_is_color_transparent(background) 95 if has_alpha: 96 self._macros[name] = sketchingpy.pillow_struct.Macro(width, height) 97 else: 98 self._buffers[name] = self._make_buffer_surface( 99 sketchingpy.pillow_struct.Rect(0, 0, width, height) 100 ) 101 rect = (0, 0, width, height) 102 self._buffers[name].get_drawable().rectangle(rect, fill=background, width=0)
Create a new named in-memory (or equivalent) buffer.
Arguments:
- name: The name of the buffer. If a prior buffer of this name exists, it will be replaced.
- width: The width of the buffer in pixels. In some renderers, the buffer will clip. In others, out of buffer values may be drawn.
- height: The height of the buffer in pixels. In some renderers, the buffer will clip. In others, out of buffer values may be drawn.
- background: The background to use for this buffer or None if transparent. Defaults to None.
104 def enter_buffer(self, name: str): 105 if name in self._buffers: 106 self._target_writable = self._buffers[name] 107 self._in_macro = False 108 else: 109 self._target_macro = self._macros[name] 110 self._in_macro = True
Switch rendering context to a buffer, exiting current buffer if active.
Arguments:
- name: The name of the buffer to which context should switch.
112 def exit_buffer(self): 113 self._target_writable = self._base_writable 114 self._in_macro = False
Exit the current offscreen buffer.
Exit the current offscreen buffer, returning to the actual sketch. This will act as a noop if not currently in a buffer.
116 def draw_buffer(self, x: float, y: float, name: str): 117 if name in self._buffers: 118 subject = self._buffers[name] 119 transformed = self._get_transformed(subject.get_image(), x, y) 120 self._draw_or_queue_transformed(transformed) 121 elif name in self._macros: 122 compiled = self._macros[name].get() 123 moved = map(lambda piece: piece.get_with_offset(x, y), compiled) 124 if self._in_macro: 125 for piece in moved: 126 self._get_target_marco().append(piece) 127 else: 128 for piece in moved: 129 self._get_retransformed(piece).draw(self._target_writable) # type: ignore 130 else: 131 raise RuntimeError('Unknown buffer: ' + name)
Draw an offscreen buffer to the current buffer or sketch.
Arguments:
- x: The horizontal position in pixels at which the left should be drawn.
- y: The vertical position in pixels at which the top should be drawn.
- name: The name of the buffer to draw.
Get access to the keyboard.
Get access to the keyboard currently registered with the operating system for the sketch. Different sketches running at the same time may have different keyboards depending on focus or OS configuration.
Returns:
Current keyboard or None if not found / supported.
Get access to the mouse.
Get access to the mouse currently registered with the operating system for the sketch. Different sketches running at the same time may have different mouse objects depending on focus or OS configuration. Note that the mouse may also be emulated if the device uses a touch screen.
Returns:
Current mouse or None if not found / supported.
147 def get_data_layer(self) -> typing.Optional[sketchingpy.data_struct.DataLayer]: 148 return sketchingpy.local_data_struct.LocalDataLayer()
Get access to reading and writing data.
Open access to the file system, network, or browser to read or write data.
Returns:
Facade for data access or None if not supported or insufficient permissions.
154 def get_dialog_layer(self) -> typing.Optional[sketchingpy.dialog_struct.DialogLayer]: 155 return None
Get access to rendering and using simple dialogs.
Open access to a simple dialog prefabricated UI system to show alerts, prompts, and other dialog boxes.
Returns:
Facade for rendering dialogs or None if not supported or insufficient permissions.
161 def clear(self, color_hex: str): 162 color = PIL.ImageColor.getrgb(color_hex) 163 transformed = sketchingpy.pillow_struct.TransformedClear(color) 164 self._draw_or_queue_transformed(transformed)
Clear the sketch to a color.
Peform the equivalent of drawing a rectangle the size of the sketch without stroke and with the given fill color.
Arguments:
- color: The color to use in clearing.
166 def draw_arc(self, x1: float, y1: float, x2: float, y2: float, a1: float, 167 a2: float): 168 state_machine = self._get_current_state_machine() 169 170 stroke_enabled = state_machine.get_stroke_enabled() 171 fill_enabled = state_machine.get_fill_enabled() 172 stroke_native = state_machine.get_stroke_native() 173 fill_native = state_machine.get_fill_native() 174 stroke_weight = state_machine.get_stroke_weight() 175 176 mode_native = state_machine.get_arc_mode_native() 177 rect = self._build_rect_with_mode(x1, y1, x2, y2, mode_native) 178 179 a1_rad = self._convert_to_radians(a1) 180 a2_rad = self._convert_to_radians(a2) 181 182 pillow_util_image = sketchingpy.pillow_util.make_arc_image( 183 rect.get_x(), 184 rect.get_y(), 185 rect.get_width(), 186 rect.get_height(), 187 a1_rad, 188 a2_rad, 189 stroke_enabled, 190 fill_enabled, 191 stroke_native if stroke_enabled else None, 192 fill_native if fill_enabled else None, 193 stroke_weight 194 ) 195 196 transformed = self._get_transformed( 197 pillow_util_image.get_image(), 198 pillow_util_image.get_x(), 199 pillow_util_image.get_y() 200 ) 201 202 self._draw_or_queue_transformed(transformed)
Draw a partial ellipse using starting and ending angles.
Using starting and ending angles, draw a partial ellipse which is either drawn outside line only (stroke) and / or filled from the center of that ellipse.
Arguments:
- x1: The x location at which to draw the arc.
- y1: The y location at which to draw the arc.
- x2: Horizontal size.
- y2: Vertical size.
- a1: Starting angle.
- a2: Ending angle.
204 def draw_ellipse(self, x1: float, y1: float, x2: float, y2: float): 205 state_machine = self._get_current_state_machine() 206 207 stroke_enabled = state_machine.get_stroke_enabled() 208 fill_enabled = state_machine.get_fill_enabled() 209 stroke_native = state_machine.get_stroke_native() 210 fill_native = state_machine.get_fill_native() 211 stroke_weight = state_machine.get_stroke_weight() 212 213 mode_native = state_machine.get_ellipse_mode_native() 214 rect = self._build_rect_with_mode(x1, y1, x2, y2, mode_native) 215 216 pillow_util_image = sketchingpy.pillow_util.make_ellipse_image( 217 rect.get_x(), 218 rect.get_y(), 219 rect.get_width(), 220 rect.get_height(), 221 stroke_enabled, 222 fill_enabled, 223 stroke_native, 224 fill_native, 225 stroke_weight 226 ) 227 228 transformed = self._get_transformed( 229 pillow_util_image.get_image(), 230 pillow_util_image.get_x(), 231 pillow_util_image.get_y() 232 ) 233 234 self._draw_or_queue_transformed(transformed)
Draw a circle or ellipse.
Draw an ellipse or, in the case of equal width and height, a circle.
Arguments:
- x1: The x location at which to draw the ellipse.
- y1: The y location at which to draw the ellipse.
- x2: Horizontal size.
- y2: Vertical size.
236 def draw_line(self, x1: float, y1: float, x2: float, y2: float): 237 state_machine = self._get_current_state_machine() 238 if not state_machine.get_stroke_enabled(): 239 return 240 241 stroke_color = state_machine.get_stroke_native() 242 stroke_weight = state_machine.get_stroke_weight_native() 243 244 point_1 = self._transformer.transform(x1, y1) 245 point_2 = self._transformer.transform(x2, y2) 246 247 transformed = sketchingpy.pillow_struct.TransformedLine( 248 point_1.get_x(), 249 point_1.get_y(), 250 point_2.get_x(), 251 point_2.get_y(), 252 stroke_color, 253 stroke_weight * point_1.get_scale() 254 ) 255 self._draw_or_queue_transformed(transformed)
Draw a simple line.
Draw a line between two points.
Arguments:
- x1: The x coordinate from which the line should be drawn.
- y1: The y coordinate from which the line should be drawn.
- x2: The x coordinate to which the line should be drawn.
- y2: The y coordinate to which the line should be drawn.
257 def draw_rect(self, x1: float, y1: float, x2: float, y2: float): 258 state_machine = self._get_current_state_machine() 259 260 stroke_enabled = state_machine.get_stroke_enabled() 261 fill_enabled = state_machine.get_fill_enabled() 262 stroke_native = state_machine.get_stroke_native() 263 fill_native = state_machine.get_fill_native() 264 stroke_weight = state_machine.get_stroke_weight() 265 266 mode_native = state_machine.get_rect_mode_native() 267 rect = self._build_rect_with_mode(x1, y1, x2, y2, mode_native) 268 269 pillow_util_image = sketchingpy.pillow_util.make_rect_image( 270 rect.get_x(), 271 rect.get_y(), 272 rect.get_width(), 273 rect.get_height(), 274 stroke_enabled, 275 fill_enabled, 276 stroke_native, 277 fill_native, 278 stroke_weight 279 ) 280 281 transformed = self._get_transformed( 282 pillow_util_image.get_image(), 283 pillow_util_image.get_x(), 284 pillow_util_image.get_y() 285 ) 286 287 self._draw_or_queue_transformed(transformed)
Draw a rectangle.
Draw a rectangle or, if width and height are the same, a square.
Arguments:
- x1: The x location at which to draw the rectangle.
- y1: The y location at which to draw the rectangle.
- x2: Horizontal size.
- y2: Vertical size.
289 def draw_shape(self, shape: sketchingpy.shape_struct.Shape): 290 if not shape.get_is_finished(): 291 raise RuntimeError('Finish your shape before drawing.') 292 293 state_machine = self._get_current_state_machine() 294 295 stroke_enabled = state_machine.get_stroke_enabled() 296 fill_enabled = state_machine.get_fill_enabled() 297 stroke_native = state_machine.get_stroke_native() 298 fill_native = state_machine.get_fill_native() 299 stroke_weight = state_machine.get_stroke_weight() 300 301 pillow_util_image = sketchingpy.pillow_util.make_shape_image( 302 shape, 303 stroke_enabled, 304 fill_enabled, 305 stroke_native if stroke_enabled else None, 306 fill_native if fill_enabled else None, 307 stroke_weight 308 ) 309 310 transformed = self._get_transformed( 311 pillow_util_image.get_image(), 312 pillow_util_image.get_x(), 313 pillow_util_image.get_y() 314 ) 315 self._draw_or_queue_transformed(transformed)
Draw a shape.
Draw a shape which consists of multiple line or curve segments and which can be either open (stroke only) or closed (can be filled).
Arguments:
- shape: The shape to draw.
317 def draw_text(self, x: float, y: float, content: str): 318 content = str(content) 319 state_machine = self._get_current_state_machine() 320 321 y = y + 1 322 323 stroke_enabled = state_machine.get_stroke_enabled() 324 fill_enabled = state_machine.get_fill_enabled() 325 stroke_native = state_machine.get_stroke_native() 326 fill_native = state_machine.get_fill_native() 327 stroke_weight = state_machine.get_stroke_weight() 328 329 text_font = state_machine.get_text_font_native() 330 331 align_info = state_machine.get_text_align_native() 332 anchor_str = align_info.get_horizontal_align() + align_info.get_vertical_align() 333 334 pillow_util_image = sketchingpy.pillow_util.make_text_image( 335 x, 336 y, 337 content, 338 text_font, 339 stroke_enabled, 340 fill_enabled, 341 stroke_native, 342 fill_native, 343 stroke_weight, 344 anchor_str 345 ) 346 347 transformed = self._get_transformed( 348 pillow_util_image.get_image(), 349 pillow_util_image.get_x(), 350 pillow_util_image.get_y() 351 ) 352 self._draw_or_queue_transformed(transformed)
Draw text using the current font.
Draw text using the current font and alignment.
Arguments:
- x: The x coordinate at which to draw the text.
- y: The y coordinate at which to draw the text.
- text: The string to draw.
Callback for when the sketch ends execution.
Register a callback for when the sketch redraws. This function should expect a single parameter which is the sketch redrawing.
Arguments:
- callback: The function to invoke when the sketch stops execution.
Callback for when the sketch ends execution.
Register a callback for when the sketch terminates.
Arguments:
- callback: The function to invoke when the sketch stops execution.
Load an image file.
Load an image from the local file system or URL.
Arguments:
- src: The location from which the file should be read.
371 def draw_image(self, x: float, y: float, image: sketchingpy.abstracted.Image): 372 if not image.get_is_loaded(): 373 return 374 375 image_mode_native = self._get_current_state_machine().get_image_mode_native() 376 377 rect = self._build_rect_with_mode( 378 x, 379 y, 380 image.get_width(), 381 image.get_height(), 382 image_mode_native 383 ) 384 385 surface = image.get_native() 386 387 transformed = self._get_transformed(surface, rect.get_x(), rect.get_y()) 388 self._draw_or_queue_transformed(transformed)
Draw an image at a location.
Draw a previously loaded image at a specific coordinate using its current size.
Arguments:
- x: Horizontal coordinate at which to draw the image.
- y: Vertical coordinate at which to draw the image.
- image: The image to draw.
390 def save_image(self, path: str): 391 if self._in_macro: 392 width = self._get_target_marco().get_width() 393 height = self._get_target_marco().get_height() 394 target = self._make_buffer_surface(sketchingpy.pillow_struct.Rect(0, 0, width, height)) 395 396 for compiled in self._get_target_marco().get(): 397 compiled.draw(target) 398 399 target.get_image().save(path) 400 else: 401 self._target_writable.get_image().save(path)
Save an image file.
Save the sketch as an image file, either directly to the file system or as a download.
Arguments:
- path: The location at which the file should be written.
Save current transformation state.
Save current sketch transformation state to the matrix history. This works as a stack (like a stack of plates) where this puts a new plate on the top of the pile. This will leave the current transformation matrix in the sketch unchanged.
410 def pop_transform(self): 411 if len(self._transformer_stack) == 0: 412 raise RuntimeError('Transformation stack empty.') 413 414 self._transformer = self._transformer_stack.pop()
Restore a previously saved transformation state.
Restore the most recently transformation configuration saved in matrix history, removing that "transform matrix" from the history. This works as a stack (like a stack of plates) where the top of the pile is taken off and restored, removing it from that stack. This will overwrite the current transformation configuration in the sketch.
Get the milliseconds since the sketch was shown.
Returns:
The number of milliseconds since the sketch was shown or 0 if never shown.
Get a reference to the underlying native renderer object.
Returns:
Native render object.
Indicate how fast the sketch should redraw.
Indicate a target frames per second that the sketch will take a "step" or redraw. Note that this is a goal and, if the system fall behind, it will drop frames and cause the on_step callback to be executed fewer times than the target.
Arguments:
- rate: The number of frames to try to draw per second.
Indicate the title to assign the window in the operating system.
Indicate the human-readable string title to assign to the sketch window.
Arguments:
- title: The text of the title.
435 def show(self, ax=None): 436 if has_matplot_lib and has_numpy_lib: 437 if ax is None: 438 ax = matplotlib.pyplot.subplot(111) 439 ax.axis('off') 440 441 ax.imshow(numpy.asarray(self._target_writable.get_image())) 442 else: 443 raise RuntimeError('Install matplotlib and numpy or use save instead.')
Show the sketch.
Show the sketch to the user and, if applicable, start the draw loop specified by set_fps. For Sketch2DApp, will execute any waiting drawing instructions provided to the sketch prior to showing. This is conceptually the same as "starting" the sketch.
Arguments:
- ax: The container into which the sketch should be shown. Currently only supported for Sketch2DStatic. Optional and ignored on most renderers.
Show the sketch and quit immediatley afterwards.
Show the sketch to the user and quit immediately afterwards, a routine potentially useful for testing.
Change the location of the origin.
Change the transform matrix such that any drawing afterwards is moved by a set amount.
Arguments:
- x: The number of pixels to offset horizontally.
- y: The number of pixels to offset vertically.
455 def rotate(self, angle_mirror: float): 456 angle = -1 * angle_mirror 457 angle_rad = self._convert_to_radians(angle) 458 self._transformer.rotate(angle_rad)
Rotate around the current origin.
Change the transform matrix such that any drawing afterwards is rotated around the current origin clock-wise.
Arguments:
- angle: The angle by which to rotate.
Scale outwards from the current origin.
Change the transform matrix such that any drawing afterwards is scaled from the current origin.
Arguments:
- scale: The factor by which to scale where values over 1 scale up and less than 1 scale down. A value of 1 will have no effect.
Inherited Members
- sketchingpy.abstracted.Sketch
- set_fill
- clear_fill
- set_stroke
- clear_stroke
- set_arc_mode
- set_ellipse_mode
- set_rect_mode
- draw_pixel
- start_shape
- set_stroke_weight
- set_text_font
- set_text_align
- set_map_pan
- set_map_zoom
- set_map_placement
- convert_geo_to_pixel
- start_geo_polygon
- push_map
- pop_map
- parse_geojson
- set_image_mode
- set_angle_mode
- push_style
- pop_style
537class PillowSketchStateMachine(sketchingpy.state_struct.SketchStateMachine): 538 539 def __init__(self): 540 super().__init__() 541 self._fill_native = self._convert_color(super().get_fill()) 542 self._stroke_native = self._convert_color(super().get_stroke()) 543 self._font_cache = {} 544 self._text_align_native = self._transform_text_align(super().get_text_align_native()) 545 546 def set_fill(self, fill: str): 547 super().set_fill(fill) 548 self._fill_native = self._convert_color(super().get_fill()) 549 550 def get_fill_native(self): 551 return self._fill_native 552 553 def set_stroke(self, stroke: str): 554 super().set_stroke(stroke) 555 self._stroke_native = self._convert_color(super().get_stroke()) 556 557 def get_stroke_native(self): 558 return self._stroke_native 559 560 def get_text_font_native(self): 561 font = self.get_text_font() 562 key = '%s.%d' % (font.get_identifier(), font.get_size()) 563 564 if key not in self._font_cache: 565 new_font = PIL.ImageFont.truetype(font.get_identifier(), font.get_size()) 566 self._font_cache[key] = new_font 567 568 return self._font_cache[key] 569 570 def set_text_align(self, text_align: sketchingpy.state_struct.TextAlign): 571 super().set_text_align(text_align) 572 self._text_align_native = self._transform_text_align(super().get_text_align_native()) 573 574 def get_text_align_native(self): 575 return self._text_align_native 576 577 def _transform_text_align(self, 578 text_align: sketchingpy.state_struct.TextAlign) -> sketchingpy.state_struct.TextAlign: 579 580 HORIZONTAL_ALIGNS = { 581 sketchingpy.const.LEFT: 'l', 582 sketchingpy.const.CENTER: 'm', 583 sketchingpy.const.RIGHT: 'r' 584 } 585 586 VERTICAL_ALIGNS = { 587 sketchingpy.const.TOP: 't', 588 sketchingpy.const.CENTER: 'm', 589 sketchingpy.const.BASELINE: 's', 590 sketchingpy.const.BOTTOM: 'b' 591 } 592 593 return sketchingpy.state_struct.TextAlign( 594 HORIZONTAL_ALIGNS[text_align.get_horizontal_align()], 595 VERTICAL_ALIGNS[text_align.get_vertical_align()] 596 ) 597 598 def _convert_color(self, target: str) -> sketchingpy.pillow_struct.COLOR_TUPLE: 599 return PIL.ImageColor.getrgb(target)
Abstract base class for sketch state.
539 def __init__(self): 540 super().__init__() 541 self._fill_native = self._convert_color(super().get_fill()) 542 self._stroke_native = self._convert_color(super().get_stroke()) 543 self._font_cache = {} 544 self._text_align_native = self._transform_text_align(super().get_text_align_native())
Create a new state machine.
546 def set_fill(self, fill: str): 547 super().set_fill(fill) 548 self._fill_native = self._convert_color(super().get_fill())
Set the fill color.
Set the color to use for filling shapes and figures.
Arguments:
- fill: Name of the color or a hex code.
Get the renderer-native version of the fill color.
Returns:
Renderer-specific value. Undefined if get_fill_enabled() is False.
553 def set_stroke(self, stroke: str): 554 super().set_stroke(stroke) 555 self._stroke_native = self._convert_color(super().get_stroke())
Set the stroke color.
Set the color to use for drawing outlines for shapes and figures as well as lines.
Arguments:
- stroke: Name of the color or a hex code.
Get the renderer-native version of the stroke color.
Returns:
Renderer-specific value. Undefined if get_stroke_enabled() is False.
560 def get_text_font_native(self): 561 font = self.get_text_font() 562 key = '%s.%d' % (font.get_identifier(), font.get_size()) 563 564 if key not in self._font_cache: 565 new_font = PIL.ImageFont.truetype(font.get_identifier(), font.get_size()) 566 self._font_cache[key] = new_font 567 568 return self._font_cache[key]
Get the type and size for text drawing.
Returns:
Renderer-specific value.
570 def set_text_align(self, text_align: sketchingpy.state_struct.TextAlign): 571 super().set_text_align(text_align) 572 self._text_align_native = self._transform_text_align(super().get_text_align_native())
Indicate the alignment to use when drawing text.
Arguments:
- text_align: Structure describing horizontal and vertical text alignment.
Get the alignment to use when drawing text.
Returns:
Renderer-specific value.
Inherited Members
- sketchingpy.state_struct.SketchStateMachine
- get_fill
- get_fill_enabled
- clear_fill
- get_stroke
- get_stroke_enabled
- clear_stroke
- set_arc_mode
- get_arc_mode
- get_arc_mode_native
- set_ellipse_mode
- get_ellipse_mode
- get_ellipse_mode_native
- set_rect_mode
- get_rect_mode
- get_rect_mode_native
- set_stroke_weight
- get_stroke_weight
- get_stroke_weight_native
- set_text_font
- get_text_font
- get_text_align
- set_image_mode
- get_image_mode
- get_image_mode_native
- set_angle_mode
- get_angle_mode
- get_angle_mode_native
602class PillowImage(sketchingpy.abstracted.Image): 603 604 def __init__(self, src: str): 605 super().__init__(src) 606 self._native = PIL.Image.open(src) 607 608 def get_width(self) -> float: 609 return self._native.width 610 611 def get_height(self) -> float: 612 return self._native.height 613 614 def resize(self, width: float, height: float): 615 self._native = self._native.resize((int(width), int(height))) 616 617 def get_native(self): 618 return self._native 619 620 def get_is_loaded(self): 621 return True
Information about an image as an abstract base class.
Create a new image record.
Arguments:
- src: The location from which the image was loaded.
Get the width of this image in pixels.
Returns:
Horizontal width of this image.
Get the height of this image in pixels.
Returns:
Vertical height of this image.
614 def resize(self, width: float, height: float): 615 self._native = self._native.resize((int(width), int(height)))
Resize this image by scaling.
Arguments:
- width: The new desired width of this image in pixels.
- height: The new desired height of this image in pixels.
Access the underlying native version of this image.
Returns:
Renderer specific native version.
Determine if this image has finished loading.
Returns:
True if loaded and ready to draw. False otherwise.