Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

✨ RLE Custom Bootscreen #26419

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
37 changes: 35 additions & 2 deletions Marlin/src/lcd/dogm/marlinui_DOGM.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -127,14 +127,47 @@ 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(COMPACT_CUSTOM_BOOTSCREEN)
#define BMPSIZE (CUSTOM_BOOTSCREEN_BMP_BYTEWIDTH * CUSTOM_BOOTSCREEN_BMPHEIGHT)
uint8_t bmp[BMPSIZE];
uint8_t *bmp_rle = (uint8_t*)custom_start_bmp_rle;
#else
const u8g_pgm_uint8_t * const bmp = custom_start_bmp;
#endif

UNUSED(frame);
#if ENABLED(COMPACT_CUSTOM_BOOTSCREEN)

uint8_t *dst = (uint8_t*)bmp;

auto rle_nybble = [&](const uint16_t i) {
const uint8_t b = bmp_rle[i / 2];
return (i & 1 ? b & 0xF : b >> 4);
};

uint8_t workbyte = 0, bitstate = rle_nybble(0) << 7;
uint16_t inindex = 1, outindex = 0;
while (outindex < BMPSIZE * 8) {
int16_t c = rle_nybble(inindex++);
if (c == 15) {
c = 16 * rle_nybble(inindex) + rle_nybble(inindex + 1) + 15; // From 16 to 270
inindex += 2;
}
while (c-- >= 0) {
const uint8_t bitind = outindex & 7,
bitval = bitstate >> bitind;
workbyte |= bitval;
if (bitind == 7) { *dst++ = workbyte; workbyte = 0; }
outindex++;
}
bitstate ^= 0x80;
}

u8g.drawBitmapP(left, top, CUSTOM_BOOTSCREEN_BMP_BYTEWIDTH, CUSTOM_BOOTSCREEN_BMPHEIGHT, bmp);
#endif // COMPACT_CUSTOM_BOOTSCREEN

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

UNUSED(frame);
#if ENABLED(CUSTOM_BOOTSCREEN_INVERTED)
if (frame == 0) {
u8g.setColorIndex(1);
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 generates 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
183 changes: 183 additions & 0 deletions buildroot/share/scripts/rle_compress_bitmap.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,183 @@
#!/usr/bin/env python3
#
# Bitwise 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')

datatype = "uint8_t"
bytewidth = 16
raw_data = []
arrname = ''

c_data_section = False ; c_skip_data = False ; c_footer = False
while True:
line = input_file.readline()
if not line: break

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]) + 7) // 8

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)

input_file.close()

#print("\nRaw Bitmap Data", raw_data)

#
# Bitwise RLE (run length) encoding
# Convert data from raw mono bitmap to a bitwise run-length-encoded format.
# - The first nybble is the starting bit state. Changing this nybble inverts the bitmap.
# - The following bytes provide the runs for alternating on/off bits.
# - A value of 0-14 encodes a run of 1-15.
# - A value of 16 indicates a run of 16-270 calculated using the next two bytes.
#
def bitwise_rle_encode(data):
warn = "This may take a while" if len(data) > 300000 else ""
print("Compressing image data...", warn)

def get_bit(data, n): return 1 if (data[n // 8] & (0x80 >> (n & 7))) else 0

bitslen = len(data) * 8
bitstate = get_bit(data, 0)
rledata = [ bitstate ]

i = 0
runlen = -1
while i <= bitslen:
if i < bitslen: b = get_bit(data, i)
runlen += 1
if bitstate != b or i == bitslen:
if i > 11 * 56 * 8: print(f'Bit change at index {i} with runlen={runlen}')
if runlen >= 16:
rledata += [ 15, runlen // 16 - 1, runlen % 16 ]
if i > 11 * 56 * 8: print(f'Storing {[ 15, runlen // 16 - 1, runlen % 16 ]}')
else:
rledata += [ runlen - 1 ]
if i > 11 * 56 * 8: print(f'Storing {[ runlen ]}')
bitstate ^= 1
runlen = 0
i += 1

print("\nrledata", rledata)

encoded = []
ri = 0
rlen = len(rledata)
while ri < rlen:
v = rledata[ri] << 4
if (ri < rlen - 1): v |= rledata[ri + 1]
encoded += [ v ]
ri += 2

print("\nencoded", encoded)
return encoded

def bitwise_rle_decode(rledata, invert=0):
expanded = []
for n in rledata: expanded += [ n >> 4, n & 0xF ]

decoded = []
bitstate = 0 ; workbyte = 0 ; outindex = 0
i = 0
while i < len(expanded):
c = expanded[i]
i += 1

if i == 1: bitstate = c ; continue

if c == 15:
c = 16 * expanded[i] + expanded[i + 1] + 15
i += 2

for _ in range(c, -1, -1):
bitval = 0x80 >> (outindex & 7)
if bitstate: workbyte |= bitval
if bitval == 1:
decoded += [ workbyte ]
workbyte = 0
outindex += 1

bitstate ^= 1

print("\nDecoded RLE data:")
pretty = [ '{0:08b}'.format(v) for v in decoded ]
rows = [pretty[i:i+bytewidth] for i in range(0, len(pretty), bytewidth)]
for row in rows: print(f"{''.join(row)}")

return decoded

def rle_emit(ofile, arrname, rledata, rawsize):

outstr = ''
rows = [ rledata[i:i+16] for i in range(0, len(rledata), 16) ]
for i in range(0, len(rows)):
rows[i] = [ '0x{0:02X}'.format(v) for v in rows[i] ]
outstr += f" {', '.join(rows[i])},\n"

outstr = outstr[:-2]
size = len(rledata)
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 = bitwise_rle_encode(raw_data)
rle_emit(ofile, arrname, rledata, len(raw_data))
ofile.close()

# Validate that code properly compressed (and decompressed) the data
checkdata = bitwise_rle_decode(rledata)
badindex = -1
for i in range(0, len(checkdata)):
if raw_data[i] != checkdata[i]:
badindex = i
break
if badindex >= 0: print(f'Data mismatch at byte {badindex}')

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('//', '/')
try:
input_cpp = open(inname)
print("Processing", inname, "...")
addCompressedData(input_cpp, output_cpp)
except OSError:
print("Can't find input file", inname)
Loading