Graphics Support

lbutils.graphics

Provides a simple graphics library for the supported screen devices (controllers) of the Pico H and Pico W. Most of this interface is provided through a base class called Canvas, which is expected to be instantiated as sub-class of one of the driver classes, e.g. lbutils.pmods.spi.OLEDrgb. The following description therefore describes the methods and attributes common to all derived drivers: but see the individual drivers for attributes and methods that may be specific to the device in question.

User and Library Cursors

The Canvas will maintain two internal drawing references

  1. A cursor, representing the current x and y drawing co-ordinates. This cursor should be assumed to be under the control of the library (Canvas). For instance, many of the drawing methods, this internal state will be modified to point to the next location which is commonly used in sequence. For example the write_text updates the cursor to point to position at the end of the text string. Or the draw_line method will change the cursor to the end of the line just drawn. This behaviour makes it easier to sequence multiple drawing methods: especially in the common case where a single origin is used to draw multiple lines or other primitives in succession to create more complex shapes.
  2. An origin, representing a reference point for a sequence of drawing commands or drawing primitives. This origin is assumed to be under the control of the user, and will usually not be modified by the internal drawing commands. Instead an application can set the origin, then use the drawing primitives with the addition of from_origin in the method name. Any internal change of state in the cursor will not be reflected in a subsequent change to the origin.
Library Organisation and Helper Classes

The main aim of the Canvas class (and the graphics library generally is to provide a basic set of capabilities which can be relied on by all users (and higher-level libraries). Those common facilities can be divided into the following categories, and are described in more detail in the following sections

  • Colour Support and Representation. Classes such as Colour which holds the internal colour representations used by the graphics library. Also provides methods to convert between common colour formats and representations, such as RGB565 and RGB588.
  • Common Drawing Primitives. The drawing primitives provided by the Canvas class of the library, such as circles, rectangles and lines. These primitives are guaranteed to be available in all graphics drivers: but depending on the driver may or may not be accelerated.
  • Fonts and Font Handling. Describes the internal font representation used in this library, and the details of the fonts available for use.
  • Helper Classes. Provides utility classes and functions which ease the abstraction of the main graphics Canvas library, e.g. Pixel. These are provided outside the main Canvas class as being the most suitable classes for re-use in other drawing and graphics routines.

Diagrammatically, the relationship between the Canvas class and the helper classes listed above can be shown as follows. Note that the fonts package is covered separately.

Graphics Package Diagram

Implementation

Much of the functionality of the Canvas class is provided by related 'helper' classes. Some of these helper classes such as Pen or Colour may be useful more widely in graphics and drawing routines. Other helper classes such as the BaseFont are likely to be useful only in the context of the Canvas class (and sub-classes).

The only methods required to be implemented in sub-classes of Canvas are read_pixel and write_pixel. All the drawing primitives, including font support, can be implemented by Canvas using only those two methods. However, in most cases the drawing speed is unacceptably slow, and so in most cases sub-classes will also choose to override methods such as draw_line where such facilities are available. The details of the accelerated methods, including any changes to the algorithms used by Canvas can be found in the documentation for the sub-class itself.

Tested Implementations

  • Raspberry Pi Pico W (MicroPython 3.4)

lbutils.graphics.colours

Implements a helper library and Colour class which holds the internal colour representations used by the graphics library. The Colour class aims to achieve three goals.

  1. To hold the internal (byte) representations of colours typically used by small OLED and LED screens.
  2. To convert between those internal representations, trading off space for colour accuracy for instance
  3. To provide a simple interface for the graphics library (and graphics routines) which can make use of the colour representations: and without having to replicate the detailed bit manipulation that colour storage and conversion involves.

Colour Reference

In addition to the Colour class, a list of 16 'VGA' colours defined in the HTML 4.01 specification is also provided. These provide common, named, colour representations suitable for most displays, for instance as

import lbutils.graphics as graphics

fg_colour = graphics.COLOUR_CYAN

A complete list of the 16 objects defined by the lbutils.colours module is shown below

Colour Colour Name Hex Representation Object Name
Black 0x000000 COLOUR_BLACK
Silver 0xC0C0C0 COLOUR_SILVER
Grey 0x808080 COLOUR_GREY
White 0xFFFFFF COLOUR_WHITE
Maroon 0x800000 COLOUR_MAROON
Red 0xFF0000 COLOUR_RED
Purple 0x800080 COLOUR_PURPLE
Fuchsia 0xFF00FF COLOUR_FUCHSIIA
Green 0x008000 COLOUR_GREEN
Lime 0x00FF00 COLOUR_LIME
Olive 0x808000 COLOUR_OLIVE
Yellow 0xFFFF00 COLOUR_YELLOW
Navy 0x000080 COLOUR_NAVY
Blue 0x0000FF COLOUR_BLUE
Teal 0x008080 COLOUR_TEAL
Aqua 0x00FFFF COLOUR_AQUA

Tested Implementations

  • Raspberry Pi Pico W (MicroPython 3.4)
  • CPython (3.10)

Colour

A (packed) representation of a colour value, as r (red), g (green) and b (blue) components. The principle purpose of this class is to both hold the internal representation of the colour; and to make the manipulation of those colour values in other graphics routines as straightforward as possible.

Attributes:

  • red (int, read-only) –

    The byte (0..255) of the red component of the colour

  • green (int, read-only) –

    The byte (0..255) of the green component of the colour

  • blue (int, read-only) –

    The byte (0..255) of the blue component of the colour

  • as_rgb565 (int, read-only) –

    Provides the colour value in the RGB565 format, using a single byte in the the standard platform representation.

  • as_rgb888 (int, read-only) –

    Provides the colour value in the RGB888 format, using a double word for the colour value in the standard platform representation.

  • word_order (DEVICE_WORD_ORDER, read-write) –

    Argument indicating if the underlying byte order used for the bit packing in specific hardware colour conversions. Defaults to DEVICE_WORD_ORDER.NORMAL, to use the standard platform (host) conventions.

Methods

from_565: Color Create a Colour object from the byte passed in as a parameter: assuming the byte is an RGB 565 packed byte.

Implementation

Where possible attribute values are cached, and so the first call of the attribute will be slightly slower than subsequent calls.

Immutable Class

To ensure the accuracy of the returned value, the Colour is also assumed to be immutable once the constructor has completed. If the private (non-public) attributes are modified outside the constructor the behaviour of the class is undefined.

Source code in lbutils/graphics/colours.py
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
class Colour:
    """A (packed) representation of a colour value, as `r` (red), `g` (green)
    and `b` (blue) components. The principle purpose of this class is to both
    hold the internal representation of the colour; and to make the manipulation
    of those colour values in other graphics routines as straightforward as
    possible.

    Attributes
    ----------
    red: int, read-only
        The byte (`0..255`) of the red component of the colour
    green: int, read-only
        The byte (`0..255`) of the green component of the colour
    blue: int, read-only
        The byte (`0..255`) of the blue component of the colour
    as_rgb565: int, read-only
        Provides the colour value in the RGB565 format, using a single
        byte in the the standard platform representation.
    as_rgb888: int, read-only
        Provides the colour value in the RGB888 format, using a
        double word for the colour value in the standard platform
        representation.
    word_order: DEVICE_WORD_ORDER, read-write
        Argument indicating if the underlying byte order used for
        the bit packing in specific hardware colour conversions.
        Defaults to `DEVICE_WORD_ORDER.NORMAL`, to use the standard
        platform (host) conventions.

    Methods
    -------

    from_565: Color
        Create a [`Colour`][lbutils.graphics.Colour] object from the byte
        passed in as a parameter: assuming the byte is an RGB 565 packed
        byte.

    Implementation
    --------------

    Where possible attribute values are cached, and so the first
    call of the attribute will be slightly slower than subsequent calls.

    !!! warning "Immutable Class"
        To ensure the accuracy of the returned value, the Colour is also
        assumed to be immutable once the constructor has completed. If
        the private (non-public) attributes are modified outside the
        constructor the behaviour of the class is undefined.
    """

    ##
    ## Internal Attributes
    ##

    _r: int
    _g: int
    _b: int

    _565: Optional[int]
    _888: Optional[int]

    _red: Optional[int]
    _green: Optional[int]
    _blue: Optional[int]

    ##
    ## Constructors
    ##

    def __init__(
        self,
        r: int,
        g: int,
        b: int,
        word_order: DEVICE_WORD_ORDER = DEVICE_WORD_ORDER.NORMAL,
    ) -> None:
        """Create a representation of a colour value, from the three integers
        `r` (red), `g` (green) and `b` (blue). The class will accept anything
        which can be coerced to an integer as arguments: the methods used to
        access the colour (and the `word_order`) will determine the byte order
        used as the final representation used when displaying the colour.

        Parameters
        ----------

        r: int
            The integer representing the red component of the colour.
        g: int
            The integer representing the green component of the colour.
        b: int
            The integer representing the blue component of the colour.
        word_order: DEVICE_WORD_ORDER, read-write
            Argument indicating if the underlying byte order used for
            the bit packing in specific hardware colour conversions.
            Defaults to `DEVICE_WORD_ORDER.NORMAL`, to use the standard
            platform (host) conventions.
        """

        # Set the colour to the RGB value specified
        self._r = int(r)
        self._g = int(g)
        self._b = int(b)

        # Record the convention for ordering
        # the bytes in the words
        self.word_order = word_order

        # Cached values
        self._565 = None
        self._888 = None
        self._red = None
        self._green = None
        self._blue = None

    ##
    ## Properties
    ##

    @property
    def red(self) -> int:
        """The red component of the colour value, packed to a single byte."""
        if self._red is None:
            self._red = self._r & 0xFF

        return self._red

    @property
    def green(self) -> int:
        """The green component of the colour value, packed to a single byte."""
        if self._green is None:
            self._green = self._g & 0xFF

        return self._green

    @property
    def blue(self) -> int:
        """The blue component of the colour value, packed to a single byte."""
        if self._blue is None:
            self._blue = self._b & 0xFF

        return self._blue

    @property
    def as_rgb565(self) -> Optional[int]:
        """Construct a packed double word from the internal colour
        representation, with 8 bits of red data, 8 bits of green, and 8 of blue.
        For most platforms this results in a byte order for the two colour words
        as follows.

        ![````
        F  E  D  C  B  A  9  8  7  6  5  4  3  2  1  0
        R4 R3 R2 R1 R0 G5 G4 G3 G2 G1 G0 B4 B3 B2 B1 B0
        ````](/media/colours_as_rgb565_fig1.svg)

        On some platforms the packed word representation has the high and low
        bytes swapped in each word, and so looks like

        ![
        ````
        F  E  D  C  B  A  9  8  7  6  5  4  3  2  1  0
        G2 G1 G0 B4 B3 B2 B1 B0 R4 R3 R2 R1 R0 G5 G4 G3
        ````](/media/colours_as_rgb565_fig2.svg)

        Platforms which require the above byte order in the word **must** set
        the `word_order` property to `DEVICE_WORD_ORDER.SWAP_BYTES`; either
        before calling this method or in the object constructor.

        Returns
        -------

        int:
            A packed byte value of the colour representation.
        """
        # Check for a cached value ...
        if self._565 is None:
            # Set-up the 565 bit representation
            bits565 = (self._r & 0xF8) << 8 | (self._g & 0xFC) << 3 | self._b >> 3

            # In most case that is all we need...
            if self.word_order == DEVICE_WORD_ORDER.NORMAL:
                self._565 = bits565
            # For some cases we need to swap the 'high' and 'low' bytes
            else:
                self._565 = (bits565 & 0xFF) << 8 | (bits565 >> 8)

        # Return the calculated value to the client
        return self._565

    @property
    def as_rgb888(self) -> Optional[int]:
        """Construct a packed double word from the internal colour
        representation, with 8 bits of red data, 8 bits of green, and 8 of blue.
        For most platforms this results in a byte order for the two colour words
        as follows.

        ![Standard Byte Order for RGB888 Structure](/media/colours_as_rgb888_fig1.svg)

        On some platforms the packed word representation has the high and low
        bytes swapped in each word, and so looks like

        ![ARM Byte Order for RGB888 Structure](/media/colours_as_rgb888_fig2.svg)

        Platforms which require the above byte order in the word **must** set
        the `word_order` property to `DEVICE_WORD_ORDER.SWAP_BYTES`; either
        before calling this method or in the object constructor.

        Returns
        -------

        int:
            A packed double word value of the colour representation.
        """
        # Check for a cached value ...
        if self._888 is None:
            # ... if there isn't one, calculate what the byte representation
            #     should look like

            # Set-up the 888 bit representation as a double word with the
            # high byte of the high word 0x00 (all zeroes)
            bits888 = self._r << 16 | self._g << 8 | self._b

            # For most cases that is all we need...
            if self.word_order == DEVICE_WORD_ORDER.NORMAL:
                self._888 = bits888
            # For some cases we need to swap the 'high' and 'low'
            # bytes in each word
            else:
                self._888 = ((bits888 & 0x00FF00FF) << 8) | (
                    (bits888 & 0x0000FF00) >> 8
                )

        # Return the calculated value to the client
        return self._888

    ##
    ## Methods
    ##

    @staticmethod
    def from_565(rgb: int) -> "Colour":
        """Create a [`Colour`][lbutils.graphics.Colour] object from the byte
        passed in as a parameter: assuming the byte is an RGB 565 packed
        byte."""
        red = rgb & 0xF800
        green = rgb & 0x07E0
        blue = rgb & 0x001F

        new_colour = Colour(red, green, blue)

        return new_colour

as_rgb565 property

as_rgb565: Optional[int]

Construct a packed double word from the internal colour representation, with 8 bits of red data, 8 bits of green, and 8 of blue. For most platforms this results in a byte order for the two colour words as follows.

F  E  D  C  B  A  9  8  7  6  5  4  3  2  1  0
R4 R3 R2 R1 R0 G5 G4 G3 G2 G1 G0 B4 B3 B2 B1 B0

On some platforms the packed word representation has the high and low bytes swapped in each word, and so looks like


F  E  D  C  B  A  9  8  7  6  5  4  3  2  1  0
G2 G1 G0 B4 B3 B2 B1 B0 R4 R3 R2 R1 R0 G5 G4 G3

Platforms which require the above byte order in the word must set the word_order property to DEVICE_WORD_ORDER.SWAP_BYTES; either before calling this method or in the object constructor.

Returns

int: A packed byte value of the colour representation.

as_rgb888 property

as_rgb888: Optional[int]

Construct a packed double word from the internal colour representation, with 8 bits of red data, 8 bits of green, and 8 of blue. For most platforms this results in a byte order for the two colour words as follows.

Standard Byte Order for RGB888 Structure

On some platforms the packed word representation has the high and low bytes swapped in each word, and so looks like

ARM Byte Order for RGB888 Structure

Platforms which require the above byte order in the word must set the word_order property to DEVICE_WORD_ORDER.SWAP_BYTES; either before calling this method or in the object constructor.

Returns:

  • int( Optional[int] ) –

    A packed double word value of the colour representation.

blue property

blue: int

The blue component of the colour value, packed to a single byte.

green property

green: int

The green component of the colour value, packed to a single byte.

red property

red: int

The red component of the colour value, packed to a single byte.

__init__

__init__(
    r: int,
    g: int,
    b: int,
    word_order: DEVICE_WORD_ORDER = DEVICE_WORD_ORDER.NORMAL,
) -> None

Create a representation of a colour value, from the three integers r (red), g (green) and b (blue). The class will accept anything which can be coerced to an integer as arguments: the methods used to access the colour (and the word_order) will determine the byte order used as the final representation used when displaying the colour.

Parameters:

  • r (int) –

    The integer representing the red component of the colour.

  • g (int) –

    The integer representing the green component of the colour.

  • b (int) –

    The integer representing the blue component of the colour.

  • word_order (DEVICE_WORD_ORDER) –

    Argument indicating if the underlying byte order used for the bit packing in specific hardware colour conversions. Defaults to DEVICE_WORD_ORDER.NORMAL, to use the standard platform (host) conventions.

Source code in lbutils/graphics/colours.py
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
def __init__(
    self,
    r: int,
    g: int,
    b: int,
    word_order: DEVICE_WORD_ORDER = DEVICE_WORD_ORDER.NORMAL,
) -> None:
    """Create a representation of a colour value, from the three integers
    `r` (red), `g` (green) and `b` (blue). The class will accept anything
    which can be coerced to an integer as arguments: the methods used to
    access the colour (and the `word_order`) will determine the byte order
    used as the final representation used when displaying the colour.

    Parameters
    ----------

    r: int
        The integer representing the red component of the colour.
    g: int
        The integer representing the green component of the colour.
    b: int
        The integer representing the blue component of the colour.
    word_order: DEVICE_WORD_ORDER, read-write
        Argument indicating if the underlying byte order used for
        the bit packing in specific hardware colour conversions.
        Defaults to `DEVICE_WORD_ORDER.NORMAL`, to use the standard
        platform (host) conventions.
    """

    # Set the colour to the RGB value specified
    self._r = int(r)
    self._g = int(g)
    self._b = int(b)

    # Record the convention for ordering
    # the bytes in the words
    self.word_order = word_order

    # Cached values
    self._565 = None
    self._888 = None
    self._red = None
    self._green = None
    self._blue = None

from_565 staticmethod

from_565(rgb: int) -> Colour

Create a Colour object from the byte passed in as a parameter: assuming the byte is an RGB 565 packed byte.

Source code in lbutils/graphics/colours.py
352
353
354
355
356
357
358
359
360
361
362
363
@staticmethod
def from_565(rgb: int) -> "Colour":
    """Create a [`Colour`][lbutils.graphics.Colour] object from the byte
    passed in as a parameter: assuming the byte is an RGB 565 packed
    byte."""
    red = rgb & 0xF800
    green = rgb & 0x07E0
    blue = rgb & 0x001F

    new_colour = Colour(red, green, blue)

    return new_colour

DEVICE_WORD_ORDER

Bases: IntEnum

Set the byte order to be used (mostly in graphics code) for the low-level representation of words send to, and received from, devices.

Source code in lbutils/graphics/colours.py
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
class DEVICE_WORD_ORDER(IntEnum):
    """Set the byte order to be used (mostly in graphics code) for the low-level
    representation of words send to, and received from, devices."""

    NORMAL = 0
    """Use the platform native representation of words, with no changes to the
    byte order.

    The endianness of the word being represented is also determined by the
    underlying platform
    """
    SWAP_BYTES = 1
    """Use the standard platform endian representation for the _bytes_: but swap
    the word order so the 'low byte' is first and the 'high byte' second."""

NORMAL class-attribute

NORMAL = 0

Use the platform native representation of words, with no changes to the byte order.

The endianness of the word being represented is also determined by the underlying platform

SWAP_BYTES class-attribute

SWAP_BYTES = 1

Use the standard platform endian representation for the bytes: but swap the word order so the 'low byte' is first and the 'high byte' second.

lbutils.graphics.helpers

Provides utility classes and functions which ease the abstraction of the main graphics Canvas library. These are typically used to abstract and encapsulate common concepts such as a Pixel: but which are small enough not a warrant a separate library.

The classes and methods provided by this library are described below, and organised as shown

Helper Class Diagrams

Tested Implementations

  • Raspberry Pi Pico W (MicroPython 3.4)
  • CPython (3.10)

BoundPixel

Bases: Pixel

Represents a Cartesian co-ordinate between limits. Used as a convenience class for instances such as cursors where a relationship between a X and a Y co-ordinate must be maintained. This is also useful when two or more co- ordinates need to be tracked, or to be switched between. For instance an 'origin' co-ordinate for a drawing, and a 'current' co-ordinate around the origin where lines are being drawn to and from.

Unlike the Pixel class, the BoundPixel will also ensure that the X and Y co-ordinates are maintained between minimum and maximum value for the width or height. This is useful for instances where a cursor, for instance, must only take values within the limits of a display. It can also be used where a clipping region is being defined to ensure that values cannot lie outside the clipped region.

Implementation Defined Origin

As for the Canvas class, the interpretation of the point '(0, 0)' is defined by the underlying graphics implementation. For instance the '(0, 0)' point may represent the top-left corner or the canvas, or the bottom- left hand corner. For details of how this point will be chosen (or changed), see the implementation of the specified sub-class of Canvas that is implemented by the chosen display driver.

Attributes:

  • x (int) –

    The X co-oridinate value.

  • y (int) –

    The Y co-ordinate value.

  • min_x (int) –

    The minimum value allowed for the x co-ordinate. Defaults to 0.

  • min_y (int) –

    The minimum value allowed for the y co-ordinate. Defaults to 0.`

  • max_x (int) –

    The maximum value allowed for the x co-ordinate.

  • max_y (int) –

    The maximum value allowed for the y co-ordinate.

Source code in lbutils/graphics/helpers.py
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
class BoundPixel(Pixel):
    """Represents a Cartesian co-ordinate between limits. Used as a convenience
    class for instances such as cursors where a relationship between a X and a Y
    co-ordinate must be maintained. This is also useful when two or more co-
    ordinates need to be tracked, or to be switched between. For instance an
    'origin' co-ordinate for a drawing, and a 'current' co-ordinate around the
    origin where lines are being drawn to and from.

    Unlike the [`Pixel`][lbutils.graphics.Pixel] class, the `BoundPixel` will
    also ensure that the X and Y co-ordinates are maintained between minimum and
    maximum value for the `width` or `height`. This is useful for instances where
    a cursor, for instance, must only take values within the limits of a display.
    It can also be used where a clipping region is being defined to ensure that
    values cannot lie outside the clipped region.

    !!! note "Implementation Defined Origin"
            As for the [`Canvas`][lbutils.graphics.Canvas] class, the
            interpretation of the point '(0, 0)' is defined by the underlying
            graphics implementation. For instance the '(0, 0)' point may
            represent the top-left corner or the canvas, or the bottom- left hand
            corner. For details of how this point will be chosen (or changed),
            see the implementation of the specified sub-class of `Canvas` that is
            implemented by the chosen display driver.

    Attributes
    ----------

    x: int
            The X co-oridinate value.
    y: int
            The Y co-ordinate value.
    min_x: int
            The minimum value allowed for the `x` co-ordinate. Defaults to
            `0`.
    min_y: int
            The minimum value allowed for the `y` co-ordinate. Defaults to
            `0`.`
    max_x: int
            The maximum value allowed for the `x` co-ordinate.
    max_y: int
            The maximum value allowed for the `y` co-ordinate.
    """

    ##
    ## Constructors
    ##

    def __init__(
        self,
        x: int,
        y: int,
        max_x: int,
        max_y: int,
        min_x: int = 0,
        min_y: int = 0,
    ) -> None:
        """Create a `Pixel` instance holding the specified `x` and `y` co-
        ordinates, together representing the Cartesian point '(`x`, `y`)'. This
        `x` and `y` value is guaranteed to be maintained between `min_x` and
        `max_x` for the `x` co- ordinate, and `min_y` and `max_y` for the `y`
        co-ordinate.

        Parameters
        ----------

        x: int
                The initial X co-ordinate value.
        y: int
                The initial Y co-ordinate value.
        max_x: int
                The maximum value allowed for the `x` co-ordinate.
        max_y: int
                The maximum value allowed for the `y` co-ordinate.
        min_x: int, optional
                The minimum value allowed for the `x` co-ordinate. Defaults to
                `0`.
        min_y: int, optional
                The minimum value allowed for the `y` co-ordinate. Defaults to
                `0`.`

        Implementation
        --------------

        As the `x` and `y` attributes of this class are compared on each write,
        this class is by definition slower and potentially more resource
        intensive that the underlying `Pixel` class. If the costs of the bounds-
        check are not required, using the 'raw' `Pixel` class may be preferable.

        !!! note
                The parameter order is specified to allow easier definition
                in the common case where the lower limits for `x` and `y` are
                `0`, and the positional parameter order is being used. If all
                four limits are being used, consider the use of named
                parameters to avoid ambiguity.
        """

        # Set-up the maximum and minimum parameters first
        self.min_x = int(min_x)
        self.max_x = int(max_x)

        self.min_y = int(min_y)
        self.max_y = int(max_y)

        # Now attempt to set the actual `x` and `y` inside those
        # parameters
        self.x = int(x)
        self.y = int(y)

    ##
    ## Properties
    ##

    @property
    def x(self) -> int:
        """The `x` co-ordinate of the `BoundPixel`, checking that it lies within
        the specified `min_x` and `max_x` limits.

        If the `x` co-ordinate does lie outside the specified region, set it to
        the `min_x` or `max_x` limit as appropriate.
        """
        if self.min_x <= self._x <= self.max_x:
            return self._x
        else:
            if self._x > self.max_x:
                self._x = self.max_x
            if self._x < self.min_x:
                self._x = self.min_x

            return self._x

    @x.setter
    def x(self, value: int) -> None:
        if self.min_x <= value <= self.max_x:
            self._x = value
        else:
            if value > self.max_x:
                self._x = self.max_x
            if value < self.min_x:
                self._x = self.min_x

    @property
    def y(self) -> int:
        """The `y` co-ordinate of the `BoundPixel`, checking that it lies within
        the specified `min_x` and `max_y` limits.

        If the `y` co-ordinate does lie outside the specified region, set it to
        the `min_y` or `may_y` limit as appropriate.
        """
        if self.min_y <= self._y <= self.max_y:
            return self._y
        else:
            if self._y > self.max_y:
                self._y = self.max_y
            if self._y < self.min_y:
                self._y = self.min_y

            return self._y

    @y.setter
    def y(self, value: int) -> None:
        if self.min_y <= value <= self.max_y:
            self._y = value
        else:
            if value > self.max_y:
                self._y = self.max_y
            if value < self.min_y:
                self._y = self.min_y

x property writable

x: int

The x co-ordinate of the BoundPixel, checking that it lies within the specified min_x and max_x limits.

If the x co-ordinate does lie outside the specified region, set it to the min_x or max_x limit as appropriate.

y property writable

y: int

The y co-ordinate of the BoundPixel, checking that it lies within the specified min_x and max_y limits.

If the y co-ordinate does lie outside the specified region, set it to the min_y or may_y limit as appropriate.

__init__

__init__(
    x: int,
    y: int,
    max_x: int,
    max_y: int,
    min_x: int = 0,
    min_y: int = 0,
) -> None

Create a Pixel instance holding the specified x and y co- ordinates, together representing the Cartesian point '(x, y)'. This x and y value is guaranteed to be maintained between min_x and max_x for the x co- ordinate, and min_y and max_y for the y co-ordinate.

Parameters:

  • x (int) –

    The initial X co-ordinate value.

  • y (int) –

    The initial Y co-ordinate value.

  • max_x (int) –

    The maximum value allowed for the x co-ordinate.

  • max_y (int) –

    The maximum value allowed for the y co-ordinate.

  • min_x (int) –

    The minimum value allowed for the x co-ordinate. Defaults to 0.

  • min_y (int) –

    The minimum value allowed for the y co-ordinate. Defaults to 0.`

Implementation

As the x and y attributes of this class are compared on each write, this class is by definition slower and potentially more resource intensive that the underlying Pixel class. If the costs of the bounds- check are not required, using the 'raw' Pixel class may be preferable.

Note

The parameter order is specified to allow easier definition in the common case where the lower limits for x and y are 0, and the positional parameter order is being used. If all four limits are being used, consider the use of named parameters to avoid ambiguity.

Source code in lbutils/graphics/helpers.py
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
def __init__(
    self,
    x: int,
    y: int,
    max_x: int,
    max_y: int,
    min_x: int = 0,
    min_y: int = 0,
) -> None:
    """Create a `Pixel` instance holding the specified `x` and `y` co-
    ordinates, together representing the Cartesian point '(`x`, `y`)'. This
    `x` and `y` value is guaranteed to be maintained between `min_x` and
    `max_x` for the `x` co- ordinate, and `min_y` and `max_y` for the `y`
    co-ordinate.

    Parameters
    ----------

    x: int
            The initial X co-ordinate value.
    y: int
            The initial Y co-ordinate value.
    max_x: int
            The maximum value allowed for the `x` co-ordinate.
    max_y: int
            The maximum value allowed for the `y` co-ordinate.
    min_x: int, optional
            The minimum value allowed for the `x` co-ordinate. Defaults to
            `0`.
    min_y: int, optional
            The minimum value allowed for the `y` co-ordinate. Defaults to
            `0`.`

    Implementation
    --------------

    As the `x` and `y` attributes of this class are compared on each write,
    this class is by definition slower and potentially more resource
    intensive that the underlying `Pixel` class. If the costs of the bounds-
    check are not required, using the 'raw' `Pixel` class may be preferable.

    !!! note
            The parameter order is specified to allow easier definition
            in the common case where the lower limits for `x` and `y` are
            `0`, and the positional parameter order is being used. If all
            four limits are being used, consider the use of named
            parameters to avoid ambiguity.
    """

    # Set-up the maximum and minimum parameters first
    self.min_x = int(min_x)
    self.max_x = int(max_x)

    self.min_y = int(min_y)
    self.max_y = int(max_y)

    # Now attempt to set the actual `x` and `y` inside those
    # parameters
    self.x = int(x)
    self.y = int(y)

Pen

Implements a convenience class for the graphics library, which represents a 'pen' with a specified foreground and background colour, and thickness. The primary purpose of this class is to make it easy to swap between common colour and line values; for instance using two pens to allow a swap between 'highlight' and 'normal' text colours. This can be accomplished by defining the foreground and background colour of the Canvas as needed: this class simply makes that switch easier.

Example

Two new pens can be defined for 'normal' and 'alert' text as

normal_text = Pen(COLOUR_WHITE)
alert_text = Pen(COLOUR_RED)

This defines a normal_text pen with a white foreground and default background and line thickness (black and 1 pixel byt default). A second pen for alert_text has a red foreground, and similarly a black background with 1 pixel thickness. Text can then be written on the canvas using these two pens on a Canvas as

canvas = Canvas(width = 96, height = 48)

canvas.write_text(start= (0, 10), "This is normal text", pen = normal_text)
canvas.write_text(start= (0, 20), "and this is alert", pen = alert_text)
canvas.write_text(start= (0, 30), "Now everything is back to normal", pen = normal_text)

Attributes:

  • bg_colour (Colour, optional) –

    The background colour of the pen. Defaults to black.

  • fg_colour (Colour, optional) –

    The foreground colour of the pen. Defaults to white.

  • thickness (int, optional) –

    The line thickness of the pen. Defaults to 1 pixel.

Source code in lbutils/graphics/helpers.py
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
class Pen:
    """Implements a convenience class for the graphics library, which represents
    a 'pen' with a specified foreground and background colour, and thickness.
    The primary purpose of this class is to make it easy to swap between common
    colour and line values; for instance using two pens to allow a swap between
    'highlight' and 'normal' text colours. This can be accomplished by defining
    the foreground and background colour of the
    [`Canvas`][lbutils.graphics.Canvas] as needed: this class simply makes that
    switch easier.

    Example
    -------

    Two new pens can be defined for 'normal' and 'alert' text as

    ````python
    normal_text = Pen(COLOUR_WHITE)
    alert_text = Pen(COLOUR_RED)
    ````

    This defines a `normal_text` pen with a white foreground and default
    background and line thickness (black and 1 pixel byt default). A second pen
    for `alert_text` has a red foreground, and similarly a black background with
    1 pixel thickness. Text can then be written on the canvas using these two
    pens on a `Canvas` as

    ````python
    canvas = Canvas(width = 96, height = 48)

    canvas.write_text(start= (0, 10), "This is normal text", pen = normal_text)
    canvas.write_text(start= (0, 20), "and this is alert", pen = alert_text)
    canvas.write_text(start= (0, 30), "Now everything is back to normal", pen = normal_text)
    ````

    Attributes
    ----------

    bg_colour: Colour, optional
            The background colour of the pen. Defaults to black.
    fg_colour: Colour, optional
            The foreground colour of the pen. Defaults to white.
    thickness: int, optional
            The line thickness of the pen. Defaults to 1 pixel.
    """

    def __init__(
        self,
        fg_colour: Colour = COLOUR_WHITE,
        bg_colour: Colour = COLOUR_BLACK,
        thickness: int = 1,
    ) -> None:
        """Create a `Pen` instance, using the specified foreground and
        background colour, and line thickness."""

        self.bg_colour = bg_colour
        self.fg_colour = fg_colour

        self.thickness = int(thickness)

__init__

__init__(
    fg_colour: Colour = COLOUR_WHITE,
    bg_colour: Colour = COLOUR_BLACK,
    thickness: int = 1,
) -> None

Create a Pen instance, using the specified foreground and background colour, and line thickness.

Source code in lbutils/graphics/helpers.py
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
def __init__(
    self,
    fg_colour: Colour = COLOUR_WHITE,
    bg_colour: Colour = COLOUR_BLACK,
    thickness: int = 1,
) -> None:
    """Create a `Pen` instance, using the specified foreground and
    background colour, and line thickness."""

    self.bg_colour = bg_colour
    self.fg_colour = fg_colour

    self.thickness = int(thickness)

Pixel

Represents a Cartesian co-ordinate. Used as a convenience class for instances such as cursors where a relationship between a X and a Y co- ordinate must be maintained. This is also useful when two or more co- ordinates need to be tracked, or to be switched between. For instance an 'origin' co-ordinate for a drawing, and a 'current' co-ordinate around the origin where lines are being drawn to and from.

Implementation Defined Origin

As for the Canvas class, the interpretation of the point '(0, 0)' is defined by the underlying graphics implementation. For instance the '(0, 0)' point may represent the top-left corner or the canvas, or the bottom- left hand corner. For details of how this point will be chosen (or changed), see the implementation of the specified sub-class of Canvas that is implemented by the chosen display driver.

Attributes:

  • x (int) –

    The X co-ordinate value.

  • y (int) –

    The Y co-ordinate value.

  • x_y (tuple[int, int]) –

    A tuple representing the co-ordinate (x ,y).

Methods
  • move_to(). Move the internal co-ordinate to the value (x, y). An alias for the x_y property.
  • offset(). Returns a tuple representing the (x, y) co-ordinate of the current Pixel with the specified Cartesian off-set applied.
  • offset_polar(). Returns a tuple representing the (x, y) co-ordinate of the current Pixel with the specified Polar off-set applied.
Source code in lbutils/graphics/helpers.py
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
class Pixel:
    """Represents a Cartesian co-ordinate. Used as a convenience class for
    instances such as cursors where a relationship between a X and a Y co-
    ordinate must be maintained. This is also useful when two or more co-
    ordinates need to be tracked, or to be switched between. For instance an
    'origin' co-ordinate for a drawing, and a 'current' co-ordinate around the
    origin where lines are being drawn to and from.

    !!! note "Implementation Defined Origin"
            As for the [`Canvas`][lbutils.graphics.Canvas] class, the
            interpretation of the point '(0, 0)' is defined by the underlying
            graphics implementation. For instance the '(0, 0)' point may
            represent the top-left corner or the canvas, or the bottom- left hand
            corner. For details of how this point will be chosen (or changed),
            see the implementation of the specified sub-class of `Canvas` that is
            implemented by the chosen display driver.

    Attributes
    ----------

    x: int
            The X co-ordinate value.
    y: int
            The Y co-ordinate value.
    x_y: tuple[int, int]
            A tuple representing the co-ordinate (x ,y).

    Methods
    -------

    * `move_to()`. Move the internal co-ordinate to the value (x, y). An alias
    for the [`x_y`][lbutils.graphics.Pixel.x_y] property.
    * `offset()`. Returns a `tuple` representing the (x, y) co-ordinate of the
    current `Pixel` with the specified Cartesian off-set applied.
    * `offset_polar()`. Returns a `tuple` representing the (x, y) co-ordinate of
    the current `Pixel` with the specified Polar off-set applied.
    """

    ##
    ## Internal Attributes
    ##

    _x: int
    _y: int

    ##
    ## Constructors
    ##

    def __init__(self, x: int, y: int) -> None:
        """Create a `Pixel` instance holding the specified `x` and `y` co-
        ordinates, together representing the Cartesian point '(`x`, `y`)'.

        Parameters
        ----------

        x: int
                The initial X co-ordinate value.
        y: int
                The initial Y co-ordinate value.
        """
        self.x = int(x)
        self.y = int(y)

    ##
    ## Properties
    ##

    @property
    def x_y(self) -> tuple[int, int]:
        """Sets, or returns, the internal `x` and `y` co-ordinates as a tuple.

        When _reading_ from this property, a tuple is returned with the first
        value of the tuple representing the `x` co-ordinate and the second
        value of the tuple representing the `y` co-ordinate.

        When _writing_ to this property the first value of the tuple represents
        the `x` co-ordinate, and the second value of the tuple represents the `y`
        co-ordinate. All other values in the tuple are ignored.

        Raises
        ------

        ValueError:
            If the `x` or `y` co-ordinate in the `xy` tuple cannot be converted
            to an integer.
        """
        return (self._x, self._y)

    @x_y.setter
    def x_y(self, xy: tuple[int, int]) -> None:
        self.x = int(xy[0])
        self.y = int(xy[1])

    ##
    ## Properties
    ##

    @property
    def x(self) -> int:
        """The `x` co-ordinate of the `Pixel`."""
        return self._x

    @x.setter
    def x(self, value: int) -> None:
        self._x = int(value)

    @property
    def y(self) -> int:
        """The `y` co-ordinate of the `Pixel`."""
        return self._y

    @y.setter
    def y(self, value: int) -> None:
        self._y = int(value)

    ##
    ## Methods
    ##

    def move_to(self, xy: tuple[int, int]) -> None:
        """Set the internal `x` and `y` co-ordinates as a tuple. An alias for
        the `x_y` property.

        Parameters
        ----------

        xy: tuple
            The first value of the `xy` tuple represents the `x` co-ordinate, and
            the second value of the `xy` tuple represents the `y` co-ordinate.
            All other values in the `xy` tuple are ignored.

        Raises
        ------

        ValueError:
            If the `x` or `y` co-ordinate in the `xy` tuple cannot be converted
            to an integer.
        """
        self.x_y = xy

    def offset(self, x: int = 0, y: int = 0) -> tuple[int, int]:
        """Return a `tuple` representing the (x, y) co-ordinate of the current
        `Pixel` with the specified Cartesian off-set applied.

        Example
        -------

        Given a `Pixel` object called `origin` with representing the co-ordinates
        '(0, 10)'

        ````python
        origin = Pixel(0, 10)
        ````

        then calling

        ````python
        new_origin = origin.offset(10, 10)
        ````

        or better

        ````python
        new_origin = origin.offset(x = 10, y = 10)
        ````

        will return the tuple `[10, 20]` as `new_origin`.

        Parameters
        ----------

        x: int, optional
            The offset to apply to the x co-ordinate value of the `Pixel`.
        y: int, optional
            The offset to apply to the x co-ordinate value of the `Pixel`.

        Returns
        -------

        tuple:
            The (x, y) co-ordinate as a two value `tuple`  with the
            first value of the `tuple` representing the `x` co-ordinate and the
            second value of the `tuple` representing the `y` co-ordinate.
        """
        return (self.x + x, self.y + y)

    def offset_polar(self, r: int = 0, theta: int = 0) -> tuple[int, int]:
        """Return a `tuple` representing the (x, y) co-ordinate of the current
        `Pixel` with the specified Polar off-set applied as the radius `r` and
        angle `theta`.

        !!! Note "Floating Point Calculations"
            Although the _return_ values of the `offset_polar` function will be
            integers, floating point routines are used internally to calculate
            the sine and cosine of the angle `theta`. This may result in this
            routine being slower than expected on some platforms.

        Example
        -------

        Given a `Pixel` object called `origin` with representing the co-ordinates
        '(0, 10)'

        ````python
        origin = Pixel(0, 10)
        ````

        then calling

        ````python
        new_origin = origin.offset_polar(13, 22)
        ````

        or better

        ````python
        new_origin = origin.offset(r = 13, theta = 22)
        ````

        will return the tuple `[12, 5]` as `new_origin`.

        Parameters
        ----------

        r: int, optional
            The offset to apply to the x co-ordinate value of the `Pixel`,
            specified as the _radius_ of the Polar co-ordinate.
        theta: int, optional
            The offset to apply to the x co-ordinate value of the `Pixel`,
            specified as the _angle_ of the Polar co-ordinate.

        Returns
        -------

        tuple:
            The (x, y) co-ordinate as a two value `tuple`  with the
            first value of the `tuple` representing the `x` co-ordinate and the
            second value of the `tuple` representing the `y` co-ordinate.
        """
        return (int(self.x + (r * cos(theta))), int(self.y + (r * sin(theta))))

x property writable

x: int

The x co-ordinate of the Pixel.

x_y property writable

x_y: tuple[int, int]

Sets, or returns, the internal x and y co-ordinates as a tuple.

When reading from this property, a tuple is returned with the first value of the tuple representing the x co-ordinate and the second value of the tuple representing the y co-ordinate.

When writing to this property the first value of the tuple represents the x co-ordinate, and the second value of the tuple represents the y co-ordinate. All other values in the tuple are ignored.

Raises:

  • ValueError:

    If the x or y co-ordinate in the xy tuple cannot be converted to an integer.

y property writable

y: int

The y co-ordinate of the Pixel.

__init__

__init__(x: int, y: int) -> None

Create a Pixel instance holding the specified x and y co- ordinates, together representing the Cartesian point '(x, y)'.

Parameters:

  • x (int) –

    The initial X co-ordinate value.

  • y (int) –

    The initial Y co-ordinate value.

Source code in lbutils/graphics/helpers.py
156
157
158
159
160
161
162
163
164
165
166
167
168
169
def __init__(self, x: int, y: int) -> None:
    """Create a `Pixel` instance holding the specified `x` and `y` co-
    ordinates, together representing the Cartesian point '(`x`, `y`)'.

    Parameters
    ----------

    x: int
            The initial X co-ordinate value.
    y: int
            The initial Y co-ordinate value.
    """
    self.x = int(x)
    self.y = int(y)

move_to

move_to(xy: tuple[int, int]) -> None

Set the internal x and y co-ordinates as a tuple. An alias for the x_y property.

Parameters:

  • xy (tuple[int, int]) –

    The first value of the xy tuple represents the x co-ordinate, and the second value of the xy tuple represents the y co-ordinate. All other values in the xy tuple are ignored.

Raises:

  • ValueError:

    If the x or y co-ordinate in the xy tuple cannot be converted to an integer.

Source code in lbutils/graphics/helpers.py
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
def move_to(self, xy: tuple[int, int]) -> None:
    """Set the internal `x` and `y` co-ordinates as a tuple. An alias for
    the `x_y` property.

    Parameters
    ----------

    xy: tuple
        The first value of the `xy` tuple represents the `x` co-ordinate, and
        the second value of the `xy` tuple represents the `y` co-ordinate.
        All other values in the `xy` tuple are ignored.

    Raises
    ------

    ValueError:
        If the `x` or `y` co-ordinate in the `xy` tuple cannot be converted
        to an integer.
    """
    self.x_y = xy

offset

offset(x: int = 0, y: int = 0) -> tuple[int, int]

Return a tuple representing the (x, y) co-ordinate of the current Pixel with the specified Cartesian off-set applied.

Example

Given a Pixel object called origin with representing the co-ordinates '(0, 10)'

origin = Pixel(0, 10)

then calling

new_origin = origin.offset(10, 10)

or better

new_origin = origin.offset(x = 10, y = 10)

will return the tuple [10, 20] as new_origin.

Parameters:

  • x (int) –

    The offset to apply to the x co-ordinate value of the Pixel.

  • y (int) –

    The offset to apply to the x co-ordinate value of the Pixel.

Returns:

  • tuple( tuple[int, int] ) –

    The (x, y) co-ordinate as a two value tuple with the first value of the tuple representing the x co-ordinate and the second value of the tuple representing the y co-ordinate.

Source code in lbutils/graphics/helpers.py
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
def offset(self, x: int = 0, y: int = 0) -> tuple[int, int]:
    """Return a `tuple` representing the (x, y) co-ordinate of the current
    `Pixel` with the specified Cartesian off-set applied.

    Example
    -------

    Given a `Pixel` object called `origin` with representing the co-ordinates
    '(0, 10)'

    ````python
    origin = Pixel(0, 10)
    ````

    then calling

    ````python
    new_origin = origin.offset(10, 10)
    ````

    or better

    ````python
    new_origin = origin.offset(x = 10, y = 10)
    ````

    will return the tuple `[10, 20]` as `new_origin`.

    Parameters
    ----------

    x: int, optional
        The offset to apply to the x co-ordinate value of the `Pixel`.
    y: int, optional
        The offset to apply to the x co-ordinate value of the `Pixel`.

    Returns
    -------

    tuple:
        The (x, y) co-ordinate as a two value `tuple`  with the
        first value of the `tuple` representing the `x` co-ordinate and the
        second value of the `tuple` representing the `y` co-ordinate.
    """
    return (self.x + x, self.y + y)

offset_polar

offset_polar(r: int = 0, theta: int = 0) -> tuple[int, int]

Return a tuple representing the (x, y) co-ordinate of the current Pixel with the specified Polar off-set applied as the radius r and angle theta.

Floating Point Calculations

Although the return values of the offset_polar function will be integers, floating point routines are used internally to calculate the sine and cosine of the angle theta. This may result in this routine being slower than expected on some platforms.

Example

Given a Pixel object called origin with representing the co-ordinates '(0, 10)'

origin = Pixel(0, 10)

then calling

new_origin = origin.offset_polar(13, 22)

or better

new_origin = origin.offset(r = 13, theta = 22)

will return the tuple [12, 5] as new_origin.

Parameters:

  • r (int) –

    The offset to apply to the x co-ordinate value of the Pixel, specified as the radius of the Polar co-ordinate.

  • theta (int) –

    The offset to apply to the x co-ordinate value of the Pixel, specified as the angle of the Polar co-ordinate.

Returns:

  • tuple( tuple[int, int] ) –

    The (x, y) co-ordinate as a two value tuple with the first value of the tuple representing the x co-ordinate and the second value of the tuple representing the y co-ordinate.

Source code in lbutils/graphics/helpers.py
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
def offset_polar(self, r: int = 0, theta: int = 0) -> tuple[int, int]:
    """Return a `tuple` representing the (x, y) co-ordinate of the current
    `Pixel` with the specified Polar off-set applied as the radius `r` and
    angle `theta`.

    !!! Note "Floating Point Calculations"
        Although the _return_ values of the `offset_polar` function will be
        integers, floating point routines are used internally to calculate
        the sine and cosine of the angle `theta`. This may result in this
        routine being slower than expected on some platforms.

    Example
    -------

    Given a `Pixel` object called `origin` with representing the co-ordinates
    '(0, 10)'

    ````python
    origin = Pixel(0, 10)
    ````

    then calling

    ````python
    new_origin = origin.offset_polar(13, 22)
    ````

    or better

    ````python
    new_origin = origin.offset(r = 13, theta = 22)
    ````

    will return the tuple `[12, 5]` as `new_origin`.

    Parameters
    ----------

    r: int, optional
        The offset to apply to the x co-ordinate value of the `Pixel`,
        specified as the _radius_ of the Polar co-ordinate.
    theta: int, optional
        The offset to apply to the x co-ordinate value of the `Pixel`,
        specified as the _angle_ of the Polar co-ordinate.

    Returns
    -------

    tuple:
        The (x, y) co-ordinate as a two value `tuple`  with the
        first value of the `tuple` representing the `x` co-ordinate and the
        second value of the `tuple` representing the `y` co-ordinate.
    """
    return (int(self.x + (r * cos(theta))), int(self.y + (r * sin(theta))))

lbutils.graphics.Canvas

Bases: ABC

A Base Class which implements a drawing surface, and which provides utility methods for those drawing surfaces. The aim is to make is easier to use the specific display drivers, such as OLEDrgb, and to provide basic drawing support for higher-level libraries.

This drawing support is provided through the following categories of tools

  • Drawing Primitives: Provides basic support for drawing lines, rectangles, circles and triangles. This serves as a basic collection of primitives that can be relied upon by higher-level libraries.
  • Font Support: The Canvas maintains a record of the current font to use when writing text through the font attribute. This can be changed by users of the library, and defaults to Org01.
  • Colour Support: Colours can be selected in different ways, and the Canvas maintains a foreground (fg_colour) and background (bg_colour) attribute: along with a common method to override these default colours quickly for individual drawing commands. Colours are selected by order of precedence, which is defined as

    1. The Colours directly specified in the method call of the drawing primitive.
    2. The colours specified by the Pen in the method call of the drawing primitive.
    3. The colours specified by the Pen of the Canvas object.
    4. The colours specified by as the default (forground or background) colour of the Canvas object.
    5. As a default of white (COLOUR_WHITE) for the foreground, and black (COLOUR_BLACK) if all other selection methods fail.

Attributes:

  • bg_colour (graphics.Colour) –

    The background Colour to use when drawing.

  • cursor (graphics.BoundPixel) –

    The x and y locations of the current write (or read) operation.

  • origin (graphics.BoundPixel) –

    The user reference point for the next sequence of drawing primitives. This origin will not be altered by changes to the x and y locations of any drawing command.

  • font (fonts.BaseFont) –

    The sub-class of BaseFont to use when drawing characters.

  • fg_colour (graphics.Colour) –

    The foreground Colour to use when drawing.

  • pen (graphics.Pen) –

    The Pen to use when drawing on the canvas.

  • height (int) –

    A read-only value for the height of the canvas in pixels.

  • width (int) –

    A read-only value for the width of the canvas in pixels.

  • x (int) –

    The X co-ordinate value of the cursor

  • y (int) –

    The Y co-ordinate value of the cursor

  • x_y (int) –

    A tuple representing the co-ordinate (x ,y) of the cursor

Methods

Cursor and Origin Movements

  • move_to(). Move the internal cursor to the co-ordinate values (x, y) for the next sequence of drawing commands.

  • move_origin_to(). Sets the user drawing origin of the Canvas to the specified co-ordinates for the next sequence of drawing commands.

Colour Management

  • select_bg_colour(). Return the colour to be used for drawing in the background, taking into account the (optional) overrides specified in bg_colour and pen. The selected colour will obey the standard colour selection precedence of the Canvas class, and is guaranteed to return a valid Colour object.

  • select_fg_colour(). Return the colour to be used for drawing in the foreground, taking into account the (optional) overrides specified in color and pen. The selected colour will obey the standard colour selection precedence of the Canvas class, and is guaranteed to return a valid Colour object.

Shape and Line Drawing Primitives

  • draw_line(). Draw a line from a specified point (by default the cursor) to a co-ordinate.

  • draw_rectangle(). Draw a rectangle at the co-ordinate (x, y) of height and width, using the specified colours for the frame of the rectangle and the interior fill colour (if any).

  • draw_to(). Draw a line from a specified point (by default the cursor) to a co-ordinate. Alias for draw_line().

  • fill_screen(). Fill the entire Canvas with the background colour.

Font and Text Handling

  • write_char(). Write a character (using the current font) starting at the specified co-ordinates (by default the current cursor co-ordinates.), in the specified colour.

  • write_text(). Write the a string (using the current font) starting at the specified co-ordinates (by default the current cursor co-ordinates.), in the specified colour.

Pixel Manipulation

  • read_pixel(). Return the Colour of the specified pixel.

  • write_pixel(). Set the pixel at the specified position to the foreground colour value.

Implementation

Many of the drawing methods implemented here are provided in the most generic manner possible: i.e. they are not fully optimised for speed. In most cases the sub-classes can (and should) use the accelerated drawing primitives available on specific hardware to improve the routines provided here.

Methods that must be implemented by sub-classes of Canvas are

Methods that could be implemented by sub-classes of Canvas are

Source code in lbutils/graphics/canvas.py
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
class Canvas(ABC):
    """A Base Class which implements a drawing surface, and which provides
    utility methods for those drawing surfaces. The aim is to make is easier to
    use the specific display drivers, such as
    [`OLEDrgb`][lbutils.pmods.spi.oledrgb.OLEDrgb], and to provide basic drawing
    support for higher-level libraries.

    This drawing support is provided through the following categories of tools

    * **Drawing Primitives**: Provides basic support for drawing lines,
    rectangles, circles and triangles. This serves as a basic collection of
    primitives that can be relied upon by higher-level libraries.
    * **Font Support**: The `Canvas` maintains a record of the current font to
    use when writing text through the `font` attribute. This can be changed by
    users of the library, and defaults to [`Org01`]
    [lbutils.graphics.fonts.Org01].
    * **Colour Support**: Colours can be selected in different ways, and the
    `Canvas` maintains a foreground (`fg_colour`) and background (`bg_colour`)
    attribute: along with a common method to override these default colours
    quickly for individual drawing commands. Colours are selected by order of
    precedence, which is defined as

        1. The `Colour`s directly specified in the method call of the drawing
        primitive.
        2. The colours specified by the `Pen` in the method call of the drawing
        primitive.
        3. The colours specified by the `Pen` of the `Canvas` object.
        4. The colours specified by as the default (forground or background)
        colour of the `Canvas` object.
        5. As a default of white (`COLOUR_WHITE`) for the foreground, and black
        (`COLOUR_BLACK`) if all other selection methods fail.

    Attributes
    ----------

    bg_colour: graphics.Colour
         The background [`Colour`][lbutils.graphics.colours.Colour] to use when
         drawing.
    cursor: graphics.BoundPixel
         The [`x`][lbutils.graphics.BoundPixel] and [`y`]
         [lbutils.graphics.BoundPixel] locations  of the current write
         (or read) operation.
    origin: graphics.BoundPixel
         The _user_ reference point for the next sequence of drawing primitives.
         This `origin` will not be altered by changes to the [`x`]
         [lbutils.graphics.BoundPixel] and [`y`]
         [lbutils.graphics.BoundPixel] locations of any drawing command.
    font: fonts.BaseFont
         The sub-class of [`BaseFont`][lbutils.graphics.fonts.base_font.BaseFont]
         to use when drawing characters.
    fg_colour: graphics.Colour
         The foreground [`Colour`][lbutils.graphics.colours.Colour] to use when
         drawing.
    pen: graphics.Pen
         The [`Pen`][lbutils.graphics.Pen] to use when drawing on the canvas.
    height: int
         A read-only value for the height of the canvas in pixels.
    width: int
         A read-only value for the width of the canvas in pixels.
    x: int
            The X co-ordinate value of the `cursor`
    y: int
            The Y co-ordinate value of the `cursor`
    x_y: int
            A tuple representing the co-ordinate (x ,y) of the `cursor`

    Methods
    -------

    **Cursor and Origin Movements**

    * `move_to()`. Move the internal [`cursor`]
    [lbutils.graphics.Canvas.cursor]  to the co-ordinate values (x, y) for
    the next sequence of drawing commands.

    * `move_origin_to()`. Sets the user drawing [`origin`]
    [lbutils.graphics.Canvas.origin] of the `Canvas` to the specified
    co-ordinates for the next sequence of drawing commands.

    **Colour Management**

    * `select_bg_colour()`. Return the colour to be used for drawing in the
    background, taking into account the (optional) overrides specified in
    `bg_colour` and `pen`. The selected colour will obey the standard colour
    selection precedence of the `Canvas` class, and is guaranteed to return a
    valid [`Colour`][lbutils.graphics.colours.Colour] object.

    * `select_fg_colour()`. Return the colour to be used for drawing in the
    foreground, taking into account the (optional) overrides specified in `color`
    and `pen`. The selected colour will obey the standard colour selection
    precedence of the `Canvas` class, and is guaranteed to return a valid
    [`Colour`][lbutils.graphics.colours.Colour] object.

    **Shape and Line Drawing Primitives**

    * `draw_line()`. Draw a line from a specified point (by default the
    [`cursor`][lbutils.graphics.Canvas.cursor]) to a co-ordinate.

    * `draw_rectangle()`. Draw a rectangle at the co-ordinate (x, y) of height
    and width, using the specified colours for the frame of the rectangle and
    the interior fill colour (if any).

    * `draw_to()`. Draw a line from a specified point (by default the
    [`cursor`][lbutils.graphics.Canvas.cursor]) to a co-ordinate. Alias for
    [`draw_line()`][lbutils.graphics.Canvas.draw_line].

    * `fill_screen()`. Fill the entire `Canvas` with the background colour.

    **Font and Text Handling**

    * `write_char()`. Write a character (using the current font) starting at the
    specified co-ordinates (by default the current [`cursor`]
    [lbutils.graphics.Canvas.cursor] co-ordinates.), in the specified colour.

    * `write_text()`. Write the a string (using the current font) starting at the
    specified co-ordinates (by default the current [`cursor`]
    [lbutils.graphics.Canvas.cursor] co-ordinates.), in the specified colour.

    **Pixel Manipulation**

    * `read_pixel()`. Return the [`Colour`][lbutils.graphics.colours.Colour] of
    the specified pixel.

    * `write_pixel()`. Set the pixel at the specified position to the foreground
    colour value.

    Implementation
    --------------

    Many of the drawing methods implemented here are provided in the
    most generic manner possible: i.e. they are not fully optimised
    for speed. In most cases the sub-classes can (and should) use the
    accelerated drawing primitives available on specific hardware to
    improve the routines provided here.

    Methods that **must** be implemented by sub-classes of `Canvas` are

    * [`write_pixel`][lbutils.graphics.Canvas.write_pixel]
    * [`read_pixel`][lbutils.graphics.Canvas.read_pixel]

    Methods that **could** be implemented by sub-classes of `Canvas` are

    * [`draw_line`][lbutils.graphics.Canvas.draw_line]
    * [`draw_rectangle`][lbutils.graphics.Canvas.draw_rectangle]
    """

    ##
    ## Internal Attributes
    ##

    _font: fonts.BaseFont

    ##
    ## Public Attributes
    ##

    bg_colour: graphics.Colour
    fg_colour: graphics.Colour

    pen: Optional[graphics.Pen]

    width: int
    height: int

    ##
    ## Constructors
    ##

    def __init__(
        self,
        width: int,
        height: int,
        word_order: colours.DEVICE_WORD_ORDER = colours.DEVICE_WORD_ORDER.NORMAL,
    ) -> None:
        """Create the drawing `Canvas` with the specified `width` and `height`.

        Parameters
        ----------

        width: int
             The width in pixels of the display.
        height: int
             The height in pixels of the display.
        word_order: DEVICE_WORD_ORDER, read-write
            Argument indicating if the underlying byte order used for
            the bit packing in specific hardware colour conversions.
            Defaults to `DEVICE_WORD_ORDER.NORMAL`, to use the standard
            platform (host) conventions.
        """
        # Set the Attribute Values. Note use the properties to ensure
        # that the type being set is correctly
        self.fg_colour = colours.Colour(255, 255, 255, word_order)
        self.bg_colour = colours.Colour(0, 0, 0, word_order)

        self.pen = None

        self.cursor = graphics.BoundPixel(
            0,
            0,
            min_x=0,
            max_x=width,
            min_y=0,
            max_y=height,
        )
        self.origin = graphics.BoundPixel(
            0,
            0,
            min_x=0,
            max_x=width,
            min_y=0,
            max_y=height,
        )

        self._font = fonts.Org01()

        self.width = width
        self.height = height

    ##
    ## Properties
    ##

    @property
    def font(self) -> fonts.BaseFont:
        return self._font

    @font.setter
    def font(self, font: fonts.BaseFont) -> None:
        if font is not None:
            self._font = font

    @property
    def x(self) -> int:
        """The `x` co-ordinate of the `cursor`; checking that it lies within the
        specified `width` of the `Canvas` when setting."""
        return self.cursor.x

    @x.setter
    def x(self, value: int) -> None:
        self.cursor.x = value

    @property
    def y(self) -> int:
        """The `y` co-ordinate of the `cursor`; checking that it lies within the
        specified `height` of the `Canvas` when setting."""
        return self.cursor.y

    @y.setter
    def y(self, value: int) -> None:
        self.cursor.y = value

    @property
    def x_y(self) -> tuple:
        """Sets, or returns, the internal `x` and `y` co-ordinates of the
        `cursor` as tuple.

        When _reading_ from this property, a tuple is returned with the first
        value of the tuple representing the `x` co-ordinate and the second
        value of the tuple representing the `y` co-ordinate.

        When _writing_ to this property the first value of the tuple represents
        the `x` co-ordinate, and the second value of the tuple represents the `y`
        co-ordinate. All other values in the tuple are ignored.

        Raises
        ------

        ValueError:
            If the `x` or `y` co-ordinate in the `xy` tuple cannot be converted
            to an integer.
        """
        return self.cursor.x_y

    @x_y.setter
    def x_y(self, xy: tuple) -> None:
        self.cursor.x = int(xy[0])
        self.cursor.y = int(xy[1])

    @property
    def cursor(self) -> graphics.BoundPixel:
        """The [`x`][lbutils.graphics.BoundPixel] and [`y`]
        [lbutils.graphics.BoundPixel] locations  of the current write (or read)
        operation."""
        return self._cursor

    @cursor.setter
    def cursor(self, value: graphics.BoundPixel) -> None:
        self._cursor = value

    @property
    def origin(self) -> graphics.BoundPixel:
        """The _user_ reference point for the next sequence of drawing
        primitives.

        This `origin` will not be altered by changes to the [`x`]
        [lbutils.graphics.BoundPixel] and [`y`] [lbutils.graphics.BoundPixel]
        locations of any drawing command.
        """
        return self._origin

    @origin.setter
    def origin(self, value: graphics.BoundPixel) -> None:
        self._origin = value

    ##
    ## Abstract Methods. These must be defined in sub-classes.
    ##

    @abstractmethod
    def read_pixel(self, x: int, y: int) -> graphics.Colour:
        """Read the colour value of the pixel at position (`x`, `y`) and return
        to the caller.

        Parameters
        ----------

        x: int
            The x co-ordinate of the pixel to read
        y: int
            The y co-ordinate of the pixel to read

        Returns
        -------

        Colour:
             The [`Colour`][lbutils.graphics.Colour] representation of the pixel
             located at (x, y).
        """
        pass

    @abstractmethod
    def write_pixel(self, x: int, y: int, colour: graphics.Colour) -> None:
        """Set the pixel at position (`x`, `y`) to the specified colour value.

        Parameters
        ----------

        x: int
             The X co-ordinate of the pixel to set.
        y: int
             The Y co-ordinate of the pixel to set.
        colour: Colour
             The [`Colour`][lbutils.graphics.Colour] representation of the pixel
             located at (x, y).
        """
        pass

    @abstractmethod
    def draw_line(
        self,
        end: tuple[int, int],
        start: Optional[tuple[int, int]] = None,
        fg_colour: Optional[graphics.Colour] = None,
        pen: Optional[graphics.Pen] = None,
    ) -> None:
        """Draw a line from the current `cursor` co-ordinates or the co-ordinate
        specified in `start`, to the point given in the `end` co-ordinates and
        using the specified RGB colour. If the drawing colour is not specified
        in the arguments to this method, then it will use the preference order
        for the foreground colour of the `Canvas` Class to find a suitable
        colour. See [`select_fg_colour`]
        [lbutils.graphics.Canvas.select_fg_colour] for more details of the
        foreground colour selection algorithm.

        Example
        -------

        If the method is called with the `start` co-ordinate as `None` then the
        current value of the [`cursor`][lbutils.graphics.Canvas.cursor] will be
        used. However the `end` co-ordinate _must_ be specified. This means that
        in normal use the method can be called as

        ````python
        canvas.draw_line([0, 20])
        ````

        to draw a line from the current [`cursor`]
        [lbutils.graphics.Canvas.cursor] to the co-ordinate '(0, 20)'. This will
        also use the current `fg_colour` of the canvas when drawing the line.

        To change the line colour, either set the [`Pen`][lbutils.graphics.Pen],
        or call the method with the colour set directly as

        ````python
        canvas.draw_line([0, 20], fg_colour = lbutils.graphics.COLOUR_NAVY)
        ````

        The start of the line to be drawn can be changed using the `start`
        parameter: however in this case it is recommended to set _both_ the
        `start` and the `end` as named parameters, e.g.

        ````python
        canvas.draw_line(start = [0, 0], end = [0, 20])
        ````

        Using named parameter makes it much more obvious to readers of the
        library code which co-ordinates are being used to draw the line. Don't
        rely on the readers of the code remembering the positional arguments.

        Parameters
        ----------

        start: tuple
             The (x, y) co-ordinate of the _start_ point of the line, with
             the first value of the `tuple` representing the `x` co-ordinate and
             the second value of the `tuple` representing the `y` co-ordinate. If
             the `start` is `None`, the default, then the current value of the
             [`cursor`][lbutils.graphics.Canvas.cursor] is used as the start
             point of the line. Values beyond the first and second entries of
             the `tuple` are ignored.
        end: tuple
             The (x, y) co-ordinate of the pixel for the _end_ point of the line,
             with the first value of the tuple representing the `x` co-ordinate
             and the second value of the tuple representing the `y` co-ordinate.
             Values beyond the first and second entries of the `tuple` are
             ignored.
        fg_colour: Type[graphics.Colour], optional
             The [`Colour`][lbutils.graphics.Colour] to be used when drawing the
             line. If not specified, use the preference order for the foreground
             colour of the `Canvas` to find a suitable colour.
        pen: Type[graphics.Pen], optional
             The [`Pen`][lbutils.graphics.Pen] to be used when drawing the line.
             If not specified, use the preference order for the foreground colour
             of the `Canvas` to find a suitable colour.
        """
        pass

    @abstractmethod
    def draw_rectangle(
        self,
        width: int,
        height: int,
        start: Optional[tuple[int, int]] = None,
        fg_colour: Optional[graphics.Colour] = None,
        bg_colour: Optional[graphics.Colour] = None,
        pen: Optional[graphics.Pen] = None,
        style: RECTANGLE_STYLE = RECTANGLE_STYLE.FILLED,
    ) -> None:
        """Draw a rectangle at the `start` co-ordinate, or the current cursor
        postion if `start` is `None`. In either case the rectangle will be drawn
        to the specified `height` and `width`, using the either the specified or
        `Canvas` `fg_colour` for the frame of the rectangle. If the `style` is
        `"FILLED"` then  either the specified `bg_colour` or `Canvas`
        `bg_colour` as the interior colour. If the `style` is `"FRAMED"` then
        the interior of the rectangle is not drawn.

        See either [`select_fg_colour`]
        [lbutils.graphics.Canvas.select_fg_colour] for more details of the
        foreground colour selection algorithm; or [`select_bg_colour`]
        [lbutils.graphics.Canvas.select_bg_colour] for more details of the
        background colour selection algorithm. By default the rectangle is
        `"FILLED"` and so both the background and foreground colours are used.

        Parameters
        ----------

        start: tuple
             The (x, y) co-ordinate of the _start_ point of the rectangle, with
             the first value of the `tuple` representing the `x` co-ordinate and
             the second value of the `tuple` representing the `y` co-ordinate. If
             the `start` is `None`, the default, then the current value of the
             [`cursor`][lbutils.graphics.Canvas.cursor] is used as the start
             point of the rectangle. Values beyond the first and second entries
             of the `tuple` are ignored.
        width: int
             The width of the rectangle in pixels.
        height: int
             The hight of the rectangle in pixels.
        fg_colour: Type[graphics.Colour], optional
             The [`Colour`][lbutils.graphics.Colour] to be used when drawing the
             rectangle. If not specified, use the preference order for the
             foreground colour of the `Canvas` to find a suitable colour.
        bg_colour: Type[graphics.Colour], optional
             The [`Colour`][lbutils.graphics.Colour] to be used when filling the
             rectangle. If not specified, use the preference order for the
             background colour of the `Canvas` to find a suitable colour.
        pen: Type[graphics.Pen], optional
             The [`Pen`][lbutils.graphics.Pen] to be used when drawing the
             rectangle, using the forground colour for the frame and the
             background colour for the fill. If not specified, use the preference
             order for the foreground and background colours of the `Canvas` to
             find suitable colours.
        style: RECTANGLE_STYLE, optional
             Set the style for the rectangle to draw. The default style,
             `RECTANGLE_STYLE.FILLED`, sets the interior of the rectangle to the
             the current background colour.
        """
        pass

    ##
    ## Colour Selection Methods
    ##

    def select_fg_colour(
        self,
        fg_colour: Optional[graphics.Colour] = None,
        pen: Optional[graphics.Pen] = None,
    ) -> graphics.Colour:
        """Return the colour to be used for drawing in the foreground, taking
        into account the (optional) overrides specified in `color` and `pen`.
        The selected colour will obey the standard colour selection precedence
        of the `Canvas` class, and is guaranteed to return a valid
        [`Colour`][lbutils.graphics.colours.Colour] object.

        Parameters
        ----------

        fg_colour: Type[graphics.Colour], optional
             Overrides the current `Canvas` forground colour, using the specified
             `fg_colour` instead.
        pen: Type[graphics.Pen], optional
             Overrides the current `Canvas` pen, using the forground colour of the specified
             `pen` to choose the returned `Colour`.

        Implementation
        --------------

        The returned [`Colour`][lbutils.graphics.Colour] object is selected
        according the defined precedence

        1. The `Colour` directly specified in the method call.
        2. The foreground colour specified by the `Pen` in the method call
        of the drawing primitive.
        3. The foreground colour specified by the `Pen` of the `Canvas`
        object.
        4. The colour specified by as the default forground colour of the
            `Canvas` object.
        5. As a default of white (`COLOUR_WHITE`) for the foreground if all
        other selection methods fail.

        Returns
        -------

        Colour:
             A [`Colour`][lbutils.graphics.Colour] object representing the
             current foreground colour of the `Canvas`
        """

        if pen is not None:
            return pen.fg_colour
        elif fg_colour is not None:
            return fg_colour
        elif self.pen is not None:
            return self.pen.fg_colour
        elif self.fg_colour is not None:
            return self.fg_colour
        else:
            return colours.COLOUR_WHITE

    def select_bg_colour(
        self,
        bg_colour: Optional[graphics.Colour] = None,
        pen: Optional[graphics.Pen] = None,
    ) -> graphics.Colour:
        """Return the colour to be used for drawing in the background, taking
        into account the (optional) overrides specified in `bg_colour` and
        `pen`. The selected colour will obey the standard colour selection
        precedence of the `Canvas` class, and is guaranteed to return a valid
        [`Colour`][lbutils.graphics.colours.Colour] object.

        Parameters
        ----------

        bg_colour: Type[graphics.Colour], optional
             Overrides the current `Canvas` background [`Colour`][lbutils.graphics.Colour],
             using the specified `bg_colour` instead.
        pen: Type[graphics.Pen], optional
             Overrides the current `Canvas` pen, using the background colour of
             the specified `pen` to choose the returned [`Colour`][lbutils.graphics.Colour].

        Implementation
        --------------

        The returned [`Colour`][lbutils.graphics.Colour] object is selected
        according the defined precedence

        1. The `Colour` directly specified in the method call.
        2. The background colour specified by the `Pen` in the method call of the
        drawing primitive.
        3. The background colour specified by the `Pen` of the `Canvas` object.
        4. The colour specified by as the default background colour of
        the`Canvas` object.
        5. As a default of black (`COLOUR_BLACK`) if all other selection methods
        fail.

        Returns
        -------

        Colour:
             A [`Colour`][lbutils.graphics.Colour] object representing the
             current background colour of the `Canvas`.
        """

        if pen is not None:
            return pen.bg_colour
        elif bg_colour is not None:
            return bg_colour
        elif self.pen is not None:
            return self.pen.bg_colour
        elif self.bg_colour is not None:
            return self.bg_colour
        else:
            return colours.COLOUR_BLACK

    ##
    ## Drawing Primitives using the `cursor`
    ##

    def fill_screen(self, bg_colour: Optional[graphics.Colour] = None) -> None:
        """Fill the entire display with the specified colour. By default this
        will use the colour preference order to find a background colour if
        `bg_colour` is `None`. See [`select_bg_colour`]
        [lbutils.graphics.Canvas.select_bg_colour] for more details of the
        background colour selection algorithm.

        Parameters
        ----------

        bg_colour: Type[graphics.Colour], optional
             The [`Colour`][lbutils.graphics.Colour] to be used to fill the
             screen. Defaults to using the colour search order of the `Canvas`
             to find a colour.
        """
        fill_colour = self.select_bg_colour(bg_colour=bg_colour)

        self.draw_rectangle(
            start=(0, 0),
            width=self.width,
            height=self.height,
            fg_colour=fill_colour,
            bg_colour=fill_colour,
            style=RECTANGLE_STYLE.FILLED,
        )

    def write_text(
        self,
        txt_str: str,
        start: Optional[tuple[int, int]] = None,
        fg_colour: Optional[graphics.Colour] = None,
        pen: Optional[graphics.Pen] = None,
    ) -> None:
        """Write the string `txt_str` (using the current `font`) starting at the
        the pixel position (`x`, `y`) specified either by the `cursor` (the
        default) or the `start` tuple. The text string is then written in the
        specified `fg_colour`, or selected from the `Canvas` `fg_colour`, to the
        display. See
        [`select_fg_colour`][lbutils.graphics.Canvas.select_fg_colour] for more
        details of the colour selection algorithm.

        !!! note
             Whilst the `txt_str` character _must_ be a valid UTF-8 string, most
             fonts only support the equivalent of the (7-bit) ASCII character
             set. This method _will not_ display character values that cannot be
             supported by the underlying font. See the font description for the
             exact values that are valid for the specific font being used.

        Parameters
        ----------

        txt_str:
             The string of characters to write to the display.
        start: tuple, optional
             The (x, y) co-ordinate of the _start_ point of the character, with
             the first value of the `tuple` representing the `x` co-ordinate and
             the second value of the `tuple` representing the `y` co-ordinate. If
             the `start` is `None`, the default, then the current value of the
             [`cursor`][lbutils.graphics.Canvas.cursor] is used as the start
             point of the character. Values beyond the first and second entries
             of the `tuple` are ignored.
        fg_colour: Type[graphics.Colour], optional
             The [`Colour`][lbutils.graphics.Colour] to be used when drawing the
             line. If not specified, use the preference order for the foreground
             colour of the `Canvas` to find a suitable colour.
        pen: Type[graphics.Pen], optional
             The [`Pen`][lbutils.graphics.Pen] to be used when drawing the line.
             If not specified, use the preference order for the foreground colour
             of the `Canvas` to find a suitable colour.
        """

        # Check to see if we have a valid font: if not things
        # end here
        if self.font is not None:
            # If the `start` has been given, move the cursor
            # to that co-ordinate
            if start is not None:
                self.cursor.x_y = start

            # Now write the string. Note that we set the `start`
            # of the `write_char()` method to `None` to hand control
            # of the cursor to that method.
            for character in txt_str:
                self.write_char(
                    start=None,
                    utf8_char=character,
                    fg_colour=fg_colour,
                    pen=pen,
                )

    def write_char(
        self,
        utf8_char: str,
        start: Optional[tuple[int, int]] = None,
        fg_colour: Optional[graphics.Colour] = None,
        pen: Optional[graphics.Pen] = None,
    ) -> None:
        """Write a `utf8_char` character (using the current `font`) starting at
        the pixel position (`x`, `y`) of the `cursor` in the specified `colour`.
        See [`select_fg_colour`][lbutils.graphics.Canvas.select_fg_colour] for
        more details of the colour selection algorithm.

        !!! note
            Whilst the `utf8_char` character _must_ be a valid UTF-8 character,
            most fonts only support the equivalent of the (7-bit) ASCII character
            set. This method _will not_ display character values that cannot be
            supported by the underlying font. See the font description for the
            exact values that are valid for the specific font being used.

        Parameters
        ----------

        utf8_char:
            The character to write to the display.
        start: tuple, optional
             The (x, y) co-ordinate of the _start_ point of the character, with
             the first value of the `tuple` representing the `x` co-ordinate and
             the second value of the `tuple` representing the `y` co-ordinate. If
             the `start` is `None`, the default, then the current value of the
             [`cursor`][lbutils.graphics.Canvas.cursor] is used as the start
             point of the character. Values beyond the first and second entries
             of the `tuple` are ignored.
        fg_colour: Type[graphics.Colour], optional
            The [`Colour`][lbutils.graphics.Colour] to be used when drawing the
            character. If not specified, use the preference order for the
            foreground colour of the `Canvas` to find a suitable colour.
        pen: Type[graphics.Pen], optional
            The [`Pen`][lbutils.graphics.Pen] to be used when drawing the line.
            If not specified, use the preference order for the foreground colour
            of the `Canvas` to find a suitable colour.
        """

        # Work out what the foreground colour should be
        use_fg_colour = self.select_fg_colour(fg_colour=fg_colour, pen=pen)

        # If a `start` has been specified, then move the cursor to
        # that co-ordinate. Otherwise we assume the cursor is in the
        # right place
        if start is not None:
            self.cursor.x_y = start

        # Get the parameters we need to draw the specified glyph in the
        # current font
        self.font.set_position(utf8_char)
        _offset, _width, _height, _cursor, x_off, y_off = self.font.current_glyph

        # Draw the glyph at the current cursor position
        for y1 in range(_height):
            for x1 in range(_width):
                if self.font.get_next():
                    self.write_pixel(
                        self.cursor.x + x1 + x_off,
                        self.cursor.y + y1 + y_off,
                        use_fg_colour,
                    )

        # Move the cursor to the `x` position at the end of the glyph.
        # This is also where the next character should be drawn
        self.cursor.x += _cursor

    ##
    ## utility Methods
    ##

    def move_to(self, xy: tuple[int, int]) -> None:
        """Set the internal (`x`, `y`) co-ordinates of the `cursor` to the
        values of the `xy` two-value tuple. An alias for the `x_y` property of
        `Canvas`.

        Parameters
        ----------

        xy: tuple
            The first value of the `xy` tuple represents the `x` co-ordinate, and
            the second value of the `xy` tuple represents the `y` co-ordinate.
            All other values in the `xy` tuple are ignored.

        Raises
        ------

        ValueError:
            If the `x` or `y` co-ordinate in the `xy` tuple cannot be converted
            to an integer.
        """
        self.cursor.x_y = xy

    def move_origin_to(self) -> None:
        """Set the user drawing [`origin`] [lbutils.graphics.Canvas.origin] of
        the `Canvas` to the specified co-ordinate the next sequence of drawing
        commands.

                Parameters
        ----------

        xy: tuple
            The first value of the `xy` tuple represents the `x` co-ordinate, and
            the second value of the `xy` tuple represents the `y` co-ordinate.
            All other values in the `xy` tuple are ignored.

        Raises
        ------

        ValueError:
            If the `x` or `y` co-ordinate in the `xy` tuple cannot be converted
            to an integer.
        """
        self.cursor = self.origin

cursor property writable

cursor: graphics.BoundPixel

The x and y locations of the current write (or read) operation.

origin property writable

origin: graphics.BoundPixel

The user reference point for the next sequence of drawing primitives.

This origin will not be altered by changes to the x and y locations of any drawing command.

x property writable

x: int

The x co-ordinate of the cursor; checking that it lies within the specified width of the Canvas when setting.

x_y property writable

x_y: tuple

Sets, or returns, the internal x and y co-ordinates of the cursor as tuple.

When reading from this property, a tuple is returned with the first value of the tuple representing the x co-ordinate and the second value of the tuple representing the y co-ordinate.

When writing to this property the first value of the tuple represents the x co-ordinate, and the second value of the tuple represents the y co-ordinate. All other values in the tuple are ignored.

Raises:

  • ValueError:

    If the x or y co-ordinate in the xy tuple cannot be converted to an integer.

y property writable

y: int

The y co-ordinate of the cursor; checking that it lies within the specified height of the Canvas when setting.

__init__

__init__(
    width: int,
    height: int,
    word_order: colours.DEVICE_WORD_ORDER = colours.DEVICE_WORD_ORDER.NORMAL,
) -> None

Create the drawing Canvas with the specified width and height.

Parameters:

  • width (int) –

    The width in pixels of the display.

  • height (int) –

    The height in pixels of the display.

  • word_order (colours.DEVICE_WORD_ORDER) –

    Argument indicating if the underlying byte order used for the bit packing in specific hardware colour conversions. Defaults to DEVICE_WORD_ORDER.NORMAL, to use the standard platform (host) conventions.

Source code in lbutils/graphics/canvas.py
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
def __init__(
    self,
    width: int,
    height: int,
    word_order: colours.DEVICE_WORD_ORDER = colours.DEVICE_WORD_ORDER.NORMAL,
) -> None:
    """Create the drawing `Canvas` with the specified `width` and `height`.

    Parameters
    ----------

    width: int
         The width in pixels of the display.
    height: int
         The height in pixels of the display.
    word_order: DEVICE_WORD_ORDER, read-write
        Argument indicating if the underlying byte order used for
        the bit packing in specific hardware colour conversions.
        Defaults to `DEVICE_WORD_ORDER.NORMAL`, to use the standard
        platform (host) conventions.
    """
    # Set the Attribute Values. Note use the properties to ensure
    # that the type being set is correctly
    self.fg_colour = colours.Colour(255, 255, 255, word_order)
    self.bg_colour = colours.Colour(0, 0, 0, word_order)

    self.pen = None

    self.cursor = graphics.BoundPixel(
        0,
        0,
        min_x=0,
        max_x=width,
        min_y=0,
        max_y=height,
    )
    self.origin = graphics.BoundPixel(
        0,
        0,
        min_x=0,
        max_x=width,
        min_y=0,
        max_y=height,
    )

    self._font = fonts.Org01()

    self.width = width
    self.height = height

draw_line

draw_line(
    end: tuple[int, int],
    start: Optional[tuple[int, int]] = None,
    fg_colour: Optional[graphics.Colour] = None,
    pen: Optional[graphics.Pen] = None,
) -> None

Draw a line from the current cursor co-ordinates or the co-ordinate specified in start, to the point given in the end co-ordinates and using the specified RGB colour. If the drawing colour is not specified in the arguments to this method, then it will use the preference order for the foreground colour of the Canvas Class to find a suitable colour. See select_fg_colour for more details of the foreground colour selection algorithm.

Example

If the method is called with the start co-ordinate as None then the current value of the cursor will be used. However the end co-ordinate must be specified. This means that in normal use the method can be called as

canvas.draw_line([0, 20])

to draw a line from the current cursor to the co-ordinate '(0, 20)'. This will also use the current fg_colour of the canvas when drawing the line.

To change the line colour, either set the Pen, or call the method with the colour set directly as

canvas.draw_line([0, 20], fg_colour = lbutils.graphics.COLOUR_NAVY)

The start of the line to be drawn can be changed using the start parameter: however in this case it is recommended to set both the start and the end as named parameters, e.g.

canvas.draw_line(start = [0, 0], end = [0, 20])

Using named parameter makes it much more obvious to readers of the library code which co-ordinates are being used to draw the line. Don't rely on the readers of the code remembering the positional arguments.

Parameters:

  • start (Optional[tuple[int, int]]) –

    The (x, y) co-ordinate of the start point of the line, with the first value of the tuple representing the x co-ordinate and the second value of the tuple representing the y co-ordinate. If the start is None, the default, then the current value of the cursor is used as the start point of the line. Values beyond the first and second entries of the tuple are ignored.

  • end (tuple[int, int]) –

    The (x, y) co-ordinate of the pixel for the end point of the line, with the first value of the tuple representing the x co-ordinate and the second value of the tuple representing the y co-ordinate. Values beyond the first and second entries of the tuple are ignored.

  • fg_colour (Optional[graphics.Colour]) –

    The Colour to be used when drawing the line. If not specified, use the preference order for the foreground colour of the Canvas to find a suitable colour.

  • pen (Optional[graphics.Pen]) –

    The Pen to be used when drawing the line. If not specified, use the preference order for the foreground colour of the Canvas to find a suitable colour.

Source code in lbutils/graphics/canvas.py
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
@abstractmethod
def draw_line(
    self,
    end: tuple[int, int],
    start: Optional[tuple[int, int]] = None,
    fg_colour: Optional[graphics.Colour] = None,
    pen: Optional[graphics.Pen] = None,
) -> None:
    """Draw a line from the current `cursor` co-ordinates or the co-ordinate
    specified in `start`, to the point given in the `end` co-ordinates and
    using the specified RGB colour. If the drawing colour is not specified
    in the arguments to this method, then it will use the preference order
    for the foreground colour of the `Canvas` Class to find a suitable
    colour. See [`select_fg_colour`]
    [lbutils.graphics.Canvas.select_fg_colour] for more details of the
    foreground colour selection algorithm.

    Example
    -------

    If the method is called with the `start` co-ordinate as `None` then the
    current value of the [`cursor`][lbutils.graphics.Canvas.cursor] will be
    used. However the `end` co-ordinate _must_ be specified. This means that
    in normal use the method can be called as

    ````python
    canvas.draw_line([0, 20])
    ````

    to draw a line from the current [`cursor`]
    [lbutils.graphics.Canvas.cursor] to the co-ordinate '(0, 20)'. This will
    also use the current `fg_colour` of the canvas when drawing the line.

    To change the line colour, either set the [`Pen`][lbutils.graphics.Pen],
    or call the method with the colour set directly as

    ````python
    canvas.draw_line([0, 20], fg_colour = lbutils.graphics.COLOUR_NAVY)
    ````

    The start of the line to be drawn can be changed using the `start`
    parameter: however in this case it is recommended to set _both_ the
    `start` and the `end` as named parameters, e.g.

    ````python
    canvas.draw_line(start = [0, 0], end = [0, 20])
    ````

    Using named parameter makes it much more obvious to readers of the
    library code which co-ordinates are being used to draw the line. Don't
    rely on the readers of the code remembering the positional arguments.

    Parameters
    ----------

    start: tuple
         The (x, y) co-ordinate of the _start_ point of the line, with
         the first value of the `tuple` representing the `x` co-ordinate and
         the second value of the `tuple` representing the `y` co-ordinate. If
         the `start` is `None`, the default, then the current value of the
         [`cursor`][lbutils.graphics.Canvas.cursor] is used as the start
         point of the line. Values beyond the first and second entries of
         the `tuple` are ignored.
    end: tuple
         The (x, y) co-ordinate of the pixel for the _end_ point of the line,
         with the first value of the tuple representing the `x` co-ordinate
         and the second value of the tuple representing the `y` co-ordinate.
         Values beyond the first and second entries of the `tuple` are
         ignored.
    fg_colour: Type[graphics.Colour], optional
         The [`Colour`][lbutils.graphics.Colour] to be used when drawing the
         line. If not specified, use the preference order for the foreground
         colour of the `Canvas` to find a suitable colour.
    pen: Type[graphics.Pen], optional
         The [`Pen`][lbutils.graphics.Pen] to be used when drawing the line.
         If not specified, use the preference order for the foreground colour
         of the `Canvas` to find a suitable colour.
    """
    pass

draw_rectangle

draw_rectangle(
    width: int,
    height: int,
    start: Optional[tuple[int, int]] = None,
    fg_colour: Optional[graphics.Colour] = None,
    bg_colour: Optional[graphics.Colour] = None,
    pen: Optional[graphics.Pen] = None,
    style: RECTANGLE_STYLE = RECTANGLE_STYLE.FILLED,
) -> None

Draw a rectangle at the start co-ordinate, or the current cursor postion if start is None. In either case the rectangle will be drawn to the specified height and width, using the either the specified or Canvas fg_colour for the frame of the rectangle. If the style is "FILLED" then either the specified bg_colour or Canvas bg_colour as the interior colour. If the style is "FRAMED" then the interior of the rectangle is not drawn.

See either select_fg_colour for more details of the foreground colour selection algorithm; or select_bg_colour for more details of the background colour selection algorithm. By default the rectangle is "FILLED" and so both the background and foreground colours are used.

Parameters:

  • start (Optional[tuple[int, int]]) –

    The (x, y) co-ordinate of the start point of the rectangle, with the first value of the tuple representing the x co-ordinate and the second value of the tuple representing the y co-ordinate. If the start is None, the default, then the current value of the cursor is used as the start point of the rectangle. Values beyond the first and second entries of the tuple are ignored.

  • width (int) –

    The width of the rectangle in pixels.

  • height (int) –

    The hight of the rectangle in pixels.

  • fg_colour (Optional[graphics.Colour]) –

    The Colour to be used when drawing the rectangle. If not specified, use the preference order for the foreground colour of the Canvas to find a suitable colour.

  • bg_colour (Optional[graphics.Colour]) –

    The Colour to be used when filling the rectangle. If not specified, use the preference order for the background colour of the Canvas to find a suitable colour.

  • pen (Optional[graphics.Pen]) –

    The Pen to be used when drawing the rectangle, using the forground colour for the frame and the background colour for the fill. If not specified, use the preference order for the foreground and background colours of the Canvas to find suitable colours.

  • style (RECTANGLE_STYLE) –

    Set the style for the rectangle to draw. The default style, RECTANGLE_STYLE.FILLED, sets the interior of the rectangle to the the current background colour.

Source code in lbutils/graphics/canvas.py
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
@abstractmethod
def draw_rectangle(
    self,
    width: int,
    height: int,
    start: Optional[tuple[int, int]] = None,
    fg_colour: Optional[graphics.Colour] = None,
    bg_colour: Optional[graphics.Colour] = None,
    pen: Optional[graphics.Pen] = None,
    style: RECTANGLE_STYLE = RECTANGLE_STYLE.FILLED,
) -> None:
    """Draw a rectangle at the `start` co-ordinate, or the current cursor
    postion if `start` is `None`. In either case the rectangle will be drawn
    to the specified `height` and `width`, using the either the specified or
    `Canvas` `fg_colour` for the frame of the rectangle. If the `style` is
    `"FILLED"` then  either the specified `bg_colour` or `Canvas`
    `bg_colour` as the interior colour. If the `style` is `"FRAMED"` then
    the interior of the rectangle is not drawn.

    See either [`select_fg_colour`]
    [lbutils.graphics.Canvas.select_fg_colour] for more details of the
    foreground colour selection algorithm; or [`select_bg_colour`]
    [lbutils.graphics.Canvas.select_bg_colour] for more details of the
    background colour selection algorithm. By default the rectangle is
    `"FILLED"` and so both the background and foreground colours are used.

    Parameters
    ----------

    start: tuple
         The (x, y) co-ordinate of the _start_ point of the rectangle, with
         the first value of the `tuple` representing the `x` co-ordinate and
         the second value of the `tuple` representing the `y` co-ordinate. If
         the `start` is `None`, the default, then the current value of the
         [`cursor`][lbutils.graphics.Canvas.cursor] is used as the start
         point of the rectangle. Values beyond the first and second entries
         of the `tuple` are ignored.
    width: int
         The width of the rectangle in pixels.
    height: int
         The hight of the rectangle in pixels.
    fg_colour: Type[graphics.Colour], optional
         The [`Colour`][lbutils.graphics.Colour] to be used when drawing the
         rectangle. If not specified, use the preference order for the
         foreground colour of the `Canvas` to find a suitable colour.
    bg_colour: Type[graphics.Colour], optional
         The [`Colour`][lbutils.graphics.Colour] to be used when filling the
         rectangle. If not specified, use the preference order for the
         background colour of the `Canvas` to find a suitable colour.
    pen: Type[graphics.Pen], optional
         The [`Pen`][lbutils.graphics.Pen] to be used when drawing the
         rectangle, using the forground colour for the frame and the
         background colour for the fill. If not specified, use the preference
         order for the foreground and background colours of the `Canvas` to
         find suitable colours.
    style: RECTANGLE_STYLE, optional
         Set the style for the rectangle to draw. The default style,
         `RECTANGLE_STYLE.FILLED`, sets the interior of the rectangle to the
         the current background colour.
    """
    pass

fill_screen

fill_screen(
    bg_colour: Optional[graphics.Colour] = None,
) -> None

Fill the entire display with the specified colour. By default this will use the colour preference order to find a background colour if bg_colour is None. See select_bg_colour for more details of the background colour selection algorithm.

Parameters:

  • bg_colour (Optional[graphics.Colour]) –

    The Colour to be used to fill the screen. Defaults to using the colour search order of the Canvas to find a colour.

Source code in lbutils/graphics/canvas.py
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
def fill_screen(self, bg_colour: Optional[graphics.Colour] = None) -> None:
    """Fill the entire display with the specified colour. By default this
    will use the colour preference order to find a background colour if
    `bg_colour` is `None`. See [`select_bg_colour`]
    [lbutils.graphics.Canvas.select_bg_colour] for more details of the
    background colour selection algorithm.

    Parameters
    ----------

    bg_colour: Type[graphics.Colour], optional
         The [`Colour`][lbutils.graphics.Colour] to be used to fill the
         screen. Defaults to using the colour search order of the `Canvas`
         to find a colour.
    """
    fill_colour = self.select_bg_colour(bg_colour=bg_colour)

    self.draw_rectangle(
        start=(0, 0),
        width=self.width,
        height=self.height,
        fg_colour=fill_colour,
        bg_colour=fill_colour,
        style=RECTANGLE_STYLE.FILLED,
    )

move_origin_to

move_origin_to() -> None

Set the user drawing origin of the Canvas to the specified co-ordinate the next sequence of drawing commands.

    Parameters

xy: tuple The first value of the xy tuple represents the x co-ordinate, and the second value of the xy tuple represents the y co-ordinate. All other values in the xy tuple are ignored.

Raises:

  • ValueError:

    If the x or y co-ordinate in the xy tuple cannot be converted to an integer.

Source code in lbutils/graphics/canvas.py
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
def move_origin_to(self) -> None:
    """Set the user drawing [`origin`] [lbutils.graphics.Canvas.origin] of
    the `Canvas` to the specified co-ordinate the next sequence of drawing
    commands.

            Parameters
    ----------

    xy: tuple
        The first value of the `xy` tuple represents the `x` co-ordinate, and
        the second value of the `xy` tuple represents the `y` co-ordinate.
        All other values in the `xy` tuple are ignored.

    Raises
    ------

    ValueError:
        If the `x` or `y` co-ordinate in the `xy` tuple cannot be converted
        to an integer.
    """
    self.cursor = self.origin

move_to

move_to(xy: tuple[int, int]) -> None

Set the internal (x, y) co-ordinates of the cursor to the values of the xy two-value tuple. An alias for the x_y property of Canvas.

Parameters:

  • xy (tuple[int, int]) –

    The first value of the xy tuple represents the x co-ordinate, and the second value of the xy tuple represents the y co-ordinate. All other values in the xy tuple are ignored.

Raises:

  • ValueError:

    If the x or y co-ordinate in the xy tuple cannot be converted to an integer.

Source code in lbutils/graphics/canvas.py
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
def move_to(self, xy: tuple[int, int]) -> None:
    """Set the internal (`x`, `y`) co-ordinates of the `cursor` to the
    values of the `xy` two-value tuple. An alias for the `x_y` property of
    `Canvas`.

    Parameters
    ----------

    xy: tuple
        The first value of the `xy` tuple represents the `x` co-ordinate, and
        the second value of the `xy` tuple represents the `y` co-ordinate.
        All other values in the `xy` tuple are ignored.

    Raises
    ------

    ValueError:
        If the `x` or `y` co-ordinate in the `xy` tuple cannot be converted
        to an integer.
    """
    self.cursor.x_y = xy

read_pixel

read_pixel(x: int, y: int) -> graphics.Colour

Read the colour value of the pixel at position (x, y) and return to the caller.

Parameters:

  • x (int) –

    The x co-ordinate of the pixel to read

  • y (int) –

    The y co-ordinate of the pixel to read

Returns:

Source code in lbutils/graphics/canvas.py
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
@abstractmethod
def read_pixel(self, x: int, y: int) -> graphics.Colour:
    """Read the colour value of the pixel at position (`x`, `y`) and return
    to the caller.

    Parameters
    ----------

    x: int
        The x co-ordinate of the pixel to read
    y: int
        The y co-ordinate of the pixel to read

    Returns
    -------

    Colour:
         The [`Colour`][lbutils.graphics.Colour] representation of the pixel
         located at (x, y).
    """
    pass

select_bg_colour

select_bg_colour(
    bg_colour: Optional[graphics.Colour] = None,
    pen: Optional[graphics.Pen] = None,
) -> graphics.Colour

Return the colour to be used for drawing in the background, taking into account the (optional) overrides specified in bg_colour and pen. The selected colour will obey the standard colour selection precedence of the Canvas class, and is guaranteed to return a valid Colour object.

Parameters:

  • bg_colour (Optional[graphics.Colour]) –

    Overrides the current Canvas background Colour, using the specified bg_colour instead.

  • pen (Optional[graphics.Pen]) –

    Overrides the current Canvas pen, using the background colour of the specified pen to choose the returned Colour.

Implementation

The returned Colour object is selected according the defined precedence

  1. The Colour directly specified in the method call.
  2. The background colour specified by the Pen in the method call of the drawing primitive.
  3. The background colour specified by the Pen of the Canvas object.
  4. The colour specified by as the default background colour of theCanvas object.
  5. As a default of black (COLOUR_BLACK) if all other selection methods fail.

Returns:

  • Colour( graphics.Colour ) –

    A Colour object representing the current background colour of the Canvas.

Source code in lbutils/graphics/canvas.py
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
def select_bg_colour(
    self,
    bg_colour: Optional[graphics.Colour] = None,
    pen: Optional[graphics.Pen] = None,
) -> graphics.Colour:
    """Return the colour to be used for drawing in the background, taking
    into account the (optional) overrides specified in `bg_colour` and
    `pen`. The selected colour will obey the standard colour selection
    precedence of the `Canvas` class, and is guaranteed to return a valid
    [`Colour`][lbutils.graphics.colours.Colour] object.

    Parameters
    ----------

    bg_colour: Type[graphics.Colour], optional
         Overrides the current `Canvas` background [`Colour`][lbutils.graphics.Colour],
         using the specified `bg_colour` instead.
    pen: Type[graphics.Pen], optional
         Overrides the current `Canvas` pen, using the background colour of
         the specified `pen` to choose the returned [`Colour`][lbutils.graphics.Colour].

    Implementation
    --------------

    The returned [`Colour`][lbutils.graphics.Colour] object is selected
    according the defined precedence

    1. The `Colour` directly specified in the method call.
    2. The background colour specified by the `Pen` in the method call of the
    drawing primitive.
    3. The background colour specified by the `Pen` of the `Canvas` object.
    4. The colour specified by as the default background colour of
    the`Canvas` object.
    5. As a default of black (`COLOUR_BLACK`) if all other selection methods
    fail.

    Returns
    -------

    Colour:
         A [`Colour`][lbutils.graphics.Colour] object representing the
         current background colour of the `Canvas`.
    """

    if pen is not None:
        return pen.bg_colour
    elif bg_colour is not None:
        return bg_colour
    elif self.pen is not None:
        return self.pen.bg_colour
    elif self.bg_colour is not None:
        return self.bg_colour
    else:
        return colours.COLOUR_BLACK

select_fg_colour

select_fg_colour(
    fg_colour: Optional[graphics.Colour] = None,
    pen: Optional[graphics.Pen] = None,
) -> graphics.Colour

Return the colour to be used for drawing in the foreground, taking into account the (optional) overrides specified in color and pen. The selected colour will obey the standard colour selection precedence of the Canvas class, and is guaranteed to return a valid Colour object.

Parameters:

  • fg_colour (Optional[graphics.Colour]) –

    Overrides the current Canvas forground colour, using the specified fg_colour instead.

  • pen (Optional[graphics.Pen]) –

    Overrides the current Canvas pen, using the forground colour of the specified pen to choose the returned Colour.

Implementation

The returned Colour object is selected according the defined precedence

  1. The Colour directly specified in the method call.
  2. The foreground colour specified by the Pen in the method call of the drawing primitive.
  3. The foreground colour specified by the Pen of the Canvas object.
  4. The colour specified by as the default forground colour of the Canvas object.
  5. As a default of white (COLOUR_WHITE) for the foreground if all other selection methods fail.

Returns:

  • Colour( graphics.Colour ) –

    A Colour object representing the current foreground colour of the Canvas

Source code in lbutils/graphics/canvas.py
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
def select_fg_colour(
    self,
    fg_colour: Optional[graphics.Colour] = None,
    pen: Optional[graphics.Pen] = None,
) -> graphics.Colour:
    """Return the colour to be used for drawing in the foreground, taking
    into account the (optional) overrides specified in `color` and `pen`.
    The selected colour will obey the standard colour selection precedence
    of the `Canvas` class, and is guaranteed to return a valid
    [`Colour`][lbutils.graphics.colours.Colour] object.

    Parameters
    ----------

    fg_colour: Type[graphics.Colour], optional
         Overrides the current `Canvas` forground colour, using the specified
         `fg_colour` instead.
    pen: Type[graphics.Pen], optional
         Overrides the current `Canvas` pen, using the forground colour of the specified
         `pen` to choose the returned `Colour`.

    Implementation
    --------------

    The returned [`Colour`][lbutils.graphics.Colour] object is selected
    according the defined precedence

    1. The `Colour` directly specified in the method call.
    2. The foreground colour specified by the `Pen` in the method call
    of the drawing primitive.
    3. The foreground colour specified by the `Pen` of the `Canvas`
    object.
    4. The colour specified by as the default forground colour of the
        `Canvas` object.
    5. As a default of white (`COLOUR_WHITE`) for the foreground if all
    other selection methods fail.

    Returns
    -------

    Colour:
         A [`Colour`][lbutils.graphics.Colour] object representing the
         current foreground colour of the `Canvas`
    """

    if pen is not None:
        return pen.fg_colour
    elif fg_colour is not None:
        return fg_colour
    elif self.pen is not None:
        return self.pen.fg_colour
    elif self.fg_colour is not None:
        return self.fg_colour
    else:
        return colours.COLOUR_WHITE

write_char

write_char(
    utf8_char: str,
    start: Optional[tuple[int, int]] = None,
    fg_colour: Optional[graphics.Colour] = None,
    pen: Optional[graphics.Pen] = None,
) -> None

Write a utf8_char character (using the current font) starting at the pixel position (x, y) of the cursor in the specified colour. See select_fg_colour for more details of the colour selection algorithm.

Note

Whilst the utf8_char character must be a valid UTF-8 character, most fonts only support the equivalent of the (7-bit) ASCII character set. This method will not display character values that cannot be supported by the underlying font. See the font description for the exact values that are valid for the specific font being used.

Parameters:

  • utf8_char (str) –

    The character to write to the display.

  • start (Optional[tuple[int, int]]) –

    The (x, y) co-ordinate of the start point of the character, with the first value of the tuple representing the x co-ordinate and the second value of the tuple representing the y co-ordinate. If the start is None, the default, then the current value of the cursor is used as the start point of the character. Values beyond the first and second entries of the tuple are ignored.

  • fg_colour (Optional[graphics.Colour]) –

    The Colour to be used when drawing the character. If not specified, use the preference order for the foreground colour of the Canvas to find a suitable colour.

  • pen (Optional[graphics.Pen]) –

    The Pen to be used when drawing the line. If not specified, use the preference order for the foreground colour of the Canvas to find a suitable colour.

Source code in lbutils/graphics/canvas.py
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
def write_char(
    self,
    utf8_char: str,
    start: Optional[tuple[int, int]] = None,
    fg_colour: Optional[graphics.Colour] = None,
    pen: Optional[graphics.Pen] = None,
) -> None:
    """Write a `utf8_char` character (using the current `font`) starting at
    the pixel position (`x`, `y`) of the `cursor` in the specified `colour`.
    See [`select_fg_colour`][lbutils.graphics.Canvas.select_fg_colour] for
    more details of the colour selection algorithm.

    !!! note
        Whilst the `utf8_char` character _must_ be a valid UTF-8 character,
        most fonts only support the equivalent of the (7-bit) ASCII character
        set. This method _will not_ display character values that cannot be
        supported by the underlying font. See the font description for the
        exact values that are valid for the specific font being used.

    Parameters
    ----------

    utf8_char:
        The character to write to the display.
    start: tuple, optional
         The (x, y) co-ordinate of the _start_ point of the character, with
         the first value of the `tuple` representing the `x` co-ordinate and
         the second value of the `tuple` representing the `y` co-ordinate. If
         the `start` is `None`, the default, then the current value of the
         [`cursor`][lbutils.graphics.Canvas.cursor] is used as the start
         point of the character. Values beyond the first and second entries
         of the `tuple` are ignored.
    fg_colour: Type[graphics.Colour], optional
        The [`Colour`][lbutils.graphics.Colour] to be used when drawing the
        character. If not specified, use the preference order for the
        foreground colour of the `Canvas` to find a suitable colour.
    pen: Type[graphics.Pen], optional
        The [`Pen`][lbutils.graphics.Pen] to be used when drawing the line.
        If not specified, use the preference order for the foreground colour
        of the `Canvas` to find a suitable colour.
    """

    # Work out what the foreground colour should be
    use_fg_colour = self.select_fg_colour(fg_colour=fg_colour, pen=pen)

    # If a `start` has been specified, then move the cursor to
    # that co-ordinate. Otherwise we assume the cursor is in the
    # right place
    if start is not None:
        self.cursor.x_y = start

    # Get the parameters we need to draw the specified glyph in the
    # current font
    self.font.set_position(utf8_char)
    _offset, _width, _height, _cursor, x_off, y_off = self.font.current_glyph

    # Draw the glyph at the current cursor position
    for y1 in range(_height):
        for x1 in range(_width):
            if self.font.get_next():
                self.write_pixel(
                    self.cursor.x + x1 + x_off,
                    self.cursor.y + y1 + y_off,
                    use_fg_colour,
                )

    # Move the cursor to the `x` position at the end of the glyph.
    # This is also where the next character should be drawn
    self.cursor.x += _cursor

write_pixel

write_pixel(
    x: int, y: int, colour: graphics.Colour
) -> None

Set the pixel at position (x, y) to the specified colour value.

Parameters:

  • x (int) –

    The X co-ordinate of the pixel to set.

  • y (int) –

    The Y co-ordinate of the pixel to set.

  • colour (graphics.Colour) –

    The Colour representation of the pixel located at (x, y).

Source code in lbutils/graphics/canvas.py
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
@abstractmethod
def write_pixel(self, x: int, y: int, colour: graphics.Colour) -> None:
    """Set the pixel at position (`x`, `y`) to the specified colour value.

    Parameters
    ----------

    x: int
         The X co-ordinate of the pixel to set.
    y: int
         The Y co-ordinate of the pixel to set.
    colour: Colour
         The [`Colour`][lbutils.graphics.Colour] representation of the pixel
         located at (x, y).
    """
    pass

write_text

write_text(
    txt_str: str,
    start: Optional[tuple[int, int]] = None,
    fg_colour: Optional[graphics.Colour] = None,
    pen: Optional[graphics.Pen] = None,
) -> None

Write the string txt_str (using the current font) starting at the the pixel position (x, y) specified either by the cursor (the default) or the start tuple. The text string is then written in the specified fg_colour, or selected from the Canvas fg_colour, to the display. See select_fg_colour for more details of the colour selection algorithm.

Note

Whilst the txt_str character must be a valid UTF-8 string, most fonts only support the equivalent of the (7-bit) ASCII character set. This method will not display character values that cannot be supported by the underlying font. See the font description for the exact values that are valid for the specific font being used.

Parameters:

  • txt_str (str) –

    The string of characters to write to the display.

  • start (Optional[tuple[int, int]]) –

    The (x, y) co-ordinate of the start point of the character, with the first value of the tuple representing the x co-ordinate and the second value of the tuple representing the y co-ordinate. If the start is None, the default, then the current value of the cursor is used as the start point of the character. Values beyond the first and second entries of the tuple are ignored.

  • fg_colour (Optional[graphics.Colour]) –

    The Colour to be used when drawing the line. If not specified, use the preference order for the foreground colour of the Canvas to find a suitable colour.

  • pen (Optional[graphics.Pen]) –

    The Pen to be used when drawing the line. If not specified, use the preference order for the foreground colour of the Canvas to find a suitable colour.

Source code in lbutils/graphics/canvas.py
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
def write_text(
    self,
    txt_str: str,
    start: Optional[tuple[int, int]] = None,
    fg_colour: Optional[graphics.Colour] = None,
    pen: Optional[graphics.Pen] = None,
) -> None:
    """Write the string `txt_str` (using the current `font`) starting at the
    the pixel position (`x`, `y`) specified either by the `cursor` (the
    default) or the `start` tuple. The text string is then written in the
    specified `fg_colour`, or selected from the `Canvas` `fg_colour`, to the
    display. See
    [`select_fg_colour`][lbutils.graphics.Canvas.select_fg_colour] for more
    details of the colour selection algorithm.

    !!! note
         Whilst the `txt_str` character _must_ be a valid UTF-8 string, most
         fonts only support the equivalent of the (7-bit) ASCII character
         set. This method _will not_ display character values that cannot be
         supported by the underlying font. See the font description for the
         exact values that are valid for the specific font being used.

    Parameters
    ----------

    txt_str:
         The string of characters to write to the display.
    start: tuple, optional
         The (x, y) co-ordinate of the _start_ point of the character, with
         the first value of the `tuple` representing the `x` co-ordinate and
         the second value of the `tuple` representing the `y` co-ordinate. If
         the `start` is `None`, the default, then the current value of the
         [`cursor`][lbutils.graphics.Canvas.cursor] is used as the start
         point of the character. Values beyond the first and second entries
         of the `tuple` are ignored.
    fg_colour: Type[graphics.Colour], optional
         The [`Colour`][lbutils.graphics.Colour] to be used when drawing the
         line. If not specified, use the preference order for the foreground
         colour of the `Canvas` to find a suitable colour.
    pen: Type[graphics.Pen], optional
         The [`Pen`][lbutils.graphics.Pen] to be used when drawing the line.
         If not specified, use the preference order for the foreground colour
         of the `Canvas` to find a suitable colour.
    """

    # Check to see if we have a valid font: if not things
    # end here
    if self.font is not None:
        # If the `start` has been given, move the cursor
        # to that co-ordinate
        if start is not None:
            self.cursor.x_y = start

        # Now write the string. Note that we set the `start`
        # of the `write_char()` method to `None` to hand control
        # of the cursor to that method.
        for character in txt_str:
            self.write_char(
                start=None,
                utf8_char=character,
                fg_colour=fg_colour,
                pen=pen,
            )

lbutils.graphics.FrameBufferCanvas

Bases: Canvas

A Canvas backed by a framebuffer.

Source code in lbutils/graphics/canvas.py
950
951
952
953
954
class FrameBufferCanvas(Canvas):
    """A [Canvas][lbutils.graphics.Canvas] backed by a [`framebuffer`](https://d
    ocs.micropython.org/en/latest/library/framebuf.html)."""

    pass