1
0
Fork 0

draw565: Switch to a different palette for RLE 2-bit images

This is an incompatible change... older 2-bit images will need to be
re-encoded to display correctly.
pull/34/head
Daniel Thompson 2020-05-18 22:17:51 +01:00
parent d9bdb0c82f
commit 7f6b1b9059
5 changed files with 181 additions and 65 deletions

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.0 KiB

After

Width:  |  Height:  |  Size: 6.8 KiB

View File

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

View File

@ -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'

View File

@ -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:

View File

@ -21,10 +21,10 @@ bomb = (
app = (
b'\x02'
b'`@'
b'\x1e@md<d<d;f?X\xec2\xf0/'
b'\x1e@\x81d<d<d;f?X\xec2\xf0/'
b'\xf2-\xf4,\xc3.\xc3,\xc3.\xc3,\xc3.\xc3,'
b'\xc3.\xc3,\xc3.\xc3,\xc3\x0c\x80\xfc\x83\x10\xc0]'
b'\xc3\x0c@\xffC,C\n\x87\x0c\xc7\nC,C\t'
b'\xc3.\xc3,\xc3.\xc3,\xc3\x0c\x80\xd2\x83\x10\xc0C'
b'\xc3\x0c@\xd7C,C\n\x87\x0c\xc7\nC,C\t'
b'\x83\x02\x84\n\xc4\x02\xc3\tC,C\x08\x82\x07\x82\x08'
b'\xc2\x07\xc2\x08C,C\x07\x82\t\x82\x06\xc2\t\xc2\x07'
b'C,C\x06\x82\x0b\x82\x04\xc2\x0b\xc2\x06C,C\x06'
@ -36,7 +36,7 @@ app = (
b'C+D\x08\x82\t\x82\x04\xc2\t\xc2\x08C*E\t'
b'\x8c\x04\xcc\tC*E\n\x8b\x04\xcb\nC*E.'
b'C*E.C*E.C*E.C*E\n'
b'\x80\xe9\x8b\x04\xc0o\xcb\nC+D\t\x8c\x04\xcc\t'
b'\x80\xbb\x8b\x04\xc0X\xcb\nC+D\t\x8c\x04\xcc\t'
b'C,C\x08\x82\t\x82\x04\xc2\t\xc2\x08C,C\x07'
b'\x82\n\x82\x04\xc2\n\xc2\x07C,C\x06\x82\x0b\x82\x04'
b'\xc2\x0b\xc1\x07C,C\x06\x82\x0b\x82\x04\xc2\x0b\xc2\x06'
@ -49,14 +49,14 @@ app = (
b'C,C\n\x86\x0e\xc6\nC,C\x0c\x83\x10\xc3\x0c'
b'C,C.C,C.C,C.C,C.'
b'C,C.C,t-r/p2l?X@'
b'mf;d<d<d\x1e'
b'\x81f;d<d<d\x1e'
)
# 2-bit RLE, generated from res/clock_icon.png, 288 bytes
clock = (
b'\x02'
b'`@'
b'?\xff\xff\xff\xff\xff\xff\x8e@\xb6F\r\xc6!E\x0b'
b'?\xff\xff\xff\xff\xff\xff\x8e@\xacF\r\xc6!E\x0b'
b'\xc8\x0cH\x0b\xca\x1dI\x07\xcc\nH\n\xcc\x1bK\x06'
b'\xce\x08C\x02C\n\xc4\x05\xc4\x1aD\x03D\x06\xc2\x08'
b'\xc4\rC\t\xc4\x07\xc3\x19D\x05D\x10\xc4\x0cC\t'
@ -76,62 +76,62 @@ clock = (
b'E\t\xcf?\xff\xff\xff\xff\xff\xff\xff\xffk'
)
# 2-bit RLE, generated from res/settings_icon.png, 472 bytes
# 2-bit RLE, generated from res/settings_icon.png, 468 bytes
settings = (
b'\x02'
b'`@'
b'\x1e@md<d<d;f?X\xec2\xf0/'
b'\xf2-\xf4,\xc3.\xc3,\xc3.\xc3,\xc3.\xc3,'
b'\xc3\x14\x80\xb7\x86\x14\xc3,\xc3\x13\x88\x13\xc3,\xc3\x13'
b'\x88\x13\xc3,\xc3\x12\x83\x04\x83\x12\xc3,\xc3\n\x84\x04'
b'\x83\x04\x83\x04\x84\n\xc3,\xc3\t\x86\x01\x85\x04\x85\x01'
b'\x86\t\xc3,\xc3\x08\x8c\x05\x8d\x08\xc3,\xc3\x07\x84\x02'
b'\x86\x08\x86\x02\x84\x07\xc3,\xc3\x07\x83\x05\x81\x0e\x81\x05'
b'\x83\x07\xc3,\xc3\x07\x83\x1a\x83\x07\xc3,\xc3\x07\x84\x18'
b'\x84\x07\xc3,\xc3\x08\x83\n\xc0o\xc4\n\x83\x08@\xff'
b'C,C\t\x83\x06\xca\x06\x83\tC,C\x08\x83\x06'
b'\xcc\x06\x83\x08C,C\x08\x83\x05\xc4\x06\xc4\x05\x83\x08'
b'C+D\x06\x85\x04\xc4\x08\xc4\x04\x85\x06C*E\x04'
b'\x86\x05\xc3\n\xc3\x05\x86\x04C*E\x03\x87\x05\xc2\x0c'
b'\xc2\x05\x87\x03C*E\x03\x83\x08\xc3\x0c\xc3\x08\x83\x03'
b'C*E\x03\x83\x08\xc3\x0c\xc3\x08\x83\x03C*E\x03'
b'\x83\x08\xc3\x0c\xc3\x08\x83\x03C*E\x03\x83\x08\xc3\x0c'
b'\xc3\x08\x83\x03C*E\x03\x86\x06\xc2\x0c\xc2\x05\x87\x03'
b'C+D\x04\x86\x05\xc3\n\xc3\x05\x86\x04C,C\x05'
b'\x86\x04\xc4\x08\xc4\x04\x85\x06C,C\x08\x83\x05\xc4\x06'
b'\xc4\x05\x83\x08C,C\x08\x83\x06\xcc\x06\x83\x08C,'
b'C\t\x83\x06\xca\x06\x83\tC,C\x08\x83\n\xc4\n'
b'\x83\x08C,C\x07\x84\x18\x84\x07C,C\x07\x83\x1a'
b'\x83\x07C,C\x07\x83\x05\x81\x0e\x81\x05\x83\x07C,'
b'C\x07\x84\x03\x85\x08\x86\x02\x84\x07C,C\x08\x8d\x04'
b'\x8d\x08C,C\t\x86\x01\x85\x04\x85\x01\x86\tC,'
b'C\n\x84\x04\x83\x04\x83\x04\x84\nC,C\x12\x83\x04'
b'\x83\x12C,C\x13\x88\x13C,C\x13\x88\x13C,'
b'C\x14\x86\x14C,C.C,C.C,C.'
b'C,t-r/p2l?X\x80m\xa6;\xa4'
b'<\xa4<\xa4\x1e'
b'\x1e\xa4<\xa4<\xa4;\xa6?X\xec2\xf0/\xf2-'
b'\xf4,\xc3.\xc3,\xc3.\xc3,\xc3.\xc3,\xc3\x14'
b'@\xadF\x14\xc3,\xc3\x13H\x13\xc3,\xc3\x13H\x13'
b'\xc3,\xc3\x12C\x04C\x12\xc3,\xc3\nD\x04C\x04'
b'C\x04D\n\xc3,\xc3\tF\x01E\x04E\x01F\t'
b'\xc3,\xc3\x08L\x05M\x08\xc3,\xc3\x07D\x02F\x08'
b'F\x02D\x07\xc3,\xc3\x07C\x05A\x0eA\x05C\x07'
b'\xc3,\xc3\x07C\x1aC\x07\xc3,\xc3\x07D\x18D\x07'
b'\xc3,\xc3\x08C\n\x80\xdd\x84\nC\x08\xc3,\xc3\t'
b'C\x06\x8a\x06C\t\xc3,\xc3\x08C\x06\x8c\x06C\x08'
b'\xc3,\xc3\x08C\x05\x84\x06\x84\x05C\x08\xc3+\xc4\x06'
b'E\x04\x84\x08\x84\x04E\x06\xc3*\xc5\x04F\x05\x83\n'
b'\x83\x05F\x04\xc3*\xc5\x03G\x05\x82\x0c\x82\x05G\x03'
b'\xc3*\xc5\x03C\x08\x83\x0c\x83\x08C\x03\xc3*\xc5\x03'
b'C\x08\x83\x0c\x83\x08C\x03\xc3*\xc5\x03C\x08\x83\x0c'
b'\x83\x08C\x03\xc3*\xc5\x03C\x08\x83\x0c\x83\x08C\x03'
b'\xc3*\xc5\x03F\x06\x82\x0c\x82\x05G\x03\xc3+\xc4\x04'
b'F\x05\x83\n\x83\x05F\x04\xc3,\xc3\x05F\x04\x84\x08'
b'\x84\x04E\x06\xc3,\xc3\x08C\x05\x84\x06\x84\x05C\x08'
b'\xc3,\xc3\x08C\x06\x8c\x06C\x08\xc3,\xc3\tC\x06'
b'\x8a\x06C\t\xc3,\xc3\x08C\n\x84\nC\x08\xc3,'
b'\xc3\x07D\x18D\x07\xc3,\xc3\x07C\x1aC\x07\xc3,'
b'\xc3\x07C\x05A\x0eA\x05C\x07\xc3,\xc3\x07D\x03'
b'E\x08F\x02D\x07\xc3,\xc3\x08M\x04M\x08\xc3,'
b'\xc3\tF\x01E\x04E\x01F\t\xc3,\xc3\nD\x04'
b'C\x04C\x04D\n\xc3,\xc3\x12C\x04C\x12\xc3,'
b'\xc3\x13H\x13\xc3,\xc3\x13H\x13\xc3,\xc3\x14F\x14'
b'\xc3,\xc3.\xc3,\xc3.\xc3,\xc3.\xc3,\xf4-'
b'\xf2/\xf02\xec?X\xc0\xdb\xe6;\xe4<\xe4<\xe4'
b'\x1e'
)
# 2-bit RLE, generated from res/torch_icon.png, 247 bytes
# 2-bit RLE, generated from res/torch_icon.png, 245 bytes
torch = (
b'\x02'
b'`@'
b'?\xff\xff\xff\xff\xff\xff\xff&\xc6\x0c@\xfdB?\n'
b'?\xff\xff\xff\xff\xff\xff\xff&\xc6\x0c@\xd4B?\n'
b'\xca\tD?\x08\xc4\x06\xc2\x07F?\x07\xc3\x07\xc2\x06'
b'H?\x06\xc2\n\xc1\x04G\xc2A8\xc5\x08\xc2\t\xc2'
b'\x02F\xc3C7\xc7\x06\xc2\x0b\xc1F\xc2F\x1e\xe8\n'
b'\xc2C\xc3H\x1d\xe8\x0c\xc1N\x1d\xc2%\xc1\x0b\xc2N'
b'\x1d\xc2%\xc1\x0c\xc1N\x1d\xc2\x04\x80m\x9d\x04\xc1\x0b'
b'\xc2N\x1d\xc2\x06\x81\x03\x81\x03\x81\x03\x81\x03\x81\x03\x81'
b'\x03\x81\x06\xc1\x0c\xc1N\x1d\xc2\x04\x9d\x04\xc1\x0b\xc2C'
b'\xcaA\x1d\xc2\x06\x81\x03\x81\x03\x81\x03\x81\x03\x81\x03\x81'
b'\x03\x81\x06\xc1\x0c\xc1N\x1d\xc2\x04\x9d\x04\xc1\x0b\xc2N'
b'\x1d\xc2%\xc1\x0c\xc1N\x1d\xc2%\xc1\x0b\xc2N\x1d\xe8'
b'\x0c\xc1N\x1e\xe8\n\xc2C\xc3H?\x05\xc2\x0b\xc1F'
b'\xc2F?\x06\xc2\t\xc2\x02F\xc3C?\x06\xc2\n\xc1'
b'\x04G\xc2A?\x07\xc3\x07\xc2\x06H?\x08\xc4\x06\xc2'
b'\x07F?\n\xca\tD?\r\xc6\x0cB?\xff\xff\xff'
b'\xff\xff\xff\x95'
b'\x1d\xc2%\xc1\x0c\xc1N\x1d\xc2\x04\x9d\x04\xc1\x0b\xc2N'
b'\x1d\xc2\x06\x81\x03\x81\x03\x81\x03\x81\x03\x81\x03\x81\x03\x81'
b'\x06\xc1\x0c\xc1N\x1d\xc2\x04\x9d\x04\xc1\x0b\xc2C\xcaA'
b'\x1d\xc2\x06\x81\x03\x81\x03\x81\x03\x81\x03\x81\x03\x81\x03\x81'
b'\x06\xc1\x0c\xc1N\x1d\xc2\x04\x9d\x04\xc1\x0b\xc2N\x1d\xc2'
b'%\xc1\x0c\xc1N\x1d\xc2%\xc1\x0b\xc2N\x1d\xe8\x0c\xc1'
b'N\x1e\xe8\n\xc2C\xc3H?\x05\xc2\x0b\xc1F\xc2F'
b'?\x06\xc2\t\xc2\x02F\xc3C?\x06\xc2\n\xc1\x04G'
b'\xc2A?\x07\xc3\x07\xc2\x06H?\x08\xc4\x06\xc2\x07F'
b'?\n\xca\tD?\r\xc6\x0cB?\xff\xff\xff\xff\xff'
b'\xff\x95'
)
# 1-bit RLE, generated from res/up_arrow.png, 16 bytes