Skip to content

Commit

Permalink
✨ RLE Custom Bootscreen
Browse files Browse the repository at this point in the history
  • Loading branch information
thinkyhead committed Nov 12, 2023
1 parent 235ad4d commit a441508
Show file tree
Hide file tree
Showing 3 changed files with 173 additions and 4 deletions.
25 changes: 24 additions & 1 deletion Marlin/src/lcd/dogm/marlinui_DOGM.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -127,13 +127,36 @@ bool MarlinUI::detected() { return true; }
#else
const u8g_pgm_uint8_t * const bmp = (u8g_pgm_uint8_t*)pgm_read_ptr(&custom_bootscreen_animation[frame]);
#endif
#elif ENABLED(CUSTOM_BOOTSCREEN_RLE)
uint8_t bmp[CUSTOM_BOOTSCREEN_BMP_BYTEWIDTH * CUSTOM_BOOTSCREEN_BMPHEIGHT];
uint8_t *bmp_rle = (uint8_t*)custom_start_bmp_rle;
#else
const u8g_pgm_uint8_t * const bmp = custom_start_bmp;
#endif

UNUSED(frame);

u8g.drawBitmapP(left, top, CUSTOM_BOOTSCREEN_BMP_BYTEWIDTH, CUSTOM_BOOTSCREEN_BMPHEIGHT, bmp);
#if ENABLED(CUSTOM_BOOTSCREEN_RLE)

uint8_t bits = 0; // Persist the last fetched bitmap byte
for (uint8_t *dst = bmp; dst < bmp + sizeof(bmp);) {
uint8_t count = *bmp_rle++; // Get the count byte
const bool uniq = bool(count & 0x80); // >= 128 is a distinct run; < 128 is a repeat run
count = (count & 0x7F) + 1; // Actual count is 7-bit plus 1
bool getbyte = true; // Get at least one bitmap byte
while (count--) { // Emit 'count' bytes
if (getbyte) {
getbyte = uniq; // Keep getting bitmap bytes if not RLE
bits = *bmp_rle++; // Bitmap byte
}
*dst++ = bits;
}
}

#endif // CUSTOM_BOOTSCREEN_RLE

u8g.TERN(CUSTOM_BOOTSCREEN_RLE, drawBitmap, drawBitmapP)
(left, top, CUSTOM_BOOTSCREEN_BMP_BYTEWIDTH, CUSTOM_BOOTSCREEN_BMPHEIGHT, bmp);

#if ENABLED(CUSTOM_BOOTSCREEN_INVERTED)
if (frame == 0) {
Expand Down
6 changes: 3 additions & 3 deletions buildroot/share/scripts/rle16_compress_cpp_image_data.py
Original file line number Diff line number Diff line change
Expand Up @@ -67,10 +67,10 @@ def rle_encode(data):
i += 1
rsize = 1
for j in range(i, len(data)):
if v != data[j]: break;
if v != data[j]: break
i += 1
rsize += 1
if rsize >= 128: break;
if rsize >= 128: break

# If the run is one, add to the distinct values
if rsize == 1: distinct.append(v)
Expand Down Expand Up @@ -131,7 +131,7 @@ def rle_emit(ofile, arrname, rledata, rawsize):

if len(sys.argv) <= 2:
print("Utility to compress Marlin RGB565 TFT data to RLE16 format.")
print("Reads the existing Marlin RGB565 cpp file and generates a new file with the additional RLE16 data.")
print("Reads a Marlin RGB565 cpp file and generate a new file with the additional RLE16 data.")
print("Usage: rle16_compress_cpp_image_data.py INPUT_FILE.cpp OUTPUT_FILE.cpp")
exit(1)

Expand Down
146 changes: 146 additions & 0 deletions buildroot/share/scripts/rle_compress_bitmap.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,146 @@
#!/usr/bin/env python3
#
# RLE compress a Marlin mono DOGM bitmap.
# Input: An existing Marlin Marlin mono DOGM bitmap .cpp or .h file.
# Output: A new file with the original and compressed data.
#
# Usage: rle_compress_bitmap.py INPUT_FILE OUTPUT_FILE
#
import sys,struct
import re

def addCompressedData(input_file, output_file):
ofile = open(output_file, 'wt')

c_data_section = False
c_skip_data = False
c_footer = False
datatype = "uint8_t"
bytewidth = 16
raw_data = []
rle_value = []
rle_count = []
arrname = ''

line = input_file.readline()
while line:
if not c_footer:
if not c_skip_data: ofile.write(line)

mat = re.match(r'CUSTOM_BOOTSCREEN_BMPWIDTH\s+(\d+)', line)
if mat: bytewidth = int(mat[1]) / 16

if "};" in line:
c_skip_data = False
c_data_section = False
c_footer = True

if c_data_section:
cleaned = re.sub(r"\s|,|\n", "", line)
mat = re.match(r'(0b|B)[01]{8}', cleaned)
if mat:
as_list = cleaned.split(mat[1])
as_list.pop(0)
raw_data += [int(x, 2) for x in as_list]
else:
as_list = cleaned.split("0x")
as_list.pop(0)
raw_data += [int(x, 16) for x in as_list]

mat = re.match(r'const (uint\d+_t|unsigned char)', line)
if mat:
# e.g.: const unsigned char custom_start_bmp[] PROGMEM = {
datatype = mat[0]
if "_rle" in line:
c_skip_data = True
else:
c_data_section = True
arrname = line.split('[')[0].split(' ')[-1]
print("Found data array", arrname)

line = input_file.readline()

input_file.close()

#
# RLE (run length) encoding
# Convert data from raw mono bitmap to a simple run-length-encoded format.
# - Each sequence begins with a count byte N.
# - If the high bit is set in N the run contains N & 0x7F + 1 unique bytes.
# - Otherwise it repeats the following byte N + 1 times.
#
def rle_encode(data):
warn = "This may take a while" if len(data) > 300000 else ""
print("Compressing image data...", warn)
rledata = []
distinct = []
i = 0
while i < len(data):
v = data[i]
i += 1
rsize = 1
for j in range(i, len(data)):
if v != data[j]: break
i += 1
rsize += 1
if rsize >= 128: break

# If the run is one, add to the distinct values
if rsize == 1: distinct.append(v)

# If distinct length >= 127, or the repeat run is 2 or more,
# store the distinct run.
nr = len(distinct)
if nr and (nr >= 128 or rsize > 1 or i >= len(data)):
rledata += [(nr - 1) | 0x80] + distinct
distinct = []

# If the repeat run is 2 or more, store the repeat run.
if rsize > 1: rledata += [rsize - 1, v]

return rledata

def append_byte(data, byte, cols=bytewidth):
if data == '': data = ' '
data += ('0x{0:02X}, '.format(byte)) # 6 characters
if len(data) % (cols * 6 + 2) == 0: data = data.rstrip() + "\n "
return data

def rle_emit(ofile, arrname, rledata, rawsize):
i = 0
outstr = ''
size = 0
while i < len(rledata):
rval = rledata[i]
i += 1
if rval & 0x80:
count = (rval & 0x7F) + 1
outstr = append_byte(outstr, rval)
size += 1
for j in range(count):
outstr = append_byte(outstr, rledata[i + j])
size += 1
i += count
else:
outstr = append_byte(outstr, rval)
outstr = append_byte(outstr, rledata[i])
i += 1
size += 2

outstr = outstr.rstrip()[:-1]
ofile.write("\n// Saves %i bytes\n%s %s_rle[%d] PROGMEM = {\n%s\n};\n" % (rawsize - size, datatype, arrname, size, outstr))

# Encode the data, write it out, close the file
rledata = rle_encode(raw_data)
rle_emit(ofile, arrname, rledata, len(raw_data))
ofile.close()

if len(sys.argv) <= 2:
print('Usage: rle_compress_bitmap.py INPUT_FILE OUTPUT_FILE')
exit(1)

output_cpp = sys.argv[2]
inname = sys.argv[1].replace('//', '/')
input_cpp = open(inname)
print("Processing", inname, "...")
addCompressedData(input_cpp, output_cpp)

0 comments on commit a441508

Please sign in to comment.