diff --git a/res/app_icon.png b/res/app_icon.png index 574f75f..438cded 100644 Binary files a/res/app_icon.png and b/res/app_icon.png differ diff --git a/tools/rle_encode.py b/tools/rle_encode.py index 36ada32..6dccbd7 100755 --- a/tools/rle_encode.py +++ b/tools/rle_encode.py @@ -8,6 +8,109 @@ 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]) @@ -62,15 +165,18 @@ def encode_2bit(im): assert(im.width <= 255) assert(im.height <= 255) + full_palette = ReverseCLUT(clut8_rgb888) + rle = [] rl = 0 px = pixels[0, 0] - palette = [0, 0xfc, 0x2d, 0xff] + # black, grey25, grey50, white + palette = [0, 254, 219, 215] next_color = 1 def encode_pixel(px, rl): nonlocal next_color - px = (px[0] & 0xe0) | ((px[1] & 0xe0) >> 3) | ((px[2] & 0xc0) >> 6) + px = full_palette((px[0] << 16) + (px[1] << 8) + px[2]) if px not in palette: rle.append(next_color << 6) rle.append(px) diff --git a/wasp/apps/gameoflife.py b/wasp/apps/gameoflife.py index 080c214..80c5528 100644 --- a/wasp/apps/gameoflife.py +++ b/wasp/apps/gameoflife.py @@ -118,11 +118,11 @@ def game_of_life(b, xmax: int, ymax: int, nb): icon = ( b'\x02' b'`@' - b'?\xff\xff\xee@\xd7B\x02B\x02B?\x16L?\x15' + b'?\xff\xff\xee@\xf8B\x02B\x02B?\x16L?\x15' b'L?\x16B\x02B\x02B?\x1bB?\x1eD?\x1d' - b'D?\x1eB?\x17\x80\xbe\x82\x02\x82\x06\x82\x02\x82?' + b'D?\x1eB?\x17\x80\xee\x82\x02\x82\x06\x82\x02\x82?' b'\x0e\x88\x04\x88?\r\x88\x04\x88?\x0e\x82\x02\x82\x06B' - b'\x02\x82?\x03\xc0\x97\xc2\x02\xc2\x02\xc2\x02\xc2\x02\xc2\x02' + b'\x02\x82?\x03\xc0\x89\xc2\x02\xc2\x02\xc2\x02\xc2\x02\xc2\x02' b'\xc2\x02\xc2\x02\xc2\x02\xc2\x02\xc2\x02\xc25\xec4\xec5' b'\xc2\x02\xc2\x02\xc2\x02\xc2\x02\xc2\x02\xc2\x02\xc2\x02\xc2\x02' b'\xc2\x02\xc2\x02\xc2*B\x02B\x12\xc2\x06B\x06\xc2\x12' diff --git a/wasp/draw565.py b/wasp/draw565.py index e7914b8..21b23ed 100644 --- a/wasp/draw565.py +++ b/wasp/draw565.py @@ -35,15 +35,25 @@ def _bitblit(bitbuf, pixels, bgfg: int, count: int): pxp += 1 @micropython.viper -def _expand_rgb(eightbit: int) -> int: - r = eightbit >> 5 - r = (r << 2) | (r >> 1) - g = (eightbit >> 2) & 7 - g *= 9 - b = eightbit & 3 - b *= 10 +def _clut8_rgb565(i: int) -> int: + 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 (r << 11) | (g << 5) | b + return rgb565 @micropython.viper def _fill(mv, color: int, count: int, offset: int): @@ -183,7 +193,7 @@ class Draw565(object): sx *= 2 sy //= 2 - palette = array.array('H', (0, 0xfffe, 0x7bef, 0xffff)) + palette = array.array('H', (0, 0x4a69, 0x7bef, 0xffff)) next_color = 1 rl = 0 buf = memoryview(display.linebuffer)[0:2*sx] @@ -204,7 +214,7 @@ class Draw565(object): if op >= 255: continue else: - palette[next_color] = _expand_rgb(op) + palette[next_color] = _clut8_rgb565(op) if next_color < 3: next_color += 1 else: diff --git a/wasp/icons.py b/wasp/icons.py index 9bfa92a..dec982a 100644 --- a/wasp/icons.py +++ b/wasp/icons.py @@ -21,10 +21,10 @@ bomb = ( app = ( b'\x02' b'`@' - b'\x1e@md