Bitmaps, Pixmaps, Fonts, and Displays for zeptoforth - tabemann/zeptoforth GitHub Wiki
zeptoforth 1.3.3 (and later) includes optional support for bitmaps, 16-bit color pixmaps, monospace bitmap fonts, I2C SSD1306-based displays, and 16-bit color SPI ST7735S-based displays. It also includes a simple ASCII monospace bitmap font. These enable displaying graphics and text on I2C SSD1306-based displays and SPI ST7735S-based displays. The source files mentioned below are to be loaded by the user after specifying either compile-to-flash
or compile-to-ram
(which is the default).
Note that while bitmaps and SSD1306-based displays were available earlier, the order of arguments has been changed so as to group column/row and width/height arguments together for the sake of making them easier to work with. Keep this in mind when using code that had previously been written which uses bitmaps and SSD1306-based displays (even though the demo code included with zeptoforth has been modified accordingly as far as possible).
These are implemented as follows:
- Bitmaps are implemented as the
<bitmap>
class, in thebitmap
module, inextra/common/bitmap.fs
. - 16-bit color pixmaps are implemented as the
<pixmap16>
class, in thepixmap16
module, inextra/common/pixmap16.fs
. - Monospace bitmap fonts are implemented as the
<font>
class, in thefont
module, inextra/common/font.fs
. These are dependent upon the<bitmap>
class and thusextra/common/bitmap.fs
. - I2C SSD1306-based displays are implemented as the
<ssd1306>
class, in thessd1306
module, inextra/common/ssd1306.fs
. These inherit from the<bitmap>
class, and thus are dependent uponextra/common/bitmap.fs
. - 16-bit color SPI ST7735S-based displays are implemented as the
<st7735s>
class, in thest7735s
module, inextra/common/st7735s.fs
. These inherit from the<pixmap16>
class, and thus are dependent uponextra/common/pixmap16.s
. - A simple 7x8 ASCII monospace bitmap font is implemented as
a-simple-font
, in thesimple-font
module, inextra/common/simple_font.fs
.a-simple-font
is an instance of the<font>
class and thus is dependent uponextra/common/font.fs
andextra/common/bitmap.fs
. Also,<font>
inextra/common/font.fs
optionally supports drawing on<pixmap16>
instances ofextra/common/pixmap16.fs
has been loaded. - For convenience, all the dependencies necessary for using SSD1306-based displays can be loaded with
extra/common/ssd1306_all.fs
and likewise all the dependencies for using ST7735S-based displays can be loaded withextra/common/st7735s_all.fs
when using zeptocom.js orutils/codeload3.py
.
It should be noted that everything below is specifically not multitasking-safe. One must protect all of the above with a lock or otherwise ensure single access (e.g. by having a single task which manages bitmaps, fonts, and displays).
All operations that can be done on bitmaps are also valid on I2C SSD1306-based displays, and likewise all operations that can be done on 16-bit pixmaps are also valid on 16-bit SPI ST7735S-based displays; unless specific for displays themselves, we will refer solely to bitmaps and pixmaps from here on.
Note that operations on displays update their dirty rectangle to ensure that when displays are updated, a minimal rectangle of pixels will be transmitted to the display transparently. While methods pertaining to dirty states exist for non-display bitmaps, they are merely placeholders to be overridden in displays.
Bitmaps and pixmaps have a coordinate system where (0, 0) is in the upper left-hand corner of the bitmap. In the below operations, the provided column/row pairs are on the upper left-hand corner of the areas of both destination and source bitmaps and pixmaps.
Drawing words defined for bitmaps, in the <bitmap>
class, are as follows:
- Modifying a pixel in a bitmap with a constant value. This is implemented by the word
bitmap::draw-pixel-const
with the signature ( constant column row op dst-bitmap -- ). - Modifying a rectangle in a bitmap with a constant value. This is implemented by the word
bitmap::draw-rect-const
with the signature ( constant column row column-count row-count op dst-bitmap -- ). Note that the order of arguments changed in 1.3.3. - Modifying a rectangle in a bitmap with a rectangle from another bitmap. This is implemented by the word
bitmap::draw-rect
with the signature ( src-column src-row dst-column dst-row column-count row-count op src-bitmap dst-bitmap ). Note that the order of arguments changed in 1.3.3. - Getting the state of a pixel in a bitmap. This is implemented as the word
bitmap::pixel@
with the signature ( column row bitmap -- state ). Note that here state is eithertrue
orfalse
depending on whether the pixel is set or unset, respectively.
In each of these cases, op may be one of:
-
op-set
, for setting pixels -
op-or
, for oring pixels -
op-and
, for anding pixels -
op-bic
, for bit clearing pixels -
op-xor
, for exclusive oring pixels
Additionally, the state of a pixel in a bitmap can be retrieved with bitmap::pixel@
( column row bitmap -- state ) where state is either true or false.
In the case of bitmap::draw-pixel-const
and bitmap::draw-rect-const
, mentioned above, the constant value used is a byte, where the byte used is treated as being eight discrete pixel values for rows from the top of the bitmap modulo eight. For zero constants for all pixels to which they are applied, use $00
; for one constants for all pixels to which they are applied, use $FF
.
Drawing words defined for pixmaps, in the <pixmap16>
class, are as follows:
- Setting a pixel in a pixmap to a constant color. This is implemented by the word
pixmap16::draw-pixel-const
with the signature ( color column row dst-pixmap -- ). - Setting a rectangle in a pixmap to a constant color. This is implemented by the word
pixmap16::draw-rect-const
with the signature ( color column row column-count row-count dst-pixmap -- ). - Setting a rectangle in a pixmap to the contents of a rectangle in another pixmap.. This is implemented by the word
pixmap16::draw-rect
with the signature ( src-column src-row, dst-column dst-row column-count row-count src-pixmap dst-pixmap -- ). - Setting a rectangle in a pixmap to a constant color where the corresponding pixels in a bitmap mask are set to true. This is implemented by the word
pixmap16::draw-rect-const-mask
with the signature ( color mask-column mask-row dst-column dst-row column-count row-count mask-bitmap dst-pixmap -- ). - Setting a rectangle in a pixmap to the contents of a rectangle in another pixmap where the corresponding pixels in a bitmap mask are set to true. This is implemented by the word
pixmap16::draw-rect-mask
with the signature ( mask-column mask-row src-column src-row dst-column dst-row column-count row-count mask-bitmap src-pixmap dst-pixmap -- ).
Additionally, the color of a pixel in a pixmap can be retrieved with pixmap16::pixel@
( column row pixmap -- color ) where color is the 16-bit color of the pixel.
Note that color values are constructed with pixmap16::rgb16
( red green blue -- color ) where red, green, and blue are color components from 0 to 255.
The operations defined for fonts, in the <font>
class, are as follows:
- Modifying a bitmap with a character from a font. This is implemented by the word
font::draw-char
with the signature ( char-index column row op dst-bitmap font -- ). Note that char-index need not be between 0 and 255. - Modifying a bitmap with a string from a font as a horizontal line of characters. This is implemented by the word
font::draw-string
with the signature ( char-addr char-count column row op dst-bitmap font -- ). Note that currently this does not support Unicode; for printing characters outside the range 0 to 255 usedraw-char
mentioned above. - Modifying a pixmap with a character from a font. This is implemented by the word
font::draw-char-to-pixmap16
with the signature ( color char-index column row dst-pixmap font -- ). Note that char-index need not be between 0 and 255. Also note that only pixels that are set in the font are written; unset pixels are left as is. - Modifying a pixmap with a string from a font as a horizontal line of characters. This is implemented by the word
font::draw-string-to-pixmap16
with the signature ( color char-addr char-count column row dst-pixmap font -- ). Note that currently this does not support Unicode; for printing characters outside the range 0 to 255 usedraw-char-to-pixmap16
instead. Also note that only pixels that are set in the font are written; unset pixels are left as is.
Here op has the same meaning as with <bitmap>
methods.
Displays have an update-display
word with the signature ( display -- ), in the case of the I2C SSD1306-based display in the <ssd1306>
class and the SPI ST7735S-based display in the <st7735s>
class, that transmits all the pixels within the display's dirty rectangle in the display's buffer to the display and then clears the display's dirty rectangle. This word must be called to display changes made to a display. Note that it is useful to carry out multiple drawing operations grouped together before calling update-display
not just for performance's sake but also so that the results of the drawing operations are displayed together at once.
I2C SSD1306-based displays also have a word display-contrast
in the <ssd1306>
class with the signature ( contrast display -- ) that takes a contrast value from $00
to $FF
and a display. Note that a contrast value $00
may turn off the display. The default contrast value is $01
.
<bitmap>
instances are initialized with init-object
(in the oo
module) as ( buffer-addr column-count row-count <bitmap>
object-addr -- ). buffer-addr here is an address of an area of memory of at least the size in bytes returned by bitmap-buf-size
(in the bitmap
module), which has the signature ( column-count row-count -- bytes ). column-count and row-count here are the number of columns and rows in the bitmap. object-addr here is the address of the memory where the <bitmap>
instance will be initialized.
<pixmap16>
instances are initialized with init-object
(in the oo
module) as ( buffer-addr column-count row-count <pixmap16>
object-addr -- ). buffer-addr here is the address of an area of memory of at least the size in bytes returned by pixmap16-buf-size
(in the pixmap16
module), which has the signature ( column-count row-count -- bytes ). column-count and row-count here are the number of columns and rows in the pixmap. object-addr here is the address of the memory where the <pixmap16>
instance will be initialized.
<font>
instances are initialized with init-object
as ( buffer-addr default-char-index char-column-count char-row-count min-char-index max-char-index <font>
object-addr -- ). buffer-addr here is an address of an area of memory of at least the size in bytes returned by font-buf-size
(in the font
module), which has the signature ( char-column-count char-row-count min-char-index max-char-index -- bytes ). default-char-index here is the default character index to use when a character index is lower than min-char-index or greater than max-char-index; it must be within this range, inclusive. char-column-count and row-column-count here are the number of columns and rows in each character in the font. min-char-index and max-char-index here are the minimum and maximum character indices, inclusive, defined in the font; all other characters will be replaced by default-char-index. object-addr here is the address of the memory where the <font>
instance will be initialized.
<ssd1306>
instances are initialized with init-object
as ( pin0 pin1 buffer-addr column-count row-count i2c-addr i2c-device <ssd1306>
object-addr -- ). pin0 and pin1 here are GPIO pins matching the I2C peripheral i2c-device which are to be initialized for use with said I2C peripheral. buffer-addr here is an address of an area of memory of at least the size in bytes returned by bitmap-buf-size
(mentioned above). column-count and row-count here are the number of columns and rows in the display, typically 128 and either 64 or 32 respectively; these must match the values provided to bitmap-buf-size
. i2c-addr here is the 7-bit I2C address of the display; typically this is SSD1306_I2C_ADDR
, i.e. $3C
, as defined in the ssd1306
module. i2c-device is the I2C device used for communicating with the SSD1306. object-addr here is the address of memory where the <ssd1306>
instance will be initialized.
<st7735s>
instances are initialized with init-object
as ( din-pin clk-pin dc-pin cs-pin backlight-pin reset-pin buffer-addr column-count row-count spi-device <st7735s>
object-addr -- ). din-pin and clk-pin here are GPIO pins matching the SPI peripheral spi-device for data transfer and clock respectively. dc-pin is a GPIO pin used for messaging in the SPI protocol used by the ST7735S. cs-pin is a GPIO pin used for chip select for SPI. backlight-pin is a GPIO pin for controlling the backlight on the ST7735S-based display. reset-pin is a GPIO pin used for resetting the ST7735S. buffer-addr here is an address of an area of memory of at least the size in bytes returned by pixmap16-buf-size
(mentioned above). column-count and row-count here are the number of columns and rows in the display, e.g. 160 and 80 respectively; these must match the values provided to pixmap16-buf-size
. spi-device is the index of the SPI device used for communicating with the ST7735S. object-addr here is the address of memory where the <st7735s>
instance will be initialized.
a-simple-font
is initialized with init-simple-font
in the simple-font
module. Calling this is necessary before using a-simple-font
.
Here we initialize an I2C SSD1306-based display of size 128x64 pixels tied to GPIO pins 14 and 15 of the RP2040 (pins 19 and 20 on the Raspberry Pi Pico) corresponding to I2C peripheral 1 with a buffer whose size is calculated with bitmap::bitmap-buf-size
. Then we draw "Hello, World!" centered in the middle of the display with font::draw-string
with the operation op-set
. Afterwards, we exclusive-or the rectangle from (32, 0) to (96, 64) with $FF
with bitmap::draw-rect-const
with the operation op-xor
and then update the display.
oo import
bitmap import
ssd1306 import
font import
simple-font import
128 constant my-width
64 constant my-height
my-width my-height bitmap-buf-size constant my-buf-size
my-buf-size 4 align buffer: my-buf
<ssd1306> class-size buffer: my-ssd1306
14 15 my-buf my-width my-height SSD1306_I2C_ADDR 1 <ssd1306> my-ssd1306 init-object
init-simple-font
s" Hello, World!" my-width 2 / 7 13 * 2 / - my-height 2 / 8 2 / - op-set my-ssd1306 a-simple-font draw-string
$FF my-width 4 / 0 my-width 2 / my-height op-xor my-ssd1306 draw-rect-const
my-ssd1306 update-display
Note that the configuration used for the ST7735S here is that found on the Waveshare RP2040-LCD-0.96, and in particular other ST7735S-based displays differ in size, and may be connected up to a board in a different fashion.
Here we initialize an SPI ST7735S-based display of size 160x80 pixels tied to GPIO pin 11 for data input, GPIO pin 10 for clock, GPIO pin 8 for DC, GPIO pin 12 for reset, GPIO pin 9 for chip select, and GPIO pin 25 for backlight on the RP2040 with SPI peripheral 1, with a buffer whose size is calculated with pixmap16::pixmap16-buf-size
. First we draw a rectangle with pixmap16::draw-rect-const
from (40, 0) to (80, 80) with the color (127, 127, 127). Then we draw "Hello, World!" centered in the middle of the display with font::draw-string-to-pixmap16
with the color (0, 255, 0).
oo import
pixmap16 import
st7735s import
font import
simple-font import
160 constant my-width
80 constant my-height
1 constant my-device
11 constant lcd-din
10 constant lcd-clk
8 constant lcd-dc
12 constant lcd-rst
9 constant lcd-cs
25 constant lcd-bl
my-width my-height pixmap16-buf-size constant my-buf-size
my-buf-size 4 align buffer: my-buf
<st7735s> class-size buffer: my-st7735s
lcd-din lcd-clk lcd-dc lcd-cs lcd-bl lcd-rst my-buf my-width my-height my-device <st7735s> my-st7735s init-object
init-simple-font
127 127 127 rgb16 my-width 4 / 0 my-width 2 / my-height my-st7735s draw-rect-const
0 255 0 rgb16 s" Hello, World!" my-width 2 / 7 13 * 2 / - my-height 2 / 8 2 / - my-st7735s a-simple-font draw-string-to-pixmap16
my-st7735s update-display
Here we initialize an I2C SSD1306-based display like before. We then initialize a bitmap my-sprite
with a 4x4 buffer whose size is calculated with bitmap::bitmap-buf-size
, drawing a small circle in it with bitmap::draw-rect-const
with the operation op-set
. Then we exclusive-or the contents of the display with this bitmap at random coordinates with bitmap::draw-rect
with the operation op-xor
256 times, each time updating the display and waiting 50 milliseconds before drawing again.
oo import
bitmap import
ssd1306 import
rng import
128 constant my-width
64 constant my-height
4 constant my-sprite-width
4 constant my-sprite-height
my-width my-height pixmap16::pixmap16-buf-size constant my-buf-size
my-buf-size 4 align buffer: my-buf
<st7735s> class-size buffer: my-st7735s
14 15 my-buf my-width my-height SSD1306_I2C_ADDR 1 <ssd1306> my-ssd1306 init-object
my-sprite-width my-sprite-height bitmap-buf-size constant my-sprite-buf-size
my-sprite-buf-size 4 align buffer: my-sprite-buf
<bitmap> class-size buffer: my-sprite
my-sprite-buf my-sprite-width my-sprite-height <bitmap> my-sprite init-object
$FF 1 0 2 1 op-set my-sprite draw-rect-const
$FF 0 1 4 2 op-set my-sprite draw-rect-const
$FF 1 3 2 1 op-set my-sprite draw-rect-const
: test
256 0 ?do
random my-width my-sprite-width - umod { sprite-col }
random my-height my-sprite-height - umod { sprite-row }
0 0 sprite-col sprite-row my-sprite-width my-sprite-height op-xor my-sprite my-ssd1306 draw-rect
my-ssd1306 update-display
50 ms
loop
;
Afterwards, execute:
test ok
Here we initialize an SPI ST7735S-based display like before. We then initialize a bitmap my-sprite
with a 4x4 buffer whose size is calculated with bitmap::bitmap-buf-size
, drawing a small circle in it with bitmap::draw-rect-const
with the operation op-set
. Then we exclusive-or the contents of the display with this bitmap at random coordinates with pixmap16::draw-rect-const-mask
with the color (0, 255, 0) 256 times, each time updating the display and waiting 50 milliseconds before drawing again.
oo import
bitmap import
pixmap16 import
st7735s import
rng import
160 constant my-width
80 constant my-height
4 constant my-sprite-width
4 constant my-sprite-height
1 constant my-device
11 constant lcd-din
10 constant lcd-clk
8 constant lcd-dc
12 constant lcd-rst
9 constant lcd-cs
25 constant lcd-bl
my-width my-height pixmap16-buf-size constant my-buf-size
my-buf-size 4 align buffer: my-buf
<st7735s> class-size buffer: my-st7735s
lcd-din lcd-clk lcd-dc lcd-cs lcd-bl lcd-rst my-buf my-width my-height my-device <st7735s> my-st7735s init-object
my-sprite-width my-sprite-height bitmap-buf-size constant my-sprite-buf-size
my-sprite-buf-size 4 align buffer: my-sprite-buf
<bitmap> class-size buffer: my-sprite
my-sprite-buf my-sprite-width my-sprite-height <bitmap> my-sprite init-object
$FF 1 0 2 1 op-set my-sprite bitmap::draw-rect-const
$FF 0 1 4 2 op-set my-sprite bitmap::draw-rect-const
$FF 1 3 2 1 op-set my-sprite bitmap::draw-rect-const
: test
256 0 ?do
random my-width my-sprite-width - umod { sprite-col }
random my-height my-sprite-height - umod { sprite-row }
0 255 0 rgb16 0 0 sprite-col sprite-row my-sprite-width my-sprite-height my-sprite my-st7735s pixmap16::draw-rect-const-mask
my-st7735s update-display
50 ms
loop
;
Afterwards, execute:
test ok
Here we create a bitmap of size 7x8 and set it with a single character at (0, 0) with draw-string
with the operation op-set
. Then we use pixel@
to read each pixel that had been written to the bitmap and print it out to the console, showing the character which had been written to the bitmap.
oo import
bitmap import
font import
simple-font import
7 constant width
8 constant height
width height bitmap-buf-size buffer: my-buf
<bitmap> class-size buffer: my-bitmap
my-buf width height <bitmap> my-bitmap init-object
: test
init-simple-font
s" @" 0 0 op-set my-bitmap a-simple-font draw-string
height 0 ?do
cr
width 0 ?do
i j my-bitmap pixel@ if ." #" else ." ." then
loop
loop
;
Afterwards, execute:
test
.####..
#....#.
#.####.
#.#..#.
#.###..
#......
.####..
....... ok