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