diff --git a/Marlin/src/lcd/dogm/marlinui_DOGM.cpp b/Marlin/src/lcd/dogm/marlinui_DOGM.cpp index d1f685134590..7b81b3637d5f 100644 --- a/Marlin/src/lcd/dogm/marlinui_DOGM.cpp +++ b/Marlin/src/lcd/dogm/marlinui_DOGM.cpp @@ -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); diff --git a/buildroot/share/scripts/rle16_compress_cpp_image_data.py b/buildroot/share/scripts/rle16_compress_cpp_image_data.py index 5a6b32d78bf5..aeb7e65d3a01 100755 --- a/buildroot/share/scripts/rle16_compress_cpp_image_data.py +++ b/buildroot/share/scripts/rle16_compress_cpp_image_data.py @@ -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) @@ -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) diff --git a/buildroot/share/scripts/rle_compress_bitmap.py b/buildroot/share/scripts/rle_compress_bitmap.py new file mode 100755 index 000000000000..8f5436fa2e37 --- /dev/null +++ b/buildroot/share/scripts/rle_compress_bitmap.py @@ -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)