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
has_matplot_lib = False
has_numpy_lib = True
DEFAULT_FPS = 20
MANUAL_OFFSET = False
TRANSFORMED_WRITABLE = <class 'sketchingpy.pillow_struct.TransformedWritable'>
class Sketch2DStatic(sketchingpy.abstracted.Sketch):
 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.

Sketch2DStatic( width: int, height: int, title: Optional[str] = None, loading_src: Optional[str] = None)
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.
def create_buffer( self, name: str, width: int, height: int, background: Optional[str] = None):
 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.
def enter_buffer(self, name: str):
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.
def exit_buffer(self):
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.

def draw_buffer(self, x: float, y: float, name: str):
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.
def get_keyboard(self) -> Optional[sketchingpy.control_struct.Keyboard]:
137    def get_keyboard(self) -> typing.Optional[sketchingpy.control_struct.Keyboard]:
138        return None

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.

def get_mouse(self) -> Optional[sketchingpy.control_struct.Mouse]:
140    def get_mouse(self) -> typing.Optional[sketchingpy.control_struct.Mouse]:
141        return None

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.

def get_data_layer(self) -> Optional[sketchingpy.data_struct.DataLayer]:
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.

def get_dialog_layer(self) -> Optional[sketchingpy.dialog_struct.DialogLayer]:
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.

def clear(self, color_hex: str):
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.
def draw_arc( self, x1: float, y1: float, x2: float, y2: float, a1: float, a2: float):
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.
def draw_ellipse(self, x1: float, y1: float, x2: float, y2: float):
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.
def draw_line(self, x1: float, y1: float, x2: float, y2: float):
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.
def draw_rect(self, x1: float, y1: float, x2: float, y2: float):
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.
def draw_shape(self, shape: sketchingpy.shape_struct.Shape):
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.
def draw_text(self, x: float, y: float, content: str):
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.
def on_step(self, callback: Callable[[ForwardRef('Sketch')], NoneType]):
358    def on_step(self, callback: sketchingpy.abstracted.StepCallback):
359        pass

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.
def on_quit(self, callback: Callable[[ForwardRef('Sketch')], NoneType]):
361    def on_quit(self, callback: sketchingpy.abstracted.QuitCallback):
362        pass

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.
def get_image(self, src: str) -> sketchingpy.abstracted.Image:
368    def get_image(self, src: str) -> sketchingpy.abstracted.Image:
369        return PillowImage(src)

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.
def draw_image(self, x: float, y: float, image: sketchingpy.abstracted.Image):
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.
def save_image(self, path: str):
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.
def push_transform(self):
407    def push_transform(self):
408        self._transformer_stack.append(copy.deepcopy(self._transformer))

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.

def pop_transform(self):
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.

def get_millis_shown(self):
420    def get_millis_shown(self):
421        return 0

Get the milliseconds since the sketch was shown.

Returns:

The number of milliseconds since the sketch was shown or 0 if never shown.

def get_native(self):
423    def get_native(self):
424        return self._target_writable

Get a reference to the underlying native renderer object.

Returns:

Native render object.

def set_fps(self, rate: int):
426    def set_fps(self, rate: int):
427        pass

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.
def set_title(self, title: str):
429    def set_title(self, title: str):
430        pass

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.
def quit(self):
432    def quit(self):
433        pass

Finish execution of the sketch.

Cause the sketch to stop execution.

def show(self, ax=None):
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.
def show_and_quit(self, ax=None):
445    def show_and_quit(self, ax=None):
446        pass

Show the sketch and quit immediatley afterwards.

Show the sketch to the user and quit immediately afterwards, a routine potentially useful for testing.

def translate(self, x: float, y: float):
452    def translate(self, x: float, y: float):
453        self._transformer.translate(x, y)

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.
def rotate(self, angle_mirror: float):
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.
def scale(self, scale: float):
460    def scale(self, scale: float):
461        self._transformer.scale(scale)

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.
class PillowSketchStateMachine(sketchingpy.state_struct.SketchStateMachine):
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.

PillowSketchStateMachine()
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.

def set_fill(self, fill: str):
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.
def get_fill_native(self):
550    def get_fill_native(self):
551        return self._fill_native

Get the renderer-native version of the fill color.

Returns:

Renderer-specific value. Undefined if get_fill_enabled() is False.

def set_stroke(self, stroke: str):
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.
def get_stroke_native(self):
557    def get_stroke_native(self):
558        return self._stroke_native

Get the renderer-native version of the stroke color.

Returns:

Renderer-specific value. Undefined if get_stroke_enabled() is False.

def get_text_font_native(self):
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.

def set_text_align(self, text_align: sketchingpy.state_struct.TextAlign):
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.
def get_text_align_native(self):
574    def get_text_align_native(self):
575        return self._text_align_native

Get the alignment to use when drawing text.

Returns:

Renderer-specific value.

class PillowImage(sketchingpy.abstracted.Image):
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.

PillowImage(src: str)
604    def __init__(self, src: str):
605        super().__init__(src)
606        self._native = PIL.Image.open(src)

Create a new image record.

Arguments:
  • src: The location from which the image was loaded.
def get_width(self) -> float:
608    def get_width(self) -> float:
609        return self._native.width

Get the width of this image in pixels.

Returns:

Horizontal width of this image.

def get_height(self) -> float:
611    def get_height(self) -> float:
612        return self._native.height

Get the height of this image in pixels.

Returns:

Vertical height of this image.

def resize(self, width: float, height: float):
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.
def get_native(self):
617    def get_native(self):
618        return self._native

Access the underlying native version of this image.

Returns:

Renderer specific native version.

def get_is_loaded(self):
620    def get_is_loaded(self):
621        return True

Determine if this image has finished loading.

Returns:

True if loaded and ready to draw. False otherwise.