rle16_compress_cpp_image_data.py 4.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142
  1. #!/usr/bin/env python3
  2. #
  3. # Utility to compress Marlin RGB565 TFT data to RLE16 format.
  4. # Reads the existing Marlin RGB565 cpp file and generates a new file with the additional RLE16 data.
  5. #
  6. # Usage: rle16_compress_cpp_image_data.py INPUT_FILE.cpp OUTPUT_FILE.cpp
  7. #
  8. import sys, struct
  9. import re
  10. def addCompressedData(input_file, output_file):
  11. ofile = open(output_file, 'wt')
  12. c_data_section = False
  13. c_skip_data = False
  14. c_footer = False
  15. raw_data = []
  16. rle_value = []
  17. rle_count = []
  18. arrname = ''
  19. line = input_file.readline()
  20. while line:
  21. if not c_footer:
  22. if not c_skip_data: ofile.write(line)
  23. if "};" in line:
  24. c_skip_data = False
  25. c_data_section = False
  26. c_footer = True
  27. if c_data_section:
  28. cleaned = re.sub(r"\s|,|\n", "", line)
  29. as_list = cleaned.split("0x")
  30. as_list.pop(0)
  31. raw_data += [int(x, 16) for x in as_list]
  32. if "const uint" in line:
  33. # e.g.: const uint16_t marlin_logo_480x320x16[153600] = {
  34. if "_rle16" in line:
  35. c_skip_data = True
  36. else:
  37. c_data_section = True
  38. arrname = line.split('[')[0].split(' ')[-1]
  39. print("Found data array", arrname)
  40. line = input_file.readline()
  41. input_file.close()
  42. #
  43. # RLE16 (run length 16) encoding
  44. # Convert data from from raw RGB565 to a simple run-length-encoded format for each word of data.
  45. # - Each sequence begins with a count byte N.
  46. # - If the high bit is set in N the run contains N & 0x7F + 1 unique words.
  47. # - Otherwise it repeats the following word N + 1 times.
  48. # - Each RGB565 word is stored in MSB / LSB order.
  49. #
  50. def rle_encode(data):
  51. warn = "This may take a while" if len(data) > 300000 else ""
  52. print("Compressing image data...", warn)
  53. rledata = []
  54. distinct = []
  55. i = 0
  56. while i < len(data):
  57. v = data[i]
  58. i += 1
  59. rsize = 1
  60. for j in range(i, len(data)):
  61. if v != data[j]: break
  62. i += 1
  63. rsize += 1
  64. if rsize >= 128: break
  65. # If the run is one, add to the distinct values
  66. if rsize == 1: distinct.append(v)
  67. # If distinct length >= 127, or the repeat run is 2 or more,
  68. # store the distinct run.
  69. nr = len(distinct)
  70. if nr and (nr >= 128 or rsize > 1 or i >= len(data)):
  71. rledata += [(nr - 1) | 0x80] + distinct
  72. distinct = []
  73. # If the repeat run is 2 or more, store the repeat run.
  74. if rsize > 1: rledata += [rsize - 1, v]
  75. return rledata
  76. def append_byte(data, byte, cols=240):
  77. if data == '': data = ' '
  78. data += ('0x{0:02X}, '.format(byte)) # 6 characters
  79. if len(data) % (cols * 6 + 2) == 0: data = data.rstrip() + "\n "
  80. return data
  81. def rle_emit(ofile, arrname, rledata, rawsize):
  82. col = 0
  83. i = 0
  84. outstr = ''
  85. size = 0
  86. while i < len(rledata):
  87. rval = rledata[i]
  88. i += 1
  89. if rval & 0x80:
  90. count = (rval & 0x7F) + 1
  91. outstr = append_byte(outstr, rval)
  92. size += 1
  93. for j in range(count):
  94. outstr = append_byte(outstr, rledata[i + j] >> 8)
  95. outstr = append_byte(outstr, rledata[i + j] & 0xFF)
  96. size += 2
  97. i += count
  98. else:
  99. outstr = append_byte(outstr, rval)
  100. outstr = append_byte(outstr, rledata[i] >> 8)
  101. outstr = append_byte(outstr, rledata[i] & 0xFF)
  102. i += 1
  103. size += 3
  104. outstr = outstr.rstrip()[:-1]
  105. ofile.write("\n// Saves %i bytes\nconst uint8_t %s_rle16[%d] = {\n%s\n};\n" % (rawsize - size, arrname, size, outstr))
  106. (w, h, d) = arrname.split("_")[-1].split('x')
  107. ofile.write("\nconst tImage MarlinLogo{0}x{1}x16 = MARLIN_LOGO_CHOSEN({0}, {1});\n".format(w, h))
  108. ofile.write("\n#endif // HAS_GRAPHICAL_TFT && SHOW_BOOTSCREEN\n".format(w, h))
  109. # Encode the data, write it out, close the file
  110. rledata = rle_encode(raw_data)
  111. rle_emit(ofile, arrname, rledata, len(raw_data) * 2)
  112. ofile.close()
  113. if len(sys.argv) <= 2:
  114. print("Utility to compress Marlin RGB565 TFT data to RLE16 format.")
  115. print("Reads a Marlin RGB565 cpp file and generates a new file with the additional RLE16 data.")
  116. print("Usage: rle16_compress_cpp_image_data.py INPUT_FILE.cpp OUTPUT_FILE.cpp")
  117. exit(1)
  118. output_cpp = sys.argv[2]
  119. inname = sys.argv[1].replace('//', '/')
  120. input_cpp = open(inname)
  121. print("Processing", inname, "...")
  122. addCompressedData(input_cpp, output_cpp)