From 3fae26498fdf60800bac3ef5013a1bccc7146a62 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jean-Fran=C3=A7ois=20Milants?= Date: Sat, 2 Jan 2021 20:51:40 +0100 Subject: [PATCH 1/3] Code cleaning (add comments, use #defines instead of constants, rename variables,...). --- .../include/pinetime_boot/pinetime_boot.h | 19 ++++- .../include/pinetime_boot/pinetime_factory.h | 2 + libs/pinetime_boot/src/display.c | 75 ++++++++++--------- libs/pinetime_boot/src/pinetime_boot.c | 11 +-- 4 files changed, 65 insertions(+), 42 deletions(-) diff --git a/libs/pinetime_boot/include/pinetime_boot/pinetime_boot.h b/libs/pinetime_boot/include/pinetime_boot/pinetime_boot.h index 4187f22..e407e4d 100644 --- a/libs/pinetime_boot/include/pinetime_boot/pinetime_boot.h +++ b/libs/pinetime_boot/include/pinetime_boot/pinetime_boot.h @@ -25,21 +25,36 @@ extern "C" { // Expose the types and functions below to C functions. #endif +// Colors +#define BLACK 0 +#define WHITE 0xffff +#define RED 0xF800 +#define BLUE 0x001F +#define GREEN 0x07E0 + /// Init the display and render the boot graphic. Called by sysinit() during startup, defined in pkg.yml. void pinetime_boot_init(void); /// Write a converted graphic file to SPI Flash int pinetime_boot_write_image(void); -/// Display the image in SPI Flash to ST7789 display controller +/// Display the boot logo to ST7789 display controller int pinetime_boot_display_image(void); + +/// Display the boot logo to ST7789 display controller using 2 colors. The first x lines (x = colorLine) +/// will be drawn in color1, the rest in color2. +int pinetime_boot_display_image_colors(uint16_t color1, uint16_t color2, uint8_t colorLine); + +/// Display the bootloader version to ST7789 display controller int pinetime_version_image(void); + +/// Clear the display void pinetime_clear_screen(void); /// Check whether the watch button is pressed void pinetime_boot_check_button(void); -int pinetime_boot_display_image_colors(uint16_t color1, uint16_t color2, uint8_t colorLine); + #ifdef __cplusplus } diff --git a/libs/pinetime_boot/include/pinetime_boot/pinetime_factory.h b/libs/pinetime_boot/include/pinetime_boot/pinetime_factory.h index 5dfe95b..6261947 100644 --- a/libs/pinetime_boot/include/pinetime_boot/pinetime_factory.h +++ b/libs/pinetime_boot/include/pinetime_boot/pinetime_factory.h @@ -1,6 +1,8 @@ #ifndef __PINETIME_FACTORY_H__ #define __PINETIME_FACTORY_H__ +/// Copy the recovery firmware from the external SPI Flash memory to the secondary slot. +/// It'll be installed in the primary slot by MCUBoot. void restore_factory(void); diff --git a/libs/pinetime_boot/src/display.c b/libs/pinetime_boot/src/display.c index b5baa45..d2a7c86 100644 --- a/libs/pinetime_boot/src/display.c +++ b/libs/pinetime_boot/src/display.c @@ -46,7 +46,7 @@ // ST7789 Colour Settings #define INVERTED 1 // Display colours are inverted -#define RGB 1 // Display colours are RGB +#define RGB 1 // Display colours are RGB // Flash Device for Image #define FLASH_DEVICE 1 // 0 for Internal Flash ROM, 1 for External SPI Flash @@ -105,42 +105,44 @@ static int write_data(const uint8_t *data, uint16_t len); static int transmit_spi(const uint8_t *data, uint16_t len); /// Buffer for reading flash and writing to display -static uint8_t flash_buffer[240*2]; +static uint8_t flash_buffer[COL_COUNT * BYTES_PER_PIXEL]; -int pinetime_display_image_colors(struct imgInfo* info, int posx, int posy, uint16_t color1, uint16_t color2, uint8_t colorLine) { +/// Display the image described by info to the ST7789 display controller using 2 colors. The first x lines (x = colorLine) +/// will be drawn in color1, the rest in color2. +int pinetime_display_image_colors(struct imgInfo* info, int posX, int posY, uint16_t color1, uint16_t color2, uint8_t colorLine) { int rc; int y = 0; - uint16_t bp = 0; - uint16_t fg = 0xffff; - const uint16_t bg = 0; - uint16_t color = bg; - uint16_t trueColor = color; - for (int i=0; idataSize; i++) { - uint8_t rl = info->data[i]; - while (rl) { - flash_buffer[bp] = trueColor >> 8; - flash_buffer[bp + 1] = trueColor & 0xff; - bp += 2; - rl -= 1; - - if (bp >= (info->width*2)) { - rc = set_window(posx, y+posy, posx+info->width-1, y+posy); assert(rc == 0); + uint16_t bufferIndex = 0; + uint8_t isBackground = 1; + const uint16_t backgroundColor = BLACK; + uint16_t trueColor = backgroundColor; + + for (int i = 0; i < info->dataSize; i++) { + uint8_t runLength = info->data[i]; + while (runLength) { + flash_buffer[bufferIndex] = trueColor >> 8; + flash_buffer[bufferIndex + 1] = trueColor & 0xff; + bufferIndex += BYTES_PER_PIXEL; + runLength -= 1; + + if (bufferIndex >= (info->width * BYTES_PER_PIXEL)) { + rc = set_window(posX, y + posY, posX + info->width - 1, y + posY); assert(rc == 0); // Write Pixels (RAMWR): st7735_lcd::draw() → set_pixel() rc = write_command(RAMWR, NULL, 0); assert(rc == 0); - rc = write_data(flash_buffer, info->width*2); assert(rc == 0); - bp = 0; + rc = write_data(flash_buffer, info->width * BYTES_PER_PIXEL); assert(rc == 0); + bufferIndex = 0; y += 1; } } - if (color == bg) { - color = fg; + if (isBackground) { + isBackground = 0; trueColor = (y < colorLine) ? color1 : color2; } else { - color = bg; - trueColor = color; + isBackground = 1; + trueColor = backgroundColor; } if(y >= info->height) break; @@ -148,24 +150,25 @@ int pinetime_display_image_colors(struct imgInfo* info, int posx, int posy, uint return 0; } +/// Clear the display void pinetime_clear_screen(void) { int rc = 0; - for(int i = 0 ; i < 240*2; i++) { + for(int i = 0 ; i < COL_COUNT * BYTES_PER_PIXEL; i++) { flash_buffer[i] = 0; } - for(int i = 0; i < 240; i++) { - rc = set_window(0, i, 239, i); assert(rc == 0); + for(int i = 0; i < ROW_COUNT; i++) { + rc = set_window(0, i, COL_COUNT-1, i); assert(rc == 0); rc = write_command(RAMWR, NULL, 0); assert(rc == 0); - rc = write_data(flash_buffer, 240*2); assert(rc == 0); + rc = write_data(flash_buffer, COL_COUNT * BYTES_PER_PIXEL); assert(rc == 0); } } -int pinetime_display_image(struct imgInfo* info, int posx, int posy) { - return pinetime_display_image_colors(info, posx, posy, 0xffff, 0xffff, 0); +/// Display the image described by info at position (posX, posY) using default color (black and white) +int pinetime_display_image(struct imgInfo* info, int posX, int posY) { + return pinetime_display_image_colors(info, posX, posY, WHITE, WHITE, 0); } -/// Display the image in SPI Flash to ST7789 display controller. -/// Derived from https://github.com/lupyuen/pinetime-rust-mynewt/blob/main/logs/spi-non-blocking.log +/// Display the boot logo to ST7789 display controller int pinetime_boot_display_image(void) { console_printf("Displaying boot logo...\n"); console_flush(); @@ -175,14 +178,16 @@ int pinetime_boot_display_image(void) { return pinetime_display_image(&bootLogoInfo, 0, 0); } +/// Display the boot logo to ST7789 display controller using 2 colors. The first x lines (x = colorLine) +/// will be drawn in color1, the rest in color2. int pinetime_boot_display_image_colors(uint16_t color1, uint16_t color2, uint8_t colorLine) { return pinetime_display_image_colors(&bootLogoInfo, 0, 0, color1, color2, colorLine); } - +/// Display the bootloader version to ST7789 display controller on the bottom of the display (centered) int pinetime_version_image(void) { console_printf("Displaying version image...\n"); console_flush(); - return pinetime_display_image(&versionInfo, 120 - (versionInfo.width/2), 240 - (versionInfo.height)); + return pinetime_display_image(&versionInfo, (COL_COUNT/2) - (versionInfo.width/2), ROW_COUNT - (versionInfo.height)); } /// Set the ST7789 display window to the coordinates (left, top), (right, bottom) @@ -316,7 +321,7 @@ static int transmit_spi(const uint8_t *data, uint16_t len) { // Select the device hal_gpio_write(DISPLAY_CS, 0); // Send the data - int rc = hal_spi_txrx(DISPLAY_SPI, + int rc = hal_spi_txrx(DISPLAY_SPI, (void *) data, // TX Buffer NULL, // RX Buffer (don't receive) len); // Length diff --git a/libs/pinetime_boot/src/pinetime_boot.c b/libs/pinetime_boot/src/pinetime_boot.c index 7eb2dd2..64484e3 100644 --- a/libs/pinetime_boot/src/pinetime_boot.c +++ b/libs/pinetime_boot/src/pinetime_boot.c @@ -74,16 +74,16 @@ void pinetime_boot_init(void) { } if(i % 8 == 0) { - uint16_t color = 0xF800; + uint16_t color = RED; if (button_samples < 3000 * 64 * 2) { - color = 0x07E0; + color = GREEN; } else if (button_samples < 3000 * 64 * 4) { - color = 0x001F; + color = BLUE; } else { - color = 0xF800; + color = RED; } - pinetime_boot_display_image_colors(0xffff, color, 240 - ((i / 8) * 6) + 1); + pinetime_boot_display_image_colors(WHITE, color, 240 - ((i / 8) * 6) + 1); } } console_printf("Waited 5 seconds (%d)\n", (int)button_samples); console_flush(); @@ -109,6 +109,7 @@ void pinetime_boot_init(void) { } } +/// Configure and start the watchdog void setup_watchdog() { NRF_WDT->CONFIG &= ~(WDT_CONFIG_SLEEP_Msk << WDT_CONFIG_SLEEP_Pos); NRF_WDT->CONFIG |= (WDT_CONFIG_HALT_Run << WDT_CONFIG_SLEEP_Pos); From a6aeafcbb8fea1f0b115b3074f36118ed3d71e0a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jean-Fran=C3=A7ois=20Milants?= Date: Sun, 3 Jan 2021 18:16:20 +0100 Subject: [PATCH 2/3] Prevent watchdog reset during bootloader operations by feeding the wdt during transfer of the recovery firmware and during mcuboot processing. This is needed because the bootloader is NOT disabled by a softreset (NVIC_systemReset()) or by a reset via the debug interface. --- libs/pinetime_boot/src/pinetime_boot.c | 2 ++ libs/pinetime_boot/src/pinetime_factory.c | 3 +++ targets/nrf52_boot/syscfg.yml | 6 +++++- 3 files changed, 10 insertions(+), 1 deletion(-) diff --git a/libs/pinetime_boot/src/pinetime_boot.c b/libs/pinetime_boot/src/pinetime_boot.c index 64484e3..0411f22 100644 --- a/libs/pinetime_boot/src/pinetime_boot.c +++ b/libs/pinetime_boot/src/pinetime_boot.c @@ -29,6 +29,7 @@ #include "pinetime_boot/pinetime_boot.h" #include "pinetime_boot/pinetime_factory.h" #include "pinetime_boot/pinetime_delay.h" +#include #define PUSH_BUTTON_IN 13 // GPIO Pin P0.13: PUSH BUTTON_IN #define PUSH_BUTTON_OUT 15 // GPIO Pin P0.15/TRACEDATA2: PUSH BUTTON_OUT @@ -71,6 +72,7 @@ void pinetime_boot_init(void) { } if(i % 64 == 0) { console_printf("step %d - %d\n", (i / (64)) + 1, (int)button_samples); console_flush(); + hal_watchdog_tickle(); } if(i % 8 == 0) { diff --git a/libs/pinetime_boot/src/pinetime_factory.c b/libs/pinetime_boot/src/pinetime_factory.c index f954cf5..804db06 100644 --- a/libs/pinetime_boot/src/pinetime_factory.c +++ b/libs/pinetime_boot/src/pinetime_factory.c @@ -1,6 +1,7 @@ #include "pinetime_boot/pinetime_factory.h" #include #include "os/mynewt.h" +#include // Flash Device for Image #define FLASH_DEVICE 1 // 0 for Internal Flash ROM, 1 for External SPI Flash @@ -18,9 +19,11 @@ void restore_factory(void) { int rc; for (uint32_t erased = 0; erased < FACTORY_SIZE; erased += 0x1000) { rc = hal_flash_erase_sector(FLASH_DEVICE, FACTORY_OFFSET_DESTINATION + erased); + hal_watchdog_tickle(); } for(uint32_t offset = 0; offset < FACTORY_SIZE; offset += BATCH_SIZE) { + hal_watchdog_tickle(); rc = hal_flash_read(FLASH_DEVICE, FACTORY_OFFSET_SOURCE + offset, flash_buffer, BATCH_SIZE); assert(rc == 0); rc = hal_flash_write(FLASH_DEVICE, FACTORY_OFFSET_DESTINATION + offset, flash_buffer, BATCH_SIZE); diff --git a/targets/nrf52_boot/syscfg.yml b/targets/nrf52_boot/syscfg.yml index cb4a9d5..8bd6eb7 100644 --- a/targets/nrf52_boot/syscfg.yml +++ b/targets/nrf52_boot/syscfg.yml @@ -49,4 +49,8 @@ syscfg.vals: OS_SYSVIEW_TRACE_CALLOUT: 0 # Disable trace of callouts OS_SYSVIEW_TRACE_EVENTQ: 0 # Disable trace of event queues OS_SYSVIEW_TRACE_MUTEX: 0 # Disable trace of mutex - OS_SYSVIEW_TRACE_SEM: 0 # Disable trace of semaphores \ No newline at end of file + OS_SYSVIEW_TRACE_SEM: 0 # Disable trace of semaphores + BOOTUTIL_FEED_WATCHDOG: 1 # Enable watchdog feeding while performing a swap upgrade + SANITY_INTERVAL: 1000 # The interval (in milliseconds) at which the sanity checks should run, should be at least 200ms prior to watchdog + WATCHDOG_INTERVAL: 2000 # The interval (in milliseconds) at which the watchdog should reset if not tickled, in ms + From a2d2f93bd6fcf3f2f637aa2acf2f472fca6eff73 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jean-Fran=C3=A7ois=20Milants?= Date: Sun, 3 Jan 2021 20:37:56 +0100 Subject: [PATCH 3/3] Set version to 0.0.1. Add tool to convert png to RLE encoded buffer from wasp-os. --- libs/pinetime_boot/src/graphic.h | 55 ++-- libs/pinetime_boot/src/version-0.0.1.png | Bin 0 -> 768 bytes libs/pinetime_boot/src/version-0.0.1.xcf | Bin 0 -> 2725 bytes tools/rle_encode.py | 379 +++++++++++++++++++++++ 4 files changed, 407 insertions(+), 27 deletions(-) create mode 100644 libs/pinetime_boot/src/version-0.0.1.png create mode 100644 libs/pinetime_boot/src/version-0.0.1.xcf create mode 100755 tools/rle_encode.py diff --git a/libs/pinetime_boot/src/graphic.h b/libs/pinetime_boot/src/graphic.h index 2f9e9d5..1591b55 100644 --- a/libs/pinetime_boot/src/graphic.h +++ b/libs/pinetime_boot/src/graphic.h @@ -110,40 +110,41 @@ struct imgInfo bootLogoInfo = { bootLogoRle }; -// /home/jf/nrf52/Pinetime/tools/rle_encode.py /home/jf/nrf52/pinetime-rust-mynewt/libs/pinetime_boot/src/version-1.2.3.png --c +// /home/jf/nrf52/Pinetime/tools/rle_encode.py /home/jf/nrf52/pinetime-rust-mynewt/libs/pinetime_boot/src/version-0.0.1.png --c static const uint8_t versionRle[] = { - 0x59, 0x56, 0x2, 0x1, 0x3, 0xc, 0x3, 0xa, 0x1, 0x38, 0x2, 0x1, - 0x3, 0xc, 0x3, 0x9, 0x2, 0x14, 0x6, 0x13, 0x6, 0x5, 0x2, 0x2, - 0x3, 0xb, 0x2, 0xa, 0x2, 0x13, 0x9, 0xf, 0xa, 0x3, 0x2, 0x2, - 0x3, 0xa, 0x3, 0x9, 0x3, 0x12, 0x4, 0x3, 0x4, 0xd, 0x5, 0x2, - 0x5, 0x2, 0x2, 0x2, 0x3, 0xa, 0x3, 0x6, 0x6, 0x11, 0x3, 0x6, - 0x4, 0xc, 0x3, 0x6, 0x3, 0x2, 0x2, 0x3, 0x3, 0x9, 0x2, 0x6, - 0x7, 0x11, 0x3, 0x7, 0x3, 0xc, 0x2, 0x8, 0x2, 0x2, 0x2, 0x3, - 0x3, 0x8, 0x3, 0x6, 0x7, 0x11, 0x2, 0x9, 0x2, 0xb, 0x3, 0x8, - 0x3, 0x1, 0x2, 0x4, 0x2, 0x8, 0x3, 0xb, 0x2, 0x11, 0x2, 0x9, - 0x2, 0xb, 0x3, 0x8, 0x2, 0x2, 0x2, 0x4, 0x3, 0x7, 0x2, 0xc, - 0x2, 0x1b, 0x3, 0x15, 0x3, 0x2, 0x2, 0x4, 0x3, 0x6, 0x3, 0xc, - 0x2, 0x1b, 0x3, 0x14, 0x4, 0x2, 0x2, 0x5, 0x3, 0x5, 0x3, 0xc, - 0x2, 0x1a, 0x3, 0x12, 0x5, 0x4, 0x2, 0x5, 0x3, 0x5, 0x2, 0xd, - 0x2, 0x19, 0x4, 0x12, 0x6, 0x3, 0x2, 0x5, 0x3, 0x4, 0x3, 0xd, - 0x2, 0x17, 0x4, 0x18, 0x3, 0x2, 0x2, 0x6, 0x3, 0x3, 0x3, 0xd, - 0x2, 0x15, 0x5, 0x1a, 0x3, 0x1, 0x2, 0x6, 0x3, 0x3, 0x2, 0xe, - 0x2, 0x13, 0x5, 0x1c, 0x3, 0x1, 0x2, 0x6, 0x3, 0x2, 0x3, 0xe, - 0x2, 0x12, 0x4, 0x13, 0x3, 0x8, 0x3, 0x1, 0x2, 0x7, 0x3, 0x1, - 0x3, 0xe, 0x2, 0x12, 0x3, 0x14, 0x3, 0x8, 0x3, 0x1, 0x2, 0x7, - 0x3, 0x1, 0x2, 0xf, 0x2, 0x11, 0x3, 0x15, 0x3, 0x8, 0x3, 0x1, - 0x2, 0x7, 0x6, 0xf, 0x2, 0x11, 0x2, 0x17, 0x3, 0x6, 0x4, 0x1, - 0x2, 0x8, 0x5, 0xf, 0x2, 0xa, 0x3, 0x3, 0xe, 0x5, 0x3, 0x4, - 0x5, 0x2, 0x5, 0x2, 0x2, 0x8, 0x4, 0x10, 0x2, 0xa, 0x3, 0x3, - 0xe, 0x5, 0x3, 0x5, 0xa, 0x3, 0x2, 0x9, 0x3, 0x10, 0x2, 0xa, - 0x3, 0x3, 0xe, 0x5, 0x3, 0x7, 0x6, 0x5, 0x2, 0x56, 0x59, + 0x59, 0x56, 0x2, 0x56, 0x2, 0x56, 0x2, 0x56, 0x2, 0x2, 0x2, 0xd, + 0x2, 0x6, 0x5, 0x13, 0x5, 0x12, 0x4, 0xa, 0x2, 0x3, 0x2, 0xb, + 0x2, 0x5, 0x9, 0xf, 0x9, 0xe, 0x6, 0xa, 0x2, 0x3, 0x2, 0xb, + 0x2, 0x5, 0x2, 0x5, 0x2, 0xf, 0x2, 0x5, 0x2, 0xe, 0x2, 0x2, + 0x2, 0xa, 0x2, 0x3, 0x3, 0x9, 0x3, 0x4, 0x2, 0x7, 0x2, 0xd, + 0x2, 0x7, 0x2, 0x11, 0x2, 0xa, 0x2, 0x4, 0x2, 0x9, 0x2, 0x5, + 0x2, 0x7, 0x2, 0xd, 0x2, 0x7, 0x2, 0x11, 0x2, 0xa, 0x2, 0x4, + 0x2, 0x9, 0x2, 0x4, 0x2, 0x9, 0x2, 0xb, 0x2, 0x9, 0x2, 0x10, + 0x2, 0xa, 0x2, 0x5, 0x2, 0x7, 0x2, 0x5, 0x2, 0x9, 0x2, 0xb, + 0x2, 0x9, 0x2, 0x10, 0x2, 0xa, 0x2, 0x5, 0x2, 0x7, 0x2, 0x5, + 0x2, 0x9, 0x2, 0xb, 0x2, 0x9, 0x2, 0x10, 0x2, 0xa, 0x2, 0x5, + 0x3, 0x5, 0x3, 0x5, 0x2, 0x9, 0x2, 0xb, 0x2, 0x9, 0x2, 0x10, + 0x2, 0xa, 0x2, 0x6, 0x2, 0x5, 0x2, 0x6, 0x2, 0x9, 0x2, 0xb, + 0x2, 0x9, 0x2, 0x10, 0x2, 0xa, 0x2, 0x6, 0x2, 0x5, 0x2, 0x6, + 0x2, 0x9, 0x2, 0xb, 0x2, 0x9, 0x2, 0x10, 0x2, 0xa, 0x2, 0x7, + 0x2, 0x3, 0x2, 0x7, 0x2, 0x9, 0x2, 0xb, 0x2, 0x9, 0x2, 0x10, + 0x2, 0xa, 0x2, 0x7, 0x2, 0x3, 0x2, 0x7, 0x2, 0x9, 0x2, 0xb, + 0x2, 0x9, 0x2, 0x10, 0x2, 0xa, 0x2, 0x7, 0x2, 0x3, 0x2, 0x8, + 0x2, 0x7, 0x2, 0xd, 0x2, 0x7, 0x2, 0x11, 0x2, 0xa, 0x2, 0x8, + 0x2, 0x1, 0x2, 0x9, 0x2, 0x7, 0x2, 0xd, 0x2, 0x7, 0x2, 0x11, + 0x2, 0xa, 0x2, 0x8, 0x2, 0x1, 0x2, 0xa, 0x2, 0x5, 0x2, 0x6, + 0x2, 0x7, 0x2, 0x5, 0x2, 0x6, 0x2, 0xa, 0x2, 0xa, 0x2, 0x8, + 0x5, 0xa, 0x9, 0x6, 0x2, 0x7, 0x9, 0x6, 0x2, 0x6, 0xa, 0x6, + 0x2, 0x9, 0x3, 0xd, 0x5, 0x8, 0x2, 0x9, 0x5, 0x8, 0x2, 0x6, + 0xa, 0x6, 0x2, 0x56, 0x2, 0x56, 0x59, }; + struct imgInfo versionInfo = { 88, 26, - 299, + 295, versionRle }; diff --git a/libs/pinetime_boot/src/version-0.0.1.png b/libs/pinetime_boot/src/version-0.0.1.png new file mode 100644 index 0000000000000000000000000000000000000000..5cd87f946e607a293df128daf0c596e015f7b230 GIT binary patch literal 768 zcmV+b1ONPqP)EX>4Tx04R}tkv&MmP!xqvQ>7vmk#-Pq$WWauNELC^DionYs1;guFnQ@8G%+M8 zE{=k0!NH%!s)LKOt`4q(Aov5~=H{g6A|>9J6k5c1;qgAsyXWxUeSpxYGR^852Q=L_ zGpVGQ%dd!`R|F751OrIO%ra&rDGlHHx~FccyExDC@B6d*)Plu;fJi*U4AUlFC!X50 z4bJ<-QC5~!;&b9LlP*a7$aTfzH_j!O1)do-vza;KD6v@TV5Ngu+0=+9iDRm!Q@)V% zSmnIMS*zAq>z@3D;ex)r#C2N3NMQkskRU=q4HZ;jBTl=bb^8^S!16O+6ztI4uKS{5* zwb&8RzYSbmw>4!CxZD8-pA6ZQT`5RQC>DYDGy0}H5V-|eSad^gZEa<4bO1wgWnpw> zWFU8GbZ8()Nlj2!fese{007uYL_t(&-tAaH62u?~BaZ+7GJDx;y9mb8o#bvtGY}#$ zGdF<>2~5;>NCPe;umpZL=7k@l;N@8GdTe|Pli#zv_#Q@45S@&*#Bz z$%@~BA6dS!A-;LRHc%9T&5x?hjE(IC6ON{csF!D}jLlCKIC?2p^(qIq7udDK;0PRT zd)b35qOZVL!%o9Z75x7Kv!mWCqU1K<<(jHl`mLQCr2d14Bp^OvDFdh&wDS+ar7c0000p@TF`D+UR%sDxChLZ};0^6b3Uv4ic< zq?^nyKw<-8!3M;REnDvN z`h$T3`4D)H1oAoX6yUn}*#ue#a1oHFfiplGxTHDro&kOb&V?xL4U@@4#yM<Ec*Q-7%9Pz?I87RM}Yc(YjLLdC)mFZGj&nX8C&VwXdo`{Y#So zZE`?sKkE3aCcbPOIR-b2eWy4@+$ff{sbg@txP;M856^NxUjz7`P(A`)0Lr6sr{C-M z%CEJ?807zSWa|1jJn50CV?Agh?oZHFXUOF|_ac8GQ?3tDZi?AbxjW5>HU%W@SgL?nZp1(UfqNk&oxm<0PN z9S_t9op|@H-u90DyrIKmslstCuI=^)yY{2QWxrglX{@j)G}r~ZC;uu-8kuF7)2zSz z#urtr_45fia5a9=ab@Dm=1pe>_sDUJbzlvx=psPg0nRwB;ygfJ+4Q;aRl4KUiWX3Z z-2nI=HFH}4b5HU!pK+5%%{=Bmp1+1%;&}d9U1RMr{hV(N;Mn%K|KxA#cg%XZAAg{q zv4*`~<8Q;yxpH1+o#ajIbsPIz`76!+T!)`yw6tEj@1S%3_7|M5oqDn9tO0J}0+*iv zn@+pf0G7_DJQf~sUY`$hYi!(>k^X#c`PqF&^^se3%q4u)C0o7wZ!cM4)#UK3&nA5V uU?+pT0`Q)-#oUifwWR$S3^RKjYkyxQbpGaEZ2zj`*G-|*zYG7{`|nSOa&e#l literal 0 HcmV?d00001 diff --git a/tools/rle_encode.py b/tools/rle_encode.py new file mode 100755 index 0000000..ed75342 --- /dev/null +++ b/tools/rle_encode.py @@ -0,0 +1,379 @@ +#!/usr/bin/env python3 + +# SPDX-License-Identifier: LGPL-3.0-or-later +# Copyright (C) 2020 Daniel Thompson + +import argparse +import sys +import os.path +from PIL import Image + +def clut8_rgb888(i): + """Reference CLUT for wasp-os. + + Technically speaking this is not a CLUT because the we lookup the colours + algorithmically to avoid the cost of a genuine CLUT. The palette is + designed to be fairly easy to generate algorithmically. + + The palette includes all 216 web-safe colours together 4 grays and + 36 additional colours that target "gaps" at the brighter end of the web + safe set. There are 11 greys (plus black and white) although two are + fairly close together. + + :param int i: Index (from 0..255 inclusive) into the CLUT + :return: 24-bit colour in RGB888 format + """ + if i < 216: + rgb888 = ( i % 6) * 0x33 + rg = i // 6 + rgb888 += (rg % 6) * 0x3300 + rgb888 += (rg // 6) * 0x330000 + elif i < 252: + i -= 216 + rgb888 = 0x7f + (( i % 3) * 0x33) + rg = i // 3 + rgb888 += 0x4c00 + ((rg % 4) * 0x3300) + rgb888 += 0x7f0000 + ((rg // 4) * 0x330000) + else: + i -= 252 + rgb888 = 0x2c2c2c + (0x101010 * i) + + return rgb888 + +def clut8_rgb565(i): + """RBG565 CLUT for wasp-os. + + This CLUT implements the same palette as :py:meth:`clut8_888` but + outputs RGB565 pixels. + + .. note:: + + This function is unused within this file but needs to be + maintained alongside the reference clut so it is reproduced + here. + + :param int i: Index (from 0..255 inclusive) into the CLUT + :return: 16-bit colour in RGB565 format + """ + if i < 216: + rgb565 = (( i % 6) * 0x33) >> 3 + rg = i // 6 + rgb565 += ((rg % 6) * (0x33 << 3)) & 0x07e0 + rgb565 += ((rg // 6) * (0x33 << 8)) & 0xf800 + elif i < 252: + i -= 216 + rgb565 = (0x7f + (( i % 3) * 0x33)) >> 3 + rg = i // 3 + rgb565 += ((0x4c << 3) + ((rg % 4) * (0x33 << 3))) & 0x07e0 + rgb565 += ((0x7f << 8) + ((rg // 4) * (0x33 << 8))) & 0xf800 + else: + i -= 252 + gr6 = (0x2c + (0x10 * i)) >> 2 + gr5 = gr6 >> 1 + rgb565 = (gr5 << 11) + (gr6 << 5) + gr5 + + return rgb565 + +class ReverseCLUT: + def __init__(self, clut): + l = [] + for i in range(256): + l.append(clut(i)) + self.clut = tuple(l) + self.lookup = {} + + def __call__(self, rgb888): + """Compare rgb888 to every element of the CLUT and pick the + closest match. + """ + if rgb888 in self.lookup: + return self.lookup[rgb888] + + best = 200000 + index = -1 + clut = self.clut + r = rgb888 >> 16 + g = (rgb888 >> 8) & 0xff + b = rgb888 & 0xff + + for i in range(256): + candidate = clut[i] + rd = r - (candidate >> 16) + gd = g - ((candidate >> 8) & 0xff) + bd = b - (candidate & 0xff) + # This is the Euclidian distance (squared) + distance = rd * rd + gd * gd + bd * bd + if distance < best: + best = distance + index = i + + self.lookup[rgb888] = index + #print(f'# #{rgb888:06x} -> #{clut8_rgb888(index):06x}') + return index + +def varname(p): + return os.path.basename(os.path.splitext(p)[0]) + +def encode(im): + pixels = im.load() + + rle = [] + rl = 0 + px = pixels[0, 0] + + def encode_pixel(px, rl): + while rl > 255: + rle.append(255) + rle.append(0) + rl -= 255 + rle.append(rl) + + for y in range(im.height): + for x in range(im.width): + newpx = pixels[x, y] + if newpx == px: + rl += 1 + assert(rl < (1 << 21)) + continue + + # Code the previous run + encode_pixel(px, rl) + + # Start a new run + rl = 1 + px = newpx + + # Handle the final run + encode_pixel(px, rl) + + return (im.width, im.height, bytes(rle)) + +def encode_2bit(im): + """2-bit palette based RLE encoder. + + This encoder has a reprogrammable 2-bit palette. This allows it to encode + arbitrary images with a full 8-bit depth but the 2-byte overhead each time + a new colour is introduced means it is not efficient unless the image is + carefully constructed to keep a good locality of reference for the three + non-background colours. + + The encoding competes well with the 1-bit encoder for small monochrome + images but once run-lengths longer than 62 start to become frequent then + this encoding is about 30% larger than a 1-bit encoding. + """ + pixels = im.load() + assert(im.width <= 255) + assert(im.height <= 255) + + full_palette = ReverseCLUT(clut8_rgb888) + + rle = [] + rl = 0 + px = pixels[0, 0] + # black, grey25, grey50, white + palette = [0, 254, 219, 215] + next_color = 1 + + def encode_pixel(px, rl): + nonlocal next_color + px = full_palette((px[0] << 16) + (px[1] << 8) + px[2]) + if px not in palette: + rle.append(next_color << 6) + rle.append(px) + palette[next_color] = px + next_color += 1 + if next_color >= len(palette): + next_color = 1 + px = palette.index(px) + if rl >= 63: + rle.append((px << 6) + 63) + rl -= 63 + while rl >= 255: + rle.append(255) + rl -= 255 + rle.append(rl) + else: + rle.append((px << 6) + rl) + + # Issue the descriptor + rle.append(2) + rle.append(im.width) + rle.append(im.height) + + for y in range(im.height): + for x in range(im.width): + newpx = pixels[x, y] + if newpx == px: + rl += 1 + assert(rl < (1 << 21)) + continue + + # Code the previous run + encode_pixel(px, rl) + + # Start a new run + rl = 1 + px = newpx + + # Handle the final run + encode_pixel(px, rl) + + return bytes(rle) + +def encode_8bit(im): + """Experimental 8-bit RLE encoder. + + For monochrome images this is about 3x less efficient than the 1-bit + encoder. This encoder is not currently used anywhere in wasp-os and + currently there is no decoder either (so don't assume this code + actually works). + """ + pixels = im.load() + + rle = [] + rl = 0 + px = pixels[0, 0] + + def encode_pixel(px, rl): + px = (px[0] & 0xe0) | ((px[1] & 0xe0) >> 3) | ((px[2] & 0xc0) >> 6) + + rle.append(px) + if rl > 0: + rle.append(px) + rl -= 2 + if rl > (1 << 14): + rle.append(0x80 | ((rl >> 14) & 0x7f)) + if rl > (1 << 7): + rle.append(0x80 | ((rl >> 7) & 0x7f)) + if rl >= 0: + rle.append( rl & 0x7f ) + + for y in range(im.height): + for x in range(im.width): + newpx = pixels[x, y] + if newpx == px: + rl += 1 + assert(rl < (1 << 21)) + continue + + # Code the previous run + encode_pixel(px, rl) + + # Start a new run + rl = 1 + px = newpx + + # Handle the final run + encode_pixel(px, rl) + + return (im.width, im.height, bytes(rle)) + +def render_c(image, fname, indent, depth): + extra_indent = ' ' * indent + if len(image) == 3: + print(f'{extra_indent}// {depth}-bit RLE, generated from {fname}, ' + f'{len(image[2])} bytes') + (x, y, pixels) = image + else: + print(f'{extra_indent}// {depth}-bit RLE, generated from {fname}, ' + f'{len(image)} bytes') + pixels = image + + print(f'{extra_indent}static const uint8_t {varname(fname)}[] = {{') + print(f'{extra_indent} ', end='') + i = 0 + for rl in pixels: + print(f' {hex(rl)},', end='') + + i += 1 + if i == 12: + print(f'\n{extra_indent} ', end='') + i = 0 + print('\n};') + +def render_py(image, fname, indent, depth): + extra_indent = ' ' * indent + if len(image) == 3: + print(f'{extra_indent}# {depth}-bit RLE, generated from {fname}, ' + f'{len(image[2])} bytes') + (x, y, pixels) = image + print(f'{extra_indent}{varname(fname)} = (') + print(f'{extra_indent} {x}, {y},') + else: + print(f'{extra_indent}# {depth}-bit RLE, generated from {fname}, ' + f'{len(image)} bytes') + pixels = image[3:] + print(f'{extra_indent}{varname(fname)} = (') + print(f'{extra_indent} {image[0:1]}') + print(f'{extra_indent} {image[1:3]}') + + # Split the bytestring to ensure each line is short enough to + # be absorbed on the target if needed. + for i in range(0, len(pixels), 16): + print(f'{extra_indent} {pixels[i:i+16]}') + print(f'{extra_indent})') + + +def decode_to_ascii(image): + (sx, sy, rle) = image + data = bytearray(2*sx) + dp = 0 + black = ord('#') + white = ord(' ') + color = black + + for rl in rle: + while rl: + data[dp] = color + data[dp+1] = color + dp += 2 + rl -= 1 + + if dp >= (2*sx): + print(data.decode('utf-8')) + dp = 0 + + if color == black: + color = white + else: + color = black + + # Check the image is the correct length + assert(dp == 0) + +parser = argparse.ArgumentParser(description='RLE encoder tool.') +parser.add_argument('files', nargs='+', + help='files to be encoded') +parser.add_argument('--ascii', action='store_true', + help='Run the resulting image(s) through an ascii art decoder') +parser.add_argument('--c', action='store_true', + help='Render the output as C instead of python') +parser.add_argument('--indent', default=0, type=int, + help='Add extra indentation in the generated code') +parser.add_argument('--2bit', action='store_true', dest='twobit', + help='Generate 2-bit image') +parser.add_argument('--8bit', action='store_true', dest='eightbit', + help='Generate 8-bit image') + +args = parser.parse_args() +if args.eightbit: + encoder = encode_8bit + depth = 8 +elif args.twobit: + encoder = encode_2bit + depth = 2 +else: + encoder = encode + depth =1 + +for fname in args.files: + image = encoder(Image.open(fname)) + + if args.c: + render_c(image, fname, args.indent, depth) + else: + render_py(image, fname, args.indent, depth) + + if args.ascii: + print() + decode_to_ascii(image)