summaryrefslogtreecommitdiff
path: root/tools
diff options
context:
space:
mode:
Diffstat (limited to 'tools')
-rwxr-xr-xtools/rle_encode.py102
-rwxr-xr-xtools/wasptool48
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__':