tools: rle_encode: Add a new "2-bit" encoding mode.
This commit is contained in:
parent
2b5ebcef72
commit
e2234112ff
1 changed files with 94 additions and 5 deletions
|
@ -45,7 +45,76 @@ def encode(im):
|
||||||
|
|
||||||
return (im.width, im.height, bytes(rle))
|
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):
|
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()
|
pixels = im.load()
|
||||||
|
|
||||||
rle = []
|
rle = []
|
||||||
|
@ -53,7 +122,6 @@ def encode_8bit(im):
|
||||||
px = pixels[0, 0]
|
px = pixels[0, 0]
|
||||||
|
|
||||||
def encode_pixel(px, rl):
|
def encode_pixel(px, rl):
|
||||||
print(rl)
|
|
||||||
px = (px[0] & 0xe0) | ((px[1] & 0xe0) >> 3) | ((px[2] & 0xc0) >> 6)
|
px = (px[0] & 0xe0) | ((px[1] & 0xe0) >> 3) | ((px[2] & 0xc0) >> 6)
|
||||||
|
|
||||||
rle.append(px)
|
rle.append(px)
|
||||||
|
@ -138,17 +206,38 @@ parser.add_argument('--ascii', action='store_true',
|
||||||
help='Run the resulting image(s) through an ascii art decoder')
|
help='Run the resulting image(s) through an ascii art decoder')
|
||||||
parser.add_argument('--c', action='store_true',
|
parser.add_argument('--c', action='store_true',
|
||||||
help='Render the output as C instead of python')
|
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()
|
args = parser.parse_args()
|
||||||
|
|
||||||
for fname in args.files:
|
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:
|
if args.c:
|
||||||
render_c(image, fname)
|
render_c(image, fname)
|
||||||
else:
|
else:
|
||||||
print(f'# 1-bit RLE, generated from {fname}, {len(image[2])} bytes')
|
print(f'# {depth}-bit RLE, generated from {fname}, {len(image[2])} bytes')
|
||||||
print(f'{varname(fname)} = {image}')
|
# Split the bytestring to ensure each line is short enough to be absorbed
|
||||||
print()
|
# 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:
|
if args.ascii:
|
||||||
print()
|
print()
|
||||||
|
|
Loading…
Reference in a new issue