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] 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)