diff options
Diffstat (limited to 'tools')
| -rwxr-xr-x | tools/rle_encode.py | 102 | ||||
| -rwxr-xr-x | tools/wasptool | 48 |
2 files changed, 131 insertions, 19 deletions
diff --git a/tools/rle_encode.py b/tools/rle_encode.py index a938c76..64b920e 100755 --- a/tools/rle_encode.py +++ b/tools/rle_encode.py @@ -1,5 +1,8 @@ #!/usr/bin/env python3 +# SPDX-License-Identifier: LGPL-3.0-or-later +# Copyright (C) 2020 Daniel Thompson + import argparse import sys import os.path @@ -42,7 +45,76 @@ def encode(im): 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() + + rle = [] + rl = 0 + px = pixels[0, 0] + palette = [0, 0xfc, 0x2d, 0xff] + next_color = 1 + + def encode_pixel(px, rl): + nonlocal next_color + px = (px[0] & 0xe0) | ((px[1] & 0xe0) >> 3) | ((px[2] & 0xc0) >> 6) + 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) + + 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_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 = [] @@ -50,7 +122,6 @@ def encode_8bit(im): px = pixels[0, 0] def encode_pixel(px, rl): - print(rl) px = (px[0] & 0xe0) | ((px[1] & 0xe0) >> 3) | ((px[2] & 0xc0) >> 6) rle.append(px) @@ -135,17 +206,38 @@ 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() for fname in args.files: - image = encode(Image.open(fname)) + if args.eightbit: + image = encode_8bit(Image.open(fname)) + depth = 8 + elif args.twobit: + image = encode_2bit(Image.open(fname)) + depth = 2 + else: + image = encode(Image.open(fname)) + depth = 1 if args.c: render_c(image, fname) else: - print(f'# 1-bit RLE, generated from {fname}, {len(image[2])} bytes') - print(f'{varname(fname)} = {image}') - print() + print(f'# {depth}-bit RLE, generated from {fname}, {len(image[2])} bytes') + # Split the bytestring to ensure each line is short enough to be absorbed + # on the target if needed. + #print(f'{varname(fname)} = {image}') + (x, y, pixels) = image + extra_indent = ' ' * args.indent + print(f'{extra_indent}{varname(fname)} = (\n{extra_indent} {x}, {y},') + for i in range(0, len(pixels), 16): + print(f'{extra_indent} {pixels[i:i+16]}') + print(f'{extra_indent})') if args.ascii: print() diff --git a/tools/wasptool b/tools/wasptool index 72e88bc..698cc6c 100755 --- a/tools/wasptool +++ b/tools/wasptool @@ -1,9 +1,7 @@ #!/usr/bin/python3 -# -# SPDX-License-Identifier: MIT +# SPDX-License-Identifier: LGPL-3.0-or-later # Copyright (c) 2020 Daniel Thompson -# import argparse import random @@ -13,7 +11,23 @@ import time import string import sys +def pbar(iterable, quiet=False): + step = 100 / len(iterable) + for i, v in enumerate(iterable): + if not quiet: + percent = round(step * i, 1) + bar = int(percent) // 2 + print(f'[{"="*bar}{"-"*(50-bar)}] {percent}%', end='\r', flush=True) + yield v + if not quiet: + print(f'[{"="*50}] 100% ') + def sync(c): + """Stop the watch and synchronize with the command prompt. + + Sending a random print ensure the final export (of the prompt) + does not accidentally match a previously issued prompt. + """ tag = ''.join([random.choice(string.ascii_uppercase) for i in range(6)]) c.send('\x03') @@ -23,11 +37,20 @@ def sync(c): c.expect('>>> ') def unsync(c): - # Set the watch running again - c.sendline('wasp.run()') + """Set the watch running again. + + There must be an expect (or a sleep) since if we kill the subordinate + process too early then the sendline will not have completed. + """ + c.sendline('wasp.system.run()') + c.expect('Watch is running, use Ctrl-C to stop') + c.send('\x18') def paste(c, f, verbose=False): docstring = False + + tosend = [] + for ln in f.readlines(): ln = ln.rstrip() @@ -43,12 +66,11 @@ def paste(c, f, verbose=False): if ln.lstrip().startswith('#'): continue + tosend.append(ln) - c.sendline(ln) - c.expect('=== ') - - if not verbose: - print('.', end='', flush=True) + for ln in pbar(tosend, verbose): + c.sendline(ln) + c.expect('=== ') def handle_eval(c, cmd): verbose = bool(c.logfile) @@ -69,14 +91,13 @@ def handle_exec(c, fname): with open(fname) as f: if not verbose: - print(f'Preparing to run {fname} ...', end='', flush=True) + print(f'Preparing to run {fname}:') c.send('\x05') c.expect('=== ') paste(c, f, verbose) - print(' done') c.logfile = sys.stdout c.send('\x04') c.expect('>>> ') @@ -101,14 +122,13 @@ def handle_upload(c, fname): with open(fname) as f: if not verbose: - print(f'Uploading {fname} ...', end='', flush=True) + print(f'Uploading {fname}:') c.sendline(f'upload("{os.path.basename(fname)}")') c.expect('=== ') paste(c, f, verbose) c.send('\x04') - print(' done') c.expect('>>> ') if __name__ == '__main__': |
