├── Compress.py ├── ESP32_BadApple.ino ├── ESP32_BadApple.jpg ├── LICENSE ├── README.md ├── data └── video.hs ├── heatshrink_LICENSE ├── heatshrink_common.h ├── heatshrink_config.h ├── heatshrink_decoder.cpp └── heatshrink_decoder.h /Compress.py: -------------------------------------------------------------------------------- 1 | from PIL import Image 2 | 3 | def get_compressed(filename): 4 | # Convert to gray and then to 1 bit using custom threshold 5 | im = Image.open(filename).convert(mode="L").point(lambda i: i > 127 and 255) 6 | 7 | imdata = list(im.getdata()) 8 | 9 | imbit = [] 10 | b_count = 0 11 | c8 = 0 12 | for c in imdata[:]: 13 | c8 = c8 << 1 14 | if c != 0: 15 | c8 = c8 | 1 16 | b_count += 1 17 | if b_count == 8: 18 | b_count = 0 19 | imbit.append(c8) 20 | c8 = 0 21 | 22 | 23 | # ENCODE 24 | im_comp = [] 25 | c_m1 = imbit[0] 26 | runlength = 0 27 | # run-length encode only bytes 0 and 255 28 | # use 0x55 as escape sequence for 0 and 0xaa for 255 29 | # afterwards the run-length is encoded into 1 or 2 bytes 30 | # for run-lengths of 1..127 1 byte is used, for run-lengths 31 | # above the highest bit is set to 1 and the MSB is added 32 | # to the second byte. 33 | # run length of 0 is used to replace escape character 34 | for c in imbit[1:]: 35 | if runlength == 0: 36 | if c_m1 in [0, 255]: 37 | if c_m1 == c: 38 | runlength = 2 39 | else: 40 | im_comp.append(c_m1) 41 | else: 42 | # encode directly 43 | im_comp.append(c_m1) 44 | if c_m1 in [0x55, 0xaa]: 45 | im_comp.append(0) 46 | else: 47 | if c_m1 == c: 48 | runlength += 1 49 | else: 50 | if runlength == 2: 51 | im_comp.append(c_m1) 52 | im_comp.append(c_m1) 53 | else: 54 | if c_m1 == 0: 55 | im_comp.append(0x55) 56 | else: 57 | im_comp.append(0xaa) 58 | if runlength <= 127: 59 | im_comp.append(runlength) 60 | else: 61 | im_comp.append((runlength & 0x7f) | 128) 62 | im_comp.append(runlength >> 7) 63 | runlength = 0 64 | c_m1 = c 65 | 66 | if runlength == 0: 67 | im_comp.append(c_m1) 68 | else: 69 | if runlength == 2: 70 | im_comp.append(c_m1) 71 | im_comp.append(c_m1) 72 | else: 73 | if c_m1 == 0: 74 | im_comp.append(0x55) 75 | else: 76 | im_comp.append(0xaa) 77 | if runlength <= 127: 78 | im_comp.append(runlength) 79 | else: 80 | im_comp.append((runlength & 0x7f) | 128) 81 | im_comp.append(runlength >> 7) 82 | runlength = 0 83 | 84 | # DECODE 85 | im_decomp = [] 86 | runlength = -1 87 | c_to_dup = -1 88 | for c in im_comp[:]: 89 | if c_to_dup == -1: 90 | if c in [0x55, 0xaa]: 91 | c_to_dup = c 92 | else: 93 | im_decomp.append(c) 94 | else: 95 | if runlength == -1: 96 | if c == 0: 97 | im_decomp.append(c_to_dup) 98 | c_to_dup = -1 99 | elif (c & 0x80) == 0: 100 | if c_to_dup == 0x55: 101 | im_decomp.extend([0] * c) 102 | else: 103 | im_decomp.extend([255] * c) 104 | c_to_dup = -1 105 | else: 106 | runlength = c & 0x7f 107 | else: 108 | runlength = runlength | (c << 7) 109 | if c_to_dup == 0x55: 110 | im_decomp.extend([0] * runlength) 111 | else: 112 | im_decomp.extend([255] * runlength) 113 | c_to_dup = -1 114 | runlength = -1 115 | 116 | if len(imbit) != len(im_decomp): 117 | print("Decomp len fail!") 118 | else: 119 | for a, b in zip(imbit, im_decomp): 120 | if (b < 0) or (b > 255): 121 | print("Range to big") 122 | if a != b: 123 | print("Decomp fail") 124 | break 125 | return im_comp, imbit 126 | 127 | sumlen = 0 128 | movie = [] 129 | output_file = open("E:\\Proggen\\BA\\video.bin","wb") 130 | output_file_uc = open("E:\\Proggen\\BA\\video.uc","wb") 131 | # 6573 5470/2 132 | for nr in range(1, int(6574)): 133 | fn = "E:\Proggen\BA\scene" + "{0:0>5}".format(nr) + ".png" 134 | comp_dat, uncomp = get_compressed(fn) 135 | output_file.write(bytearray(comp_dat)) 136 | output_file_uc.write(bytearray(uncomp)) 137 | # movie.extend(comp_dat) 138 | sumlen += len(comp_dat) 139 | 140 | output_file.close() 141 | output_file_uc.close() 142 | #movie_dat = bytearray(movie) 143 | #print(len(movie_dat)) 144 | 145 | print(sumlen) 146 | print("Done") 147 | #im.show() 148 | -------------------------------------------------------------------------------- /ESP32_BadApple.ino: -------------------------------------------------------------------------------- 1 | // Bad Apple for ESP32 with OLED SSD1306 | 2018 by Hackerspace-FFM.de | MIT-License. 2 | #include "FS.h" 3 | #include "SPIFFS.h" 4 | #include "SSD1306.h" 5 | #include "heatshrink_decoder.h" 6 | 7 | // Hints: 8 | // * Adjust the display pins below 9 | // * After uploading to ESP32, also do "ESP32 Sketch Data Upload" from Arduino 10 | 11 | SSD1306 display (0x3c, 4, 15); // For Heltec 12 | //SSD1306 display (0x3c, 5, 4); 13 | 14 | #if HEATSHRINK_DYNAMIC_ALLOC 15 | #error HEATSHRINK_DYNAMIC_ALLOC must be false for static allocation test suite. 16 | #endif 17 | 18 | static heatshrink_decoder hsd; 19 | 20 | // global storage for putPixels 21 | int16_t curr_x = 0; 22 | int16_t curr_y = 0; 23 | 24 | // global storage for decodeRLE 25 | int32_t runlength = -1; 26 | int32_t c_to_dup = -1; 27 | 28 | void listDir(fs::FS &fs, const char * dirname, uint8_t levels){ 29 | Serial.printf("Listing directory: %s\n", dirname); 30 | 31 | File root = fs.open(dirname); 32 | if(!root){ 33 | Serial.println("Failed to open directory"); 34 | return; 35 | } 36 | if(!root.isDirectory()){ 37 | Serial.println("Not a directory"); 38 | return; 39 | } 40 | 41 | File file = root.openNextFile(); 42 | while(file){ 43 | if(file.isDirectory()){ 44 | Serial.print(" DIR : "); 45 | Serial.println(file.name()); 46 | if(levels){ 47 | listDir(fs, file.name(), levels -1); 48 | } 49 | } else { 50 | Serial.print(" FILE: "); 51 | Serial.print(file.name()); 52 | Serial.print(" SIZE: "); 53 | Serial.println(file.size()); 54 | } 55 | file = root.openNextFile(); 56 | } 57 | } 58 | 59 | uint32_t lastRefresh = 0; 60 | 61 | void putPixels(uint8_t c, int32_t len) { 62 | uint8_t b = 0; 63 | while(len--) { 64 | b = 128; 65 | for(int i=0; i<8; i++) { 66 | if(c & b) { 67 | display.setColor(WHITE); 68 | } else { 69 | display.setColor(BLACK); 70 | } 71 | b >>= 1; 72 | display.setPixel(curr_x, curr_y); 73 | curr_x++; 74 | if(curr_x >= 128) { 75 | curr_x = 0; 76 | curr_y++; 77 | if(curr_y >= 64) { 78 | curr_y = 0; 79 | display.display(); 80 | //display.clear(); 81 | // 30 fps target rate 82 | if(digitalRead(0)) while((millis() - lastRefresh) < 33) ; 83 | lastRefresh = millis(); 84 | } 85 | } 86 | } 87 | } 88 | } 89 | 90 | void decodeRLE(uint8_t c) { 91 | if(c_to_dup == -1) { 92 | if((c == 0x55) || (c == 0xaa)) { 93 | c_to_dup = c; 94 | } else { 95 | putPixels(c, 1); 96 | } 97 | } else { 98 | if(runlength == -1) { 99 | if(c == 0) { 100 | putPixels(c_to_dup & 0xff, 1); 101 | c_to_dup = -1; 102 | } else if((c & 0x80) == 0) { 103 | if(c_to_dup == 0x55) { 104 | putPixels(0, c); 105 | } else { 106 | putPixels(255, c); 107 | } 108 | c_to_dup = -1; 109 | } else { 110 | runlength = c & 0x7f; 111 | } 112 | } else { 113 | runlength = runlength | (c << 7); 114 | if(c_to_dup == 0x55) { 115 | putPixels(0, runlength); 116 | } else { 117 | putPixels(255, runlength); 118 | } 119 | c_to_dup = -1; 120 | runlength = -1; 121 | } 122 | } 123 | } 124 | 125 | #define RLEBUFSIZE 4096 126 | #define READBUFSIZE 2048 127 | void readFile(fs::FS &fs, const char * path){ 128 | static uint8_t rle_buf[RLEBUFSIZE]; 129 | size_t rle_bufhead = 0; 130 | size_t rle_size = 0; 131 | 132 | size_t filelen = 0; 133 | size_t filesize; 134 | static uint8_t compbuf[READBUFSIZE]; 135 | 136 | Serial.printf("Reading file: %s\n", path); 137 | File file = fs.open(path); 138 | if(!file || file.isDirectory()){ 139 | Serial.println("Failed to open file for reading"); 140 | display.drawStringMaxWidth(0, 10, 128, "File open error. Upload video.hs using ESP32 Sketch Upload."); display.display(); 141 | return; 142 | } 143 | filelen = file.size(); 144 | filesize = filelen; 145 | Serial.printf("File size: %d\n", filelen); 146 | 147 | // init display, putPixels and decodeRLE 148 | display.clear(); 149 | display.display(); 150 | curr_x = 0; 151 | curr_y = 0; 152 | runlength = -1; 153 | c_to_dup = -1; 154 | lastRefresh = millis(); 155 | 156 | // init decoder 157 | heatshrink_decoder_reset(&hsd); 158 | size_t count = 0; 159 | uint32_t sunk = 0; 160 | size_t toRead; 161 | size_t toSink = 0; 162 | uint32_t sinkHead = 0; 163 | 164 | 165 | // Go through file... 166 | while(filelen) { 167 | if(toSink == 0) { 168 | toRead = filelen; 169 | if(toRead > READBUFSIZE) toRead = READBUFSIZE; 170 | file.read(compbuf, toRead); 171 | filelen -= toRead; 172 | toSink = toRead; 173 | sinkHead = 0; 174 | } 175 | 176 | // uncompress buffer 177 | HSD_sink_res sres; 178 | sres = heatshrink_decoder_sink(&hsd, &compbuf[sinkHead], toSink, &count); 179 | //Serial.print("^^ sinked "); 180 | //Serial.println(count); 181 | toSink -= count; 182 | sinkHead = count; 183 | sunk += count; 184 | if (sunk == filesize) { 185 | heatshrink_decoder_finish(&hsd); 186 | } 187 | 188 | HSD_poll_res pres; 189 | do { 190 | rle_size = 0; 191 | pres = heatshrink_decoder_poll(&hsd, rle_buf, RLEBUFSIZE, &rle_size); 192 | //Serial.print("^^ polled "); 193 | //Serial.println(rle_size); 194 | if(pres < 0) { 195 | Serial.print("POLL ERR! "); 196 | Serial.println(pres); 197 | return; 198 | } 199 | 200 | rle_bufhead = 0; 201 | while(rle_size) { 202 | rle_size--; 203 | if(rle_bufhead >= RLEBUFSIZE) { 204 | Serial.println("RLE_SIZE ERR!"); 205 | return; 206 | } 207 | decodeRLE(rle_buf[rle_bufhead++]); 208 | } 209 | } while (pres == HSDR_POLL_MORE); 210 | } 211 | file.close(); 212 | Serial.println("Done."); 213 | } 214 | 215 | 216 | 217 | void setup(){ 218 | Serial.begin(115200); 219 | // Reset for some displays 220 | pinMode(16,OUTPUT); digitalWrite(16, LOW); delay(50); digitalWrite(16, HIGH); 221 | display.init(); 222 | display.flipScreenVertically (); 223 | display.clear(); 224 | display.setTextAlignment (TEXT_ALIGN_LEFT); 225 | display.setFont(ArialMT_Plain_10); 226 | display.setColor(WHITE); 227 | display.drawString(0, 0, "Mounting SPIFFS... "); 228 | display.display(); 229 | if(!SPIFFS.begin()){ 230 | Serial.println("SPIFFS mount failed"); 231 | display.drawStringMaxWidth(0, 10, 128, "SPIFFS mount failed. Upload video.hs using ESP32 Sketch Upload."); display.display(); 232 | return; 233 | } 234 | 235 | pinMode(0, INPUT_PULLUP); 236 | Serial.print("totalBytes(): "); 237 | Serial.println(SPIFFS.totalBytes()); 238 | Serial.print("usedBytes(): "); 239 | Serial.println(SPIFFS.usedBytes()); 240 | listDir(SPIFFS, "/", 0); 241 | readFile(SPIFFS, "/video.hs"); 242 | 243 | //Serial.print("Format SPIFSS? (enter y for yes): "); 244 | // while(!Serial.available()) ; 245 | //if(Serial.read() == 'y') { 246 | // bool ret = SPIFFS.format(); 247 | // if(ret) Serial.println("Success. "); else Serial.println("FAILED! "); 248 | //} else { 249 | // Serial.println("Aborted."); 250 | //} 251 | } 252 | 253 | void loop(){ 254 | 255 | } 256 | -------------------------------------------------------------------------------- /ESP32_BadApple.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hackffm/ESP32_BadApple/7fe25a8a9ef89eb8aa419aa8cfb7939632de116b/ESP32_BadApple.jpg -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 Hackerspace Ffm e.V. 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # ESP32_BadApple 2 | Bad Apple video by Touhou on ESP32 with SSD1306 OLED, uses the Heatshrink compression library to decompress the RLE encoded video data. 3 | First version, no sound yet, video only. 4 | 5 | ![Bad Apple on ESP32](ESP32_BadApple.jpg) 6 | 7 | ## Hardware Requirements 8 | Runs on ESP32 modules with OLED displays based on SSD1306. Typical modules are available from Heltec or TTGO or others. 9 | 10 | ## Software Requirements 11 | * Arduino 1.8.x 12 | * ESP32 Arduino core from https://github.com/espressif/arduino-esp32 13 | * OLED display driver from https://github.com/ThingPulse/esp8266-oled-ssd1306 14 | * ESP32 SPIFFS Upload Plugin from https://github.com/me-no-dev/arduino-esp32fs-plugin 15 | 16 | # Usage 17 | * Adapt display pins in main sketch if necessary 18 | * Upload sketch 19 | * Upload sketch data via "Tools" -> "ESP32 Sketch Data Upload" 20 | 21 | Enjoy video. Pressing PRG button (GPIO0) for max display speed (mainly limited by I2C transfer), otherwise limited to 30 fps. 22 | 23 | # How does it work 24 | Video have been separated into >6500 single pictures, resized to 128x64 pixels using VLC. 25 | Python skript used to run-length encode the 8-bit-packed data using 0x55 and 0xAA as escape marker and putting all into one file. 26 | RLE file has been further compressed using heatshrink compression for easy storage into SPIFFS (which can hold only 1MB by default). 27 | Heatshrink for Arduino uses ZIP-like algorithm and is available also as a library under https://github.com/p-v-o-s/Arduino-HScompression and 28 | original documentation is here: https://spin.atomicobject.com/2013/03/14/heatshrink-embedded-data-compression/ 29 | 30 | # Known issues 31 | SPIFFS is broken currently (Feb 2018) in ESP32 Arduino core - hopefully will be fixed there soon. Search through issue list there for solutions - 32 | usually an earlier commit can be used to get it working. 33 | 34 | 35 | -------------------------------------------------------------------------------- /data/video.hs: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hackffm/ESP32_BadApple/7fe25a8a9ef89eb8aa419aa8cfb7939632de116b/data/video.hs -------------------------------------------------------------------------------- /heatshrink_LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2013-2015, Scott Vokes 2 | All rights reserved. 3 | 4 | Permission to use, copy, modify, and/or distribute this software for any 5 | purpose with or without fee is hereby granted, provided that the above 6 | copyright notice and this permission notice appear in all copies. 7 | 8 | THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 9 | WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 10 | MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 11 | ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 12 | WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 13 | ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 14 | OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 15 | -------------------------------------------------------------------------------- /heatshrink_common.h: -------------------------------------------------------------------------------- 1 | #ifndef HEATSHRINK_H 2 | #define HEATSHRINK_H 3 | 4 | #define HEATSHRINK_AUTHOR "Scott Vokes " 5 | #define HEATSHRINK_URL "https://github.com/atomicobject/heatshrink" 6 | 7 | /* Version 0.4.1 */ 8 | #define HEATSHRINK_VERSION_MAJOR 0 9 | #define HEATSHRINK_VERSION_MINOR 4 10 | #define HEATSHRINK_VERSION_PATCH 1 11 | 12 | #define HEATSHRINK_MIN_WINDOW_BITS 4 13 | #define HEATSHRINK_MAX_WINDOW_BITS 15 14 | 15 | #define HEATSHRINK_MIN_LOOKAHEAD_BITS 3 16 | 17 | #define HEATSHRINK_LITERAL_MARKER 0x01 18 | #define HEATSHRINK_BACKREF_MARKER 0x00 19 | 20 | #endif 21 | -------------------------------------------------------------------------------- /heatshrink_config.h: -------------------------------------------------------------------------------- 1 | #ifndef HEATSHRINK_CONFIG_H 2 | #define HEATSHRINK_CONFIG_H 3 | 4 | /* Should functionality assuming dynamic allocation be used? */ 5 | #ifndef HEATSHRINK_DYNAMIC_ALLOC 6 | #define HEATSHRINK_DYNAMIC_ALLOC 0 7 | #endif 8 | 9 | #if HEATSHRINK_DYNAMIC_ALLOC 10 | /* Optional replacement of malloc/free */ 11 | #define HEATSHRINK_MALLOC(SZ) malloc(SZ) 12 | #define HEATSHRINK_FREE(P, SZ) free(P) 13 | #else 14 | /* Required parameters for static configuration */ 15 | #define HEATSHRINK_STATIC_INPUT_BUFFER_SIZE 2048 16 | #define HEATSHRINK_STATIC_WINDOW_BITS 11 17 | #define HEATSHRINK_STATIC_LOOKAHEAD_BITS 4 18 | #endif 19 | 20 | /* Turn on logging for debugging. */ 21 | #define HEATSHRINK_DEBUGGING_LOGS 0 22 | 23 | /* Use indexing for faster compression. (This requires additional space.) */ 24 | #define HEATSHRINK_USE_INDEX 1 25 | 26 | #endif 27 | -------------------------------------------------------------------------------- /heatshrink_decoder.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include "heatshrink_decoder.h" 4 | 5 | /* States for the polling state machine. */ 6 | typedef enum { 7 | HSDS_TAG_BIT, /* tag bit */ 8 | HSDS_YIELD_LITERAL, /* ready to yield literal byte */ 9 | HSDS_BACKREF_INDEX_MSB, /* most significant byte of index */ 10 | HSDS_BACKREF_INDEX_LSB, /* least significant byte of index */ 11 | HSDS_BACKREF_COUNT_MSB, /* most significant byte of count */ 12 | HSDS_BACKREF_COUNT_LSB, /* least significant byte of count */ 13 | HSDS_YIELD_BACKREF, /* ready to yield back-reference */ 14 | } HSD_state; 15 | 16 | #if HEATSHRINK_DEBUGGING_LOGS 17 | #include 18 | #include 19 | #include 20 | #define LOG(...) fprintf(stderr, __VA_ARGS__) 21 | #define ASSERT(X) assert(X) 22 | static const char *state_names[] = { 23 | "tag_bit", 24 | "yield_literal", 25 | "backref_index_msb", 26 | "backref_index_lsb", 27 | "backref_count_msb", 28 | "backref_count_lsb", 29 | "yield_backref", 30 | }; 31 | #else 32 | #define LOG(...) /* no-op */ 33 | #define ASSERT(X) /* no-op */ 34 | #endif 35 | 36 | typedef struct { 37 | uint8_t *buf; /* output buffer */ 38 | size_t buf_size; /* buffer size */ 39 | size_t *output_size; /* bytes pushed to buffer, so far */ 40 | } output_info; 41 | 42 | #define NO_BITS ((uint16_t)-1) 43 | 44 | /* Forward references. */ 45 | static uint16_t get_bits(heatshrink_decoder *hsd, uint8_t count); 46 | static void push_byte(heatshrink_decoder *hsd, output_info *oi, uint8_t byte); 47 | 48 | #if HEATSHRINK_DYNAMIC_ALLOC 49 | heatshrink_decoder *heatshrink_decoder_alloc(uint16_t input_buffer_size, 50 | uint8_t window_sz2, 51 | uint8_t lookahead_sz2) { 52 | if ((window_sz2 < HEATSHRINK_MIN_WINDOW_BITS) || 53 | (window_sz2 > HEATSHRINK_MAX_WINDOW_BITS) || 54 | (input_buffer_size == 0) || 55 | (lookahead_sz2 < HEATSHRINK_MIN_LOOKAHEAD_BITS) || 56 | (lookahead_sz2 >= window_sz2)) { 57 | return NULL; 58 | } 59 | size_t buffers_sz = (1 << window_sz2) + input_buffer_size; 60 | size_t sz = sizeof(heatshrink_decoder) + buffers_sz; 61 | heatshrink_decoder *hsd = HEATSHRINK_MALLOC(sz); 62 | if (hsd == NULL) { return NULL; } 63 | hsd->input_buffer_size = input_buffer_size; 64 | hsd->window_sz2 = window_sz2; 65 | hsd->lookahead_sz2 = lookahead_sz2; 66 | heatshrink_decoder_reset(hsd); 67 | LOG("-- allocated decoder with buffer size of %zu (%zu + %u + %u)\n", 68 | sz, sizeof(heatshrink_decoder), (1 << window_sz2), input_buffer_size); 69 | return hsd; 70 | } 71 | 72 | void heatshrink_decoder_free(heatshrink_decoder *hsd) { 73 | size_t buffers_sz = (1 << hsd->window_sz2) + hsd->input_buffer_size; 74 | size_t sz = sizeof(heatshrink_decoder) + buffers_sz; 75 | HEATSHRINK_FREE(hsd, sz); 76 | (void)sz; /* may not be used by free */ 77 | } 78 | #endif 79 | 80 | void heatshrink_decoder_reset(heatshrink_decoder *hsd) { 81 | size_t buf_sz = 1 << HEATSHRINK_DECODER_WINDOW_BITS(hsd); 82 | size_t input_sz = HEATSHRINK_DECODER_INPUT_BUFFER_SIZE(hsd); 83 | memset(hsd->buffers, 0, buf_sz + input_sz); 84 | hsd->state = HSDS_TAG_BIT; 85 | hsd->input_size = 0; 86 | hsd->input_index = 0; 87 | hsd->bit_index = 0x00; 88 | hsd->current_byte = 0x00; 89 | hsd->output_count = 0; 90 | hsd->output_index = 0; 91 | hsd->head_index = 0; 92 | } 93 | 94 | /* Copy SIZE bytes into the decoder's input buffer, if it will fit. */ 95 | HSD_sink_res heatshrink_decoder_sink(heatshrink_decoder *hsd, 96 | uint8_t *in_buf, size_t size, size_t *input_size) { 97 | if ((hsd == NULL) || (in_buf == NULL) || (input_size == NULL)) { 98 | return HSDR_SINK_ERROR_NULL; 99 | } 100 | 101 | size_t rem = HEATSHRINK_DECODER_INPUT_BUFFER_SIZE(hsd) - hsd->input_size; 102 | if (rem == 0) { 103 | *input_size = 0; 104 | return HSDR_SINK_FULL; 105 | } 106 | 107 | size = rem < size ? rem : size; 108 | LOG("-- sinking %zd bytes\n", size); 109 | /* copy into input buffer (at head of buffers) */ 110 | memcpy(&hsd->buffers[hsd->input_size], in_buf, size); 111 | hsd->input_size += size; 112 | *input_size = size; 113 | return HSDR_SINK_OK; 114 | } 115 | 116 | 117 | /***************** 118 | * Decompression * 119 | *****************/ 120 | 121 | #define BACKREF_COUNT_BITS(HSD) (HEATSHRINK_DECODER_LOOKAHEAD_BITS(HSD)) 122 | #define BACKREF_INDEX_BITS(HSD) (HEATSHRINK_DECODER_WINDOW_BITS(HSD)) 123 | 124 | // States 125 | static HSD_state st_tag_bit(heatshrink_decoder *hsd); 126 | static HSD_state st_yield_literal(heatshrink_decoder *hsd, 127 | output_info *oi); 128 | static HSD_state st_backref_index_msb(heatshrink_decoder *hsd); 129 | static HSD_state st_backref_index_lsb(heatshrink_decoder *hsd); 130 | static HSD_state st_backref_count_msb(heatshrink_decoder *hsd); 131 | static HSD_state st_backref_count_lsb(heatshrink_decoder *hsd); 132 | static HSD_state st_yield_backref(heatshrink_decoder *hsd, 133 | output_info *oi); 134 | 135 | HSD_poll_res heatshrink_decoder_poll(heatshrink_decoder *hsd, 136 | uint8_t *out_buf, size_t out_buf_size, size_t *output_size) { 137 | if ((hsd == NULL) || (out_buf == NULL) || (output_size == NULL)) { 138 | return HSDR_POLL_ERROR_NULL; 139 | } 140 | *output_size = 0; 141 | 142 | output_info oi; 143 | oi.buf = out_buf; 144 | oi.buf_size = out_buf_size; 145 | oi.output_size = output_size; 146 | 147 | while (1) { 148 | LOG("-- poll, state is %d (%s), input_size %d\n", 149 | hsd->state, state_names[hsd->state], hsd->input_size); 150 | uint8_t in_state = hsd->state; 151 | switch (in_state) { 152 | case HSDS_TAG_BIT: 153 | hsd->state = st_tag_bit(hsd); 154 | break; 155 | case HSDS_YIELD_LITERAL: 156 | hsd->state = st_yield_literal(hsd, &oi); 157 | break; 158 | case HSDS_BACKREF_INDEX_MSB: 159 | hsd->state = st_backref_index_msb(hsd); 160 | break; 161 | case HSDS_BACKREF_INDEX_LSB: 162 | hsd->state = st_backref_index_lsb(hsd); 163 | break; 164 | case HSDS_BACKREF_COUNT_MSB: 165 | hsd->state = st_backref_count_msb(hsd); 166 | break; 167 | case HSDS_BACKREF_COUNT_LSB: 168 | hsd->state = st_backref_count_lsb(hsd); 169 | break; 170 | case HSDS_YIELD_BACKREF: 171 | hsd->state = st_yield_backref(hsd, &oi); 172 | break; 173 | default: 174 | return HSDR_POLL_ERROR_UNKNOWN; 175 | } 176 | 177 | /* If the current state cannot advance, check if input or output 178 | * buffer are exhausted. */ 179 | if (hsd->state == in_state) { 180 | if (*output_size == out_buf_size) { return HSDR_POLL_MORE; } 181 | return HSDR_POLL_EMPTY; 182 | } 183 | } 184 | } 185 | 186 | static HSD_state st_tag_bit(heatshrink_decoder *hsd) { 187 | uint32_t bits = get_bits(hsd, 1); // get tag bit 188 | if (bits == NO_BITS) { 189 | return HSDS_TAG_BIT; 190 | } else if (bits) { 191 | return HSDS_YIELD_LITERAL; 192 | } else if (HEATSHRINK_DECODER_WINDOW_BITS(hsd) > 8) { 193 | return HSDS_BACKREF_INDEX_MSB; 194 | } else { 195 | hsd->output_index = 0; 196 | return HSDS_BACKREF_INDEX_LSB; 197 | } 198 | } 199 | 200 | static HSD_state st_yield_literal(heatshrink_decoder *hsd, 201 | output_info *oi) { 202 | /* Emit a repeated section from the window buffer, and add it (again) 203 | * to the window buffer. (Note that the repetition can include 204 | * itself.)*/ 205 | if (*oi->output_size < oi->buf_size) { 206 | uint16_t byte = get_bits(hsd, 8); 207 | if (byte == NO_BITS) { return HSDS_YIELD_LITERAL; } /* out of input */ 208 | uint8_t *buf = &hsd->buffers[HEATSHRINK_DECODER_INPUT_BUFFER_SIZE(hsd)]; 209 | uint16_t mask = (1 << HEATSHRINK_DECODER_WINDOW_BITS(hsd)) - 1; 210 | uint8_t c = byte & 0xFF; 211 | LOG("-- emitting literal byte 0x%02x ('%c')\n", c, isprint(c) ? c : '.'); 212 | buf[hsd->head_index++ & mask] = c; 213 | push_byte(hsd, oi, c); 214 | return HSDS_TAG_BIT; 215 | } else { 216 | return HSDS_YIELD_LITERAL; 217 | } 218 | } 219 | 220 | static HSD_state st_backref_index_msb(heatshrink_decoder *hsd) { 221 | uint8_t bit_ct = BACKREF_INDEX_BITS(hsd); 222 | ASSERT(bit_ct > 8); 223 | uint16_t bits = get_bits(hsd, bit_ct - 8); 224 | LOG("-- backref index (msb), got 0x%04x (+1)\n", bits); 225 | if (bits == NO_BITS) { return HSDS_BACKREF_INDEX_MSB; } 226 | hsd->output_index = bits << 8; 227 | return HSDS_BACKREF_INDEX_LSB; 228 | } 229 | 230 | static HSD_state st_backref_index_lsb(heatshrink_decoder *hsd) { 231 | uint8_t bit_ct = BACKREF_INDEX_BITS(hsd); 232 | uint16_t bits = get_bits(hsd, bit_ct < 8 ? bit_ct : 8); 233 | LOG("-- backref index (lsb), got 0x%04x (+1)\n", bits); 234 | if (bits == NO_BITS) { return HSDS_BACKREF_INDEX_LSB; } 235 | hsd->output_index |= bits; 236 | hsd->output_index++; 237 | uint8_t br_bit_ct = BACKREF_COUNT_BITS(hsd); 238 | hsd->output_count = 0; 239 | return (br_bit_ct > 8) ? HSDS_BACKREF_COUNT_MSB : HSDS_BACKREF_COUNT_LSB; 240 | } 241 | 242 | static HSD_state st_backref_count_msb(heatshrink_decoder *hsd) { 243 | uint8_t br_bit_ct = BACKREF_COUNT_BITS(hsd); 244 | ASSERT(br_bit_ct > 8); 245 | uint16_t bits = get_bits(hsd, br_bit_ct - 8); 246 | LOG("-- backref count (msb), got 0x%04x (+1)\n", bits); 247 | if (bits == NO_BITS) { return HSDS_BACKREF_COUNT_MSB; } 248 | hsd->output_count = bits << 8; 249 | return HSDS_BACKREF_COUNT_LSB; 250 | } 251 | 252 | static HSD_state st_backref_count_lsb(heatshrink_decoder *hsd) { 253 | uint8_t br_bit_ct = BACKREF_COUNT_BITS(hsd); 254 | uint16_t bits = get_bits(hsd, br_bit_ct < 8 ? br_bit_ct : 8); 255 | LOG("-- backref count (lsb), got 0x%04x (+1)\n", bits); 256 | if (bits == NO_BITS) { return HSDS_BACKREF_COUNT_LSB; } 257 | hsd->output_count |= bits; 258 | hsd->output_count++; 259 | return HSDS_YIELD_BACKREF; 260 | } 261 | 262 | static HSD_state st_yield_backref(heatshrink_decoder *hsd, 263 | output_info *oi) { 264 | size_t count = oi->buf_size - *oi->output_size; 265 | if (count > 0) { 266 | size_t i = 0; 267 | if (hsd->output_count < count) count = hsd->output_count; 268 | uint8_t *buf = &hsd->buffers[HEATSHRINK_DECODER_INPUT_BUFFER_SIZE(hsd)]; 269 | uint16_t mask = (1 << HEATSHRINK_DECODER_WINDOW_BITS(hsd)) - 1; 270 | uint16_t neg_offset = hsd->output_index; 271 | LOG("-- emitting %zu bytes from -%u bytes back\n", count, neg_offset); 272 | ASSERT(neg_offset <= mask + 1); 273 | ASSERT(count <= (size_t)(1 << BACKREF_COUNT_BITS(hsd))); 274 | 275 | for (i=0; ihead_index - neg_offset) & mask]; 277 | push_byte(hsd, oi, c); 278 | buf[hsd->head_index & mask] = c; 279 | hsd->head_index++; 280 | LOG(" -- ++ 0x%02x\n", c); 281 | } 282 | hsd->output_count -= count; 283 | if (hsd->output_count == 0) { return HSDS_TAG_BIT; } 284 | } 285 | return HSDS_YIELD_BACKREF; 286 | } 287 | 288 | /* Get the next COUNT bits from the input buffer, saving incremental progress. 289 | * Returns NO_BITS on end of input, or if more than 15 bits are requested. */ 290 | static uint16_t get_bits(heatshrink_decoder *hsd, uint8_t count) { 291 | uint16_t accumulator = 0; 292 | int i = 0; 293 | if (count > 15) { return NO_BITS; } 294 | LOG("-- popping %u bit(s)\n", count); 295 | 296 | /* If we aren't able to get COUNT bits, suspend immediately, because we 297 | * don't track how many bits of COUNT we've accumulated before suspend. */ 298 | if (hsd->input_size == 0) { 299 | if (hsd->bit_index < (1 << (count - 1))) { return NO_BITS; } 300 | } 301 | 302 | for (i = 0; i < count; i++) { 303 | if (hsd->bit_index == 0x00) { 304 | if (hsd->input_size == 0) { 305 | LOG(" -- out of bits, suspending w/ accumulator of %u (0x%02x)\n", 306 | accumulator, accumulator); 307 | return NO_BITS; 308 | } 309 | hsd->current_byte = hsd->buffers[hsd->input_index++]; 310 | LOG(" -- pulled byte 0x%02x\n", hsd->current_byte); 311 | if (hsd->input_index == hsd->input_size) { 312 | hsd->input_index = 0; /* input is exhausted */ 313 | hsd->input_size = 0; 314 | } 315 | hsd->bit_index = 0x80; 316 | } 317 | accumulator <<= 1; 318 | if (hsd->current_byte & hsd->bit_index) { 319 | accumulator |= 0x01; 320 | if (0) { 321 | LOG(" -- got 1, accumulator 0x%04x, bit_index 0x%02x\n", 322 | accumulator, hsd->bit_index); 323 | } 324 | } else { 325 | if (0) { 326 | LOG(" -- got 0, accumulator 0x%04x, bit_index 0x%02x\n", 327 | accumulator, hsd->bit_index); 328 | } 329 | } 330 | hsd->bit_index >>= 1; 331 | } 332 | 333 | if (count > 1) { LOG(" -- accumulated %08x\n", accumulator); } 334 | return accumulator; 335 | } 336 | 337 | HSD_finish_res heatshrink_decoder_finish(heatshrink_decoder *hsd) { 338 | if (hsd == NULL) { return HSDR_FINISH_ERROR_NULL; } 339 | switch (hsd->state) { 340 | case HSDS_TAG_BIT: 341 | return hsd->input_size == 0 ? HSDR_FINISH_DONE : HSDR_FINISH_MORE; 342 | 343 | /* If we want to finish with no input, but are in these states, it's 344 | * because the 0-bit padding to the last byte looks like a backref 345 | * marker bit followed by all 0s for index and count bits. */ 346 | case HSDS_BACKREF_INDEX_LSB: 347 | case HSDS_BACKREF_INDEX_MSB: 348 | case HSDS_BACKREF_COUNT_LSB: 349 | case HSDS_BACKREF_COUNT_MSB: 350 | return hsd->input_size == 0 ? HSDR_FINISH_DONE : HSDR_FINISH_MORE; 351 | 352 | /* If the output stream is padded with 0xFFs (possibly due to being in 353 | * flash memory), also explicitly check the input size rather than 354 | * uselessly returning MORE but yielding 0 bytes when polling. */ 355 | case HSDS_YIELD_LITERAL: 356 | return hsd->input_size == 0 ? HSDR_FINISH_DONE : HSDR_FINISH_MORE; 357 | 358 | default: 359 | return HSDR_FINISH_MORE; 360 | } 361 | } 362 | 363 | static void push_byte(heatshrink_decoder *hsd, output_info *oi, uint8_t byte) { 364 | LOG(" -- pushing byte: 0x%02x ('%c')\n", byte, isprint(byte) ? byte : '.'); 365 | oi->buf[(*oi->output_size)++] = byte; 366 | (void)hsd; 367 | } 368 | -------------------------------------------------------------------------------- /heatshrink_decoder.h: -------------------------------------------------------------------------------- 1 | #ifndef HEATSHRINK_DECODER_H 2 | #define HEATSHRINK_DECODER_H 3 | 4 | #include 5 | #include 6 | #include "heatshrink_common.h" 7 | #include "heatshrink_config.h" 8 | 9 | typedef enum { 10 | HSDR_SINK_OK, /* data sunk, ready to poll */ 11 | HSDR_SINK_FULL, /* out of space in internal buffer */ 12 | HSDR_SINK_ERROR_NULL=-1, /* NULL argument */ 13 | } HSD_sink_res; 14 | 15 | typedef enum { 16 | HSDR_POLL_EMPTY, /* input exhausted */ 17 | HSDR_POLL_MORE, /* more data remaining, call again w/ fresh output buffer */ 18 | HSDR_POLL_ERROR_NULL=-1, /* NULL arguments */ 19 | HSDR_POLL_ERROR_UNKNOWN=-2, 20 | } HSD_poll_res; 21 | 22 | typedef enum { 23 | HSDR_FINISH_DONE, /* output is done */ 24 | HSDR_FINISH_MORE, /* more output remains */ 25 | HSDR_FINISH_ERROR_NULL=-1, /* NULL arguments */ 26 | } HSD_finish_res; 27 | 28 | #if HEATSHRINK_DYNAMIC_ALLOC 29 | #define HEATSHRINK_DECODER_INPUT_BUFFER_SIZE(BUF) \ 30 | ((BUF)->input_buffer_size) 31 | #define HEATSHRINK_DECODER_WINDOW_BITS(BUF) \ 32 | ((BUF)->window_sz2) 33 | #define HEATSHRINK_DECODER_LOOKAHEAD_BITS(BUF) \ 34 | ((BUF)->lookahead_sz2) 35 | #else 36 | #define HEATSHRINK_DECODER_INPUT_BUFFER_SIZE(_) \ 37 | HEATSHRINK_STATIC_INPUT_BUFFER_SIZE 38 | #define HEATSHRINK_DECODER_WINDOW_BITS(_) \ 39 | (HEATSHRINK_STATIC_WINDOW_BITS) 40 | #define HEATSHRINK_DECODER_LOOKAHEAD_BITS(BUF) \ 41 | (HEATSHRINK_STATIC_LOOKAHEAD_BITS) 42 | #endif 43 | 44 | typedef struct { 45 | uint16_t input_size; /* bytes in input buffer */ 46 | uint16_t input_index; /* offset to next unprocessed input byte */ 47 | uint16_t output_count; /* how many bytes to output */ 48 | uint16_t output_index; /* index for bytes to output */ 49 | uint16_t head_index; /* head of window buffer */ 50 | uint8_t state; /* current state machine node */ 51 | uint8_t current_byte; /* current byte of input */ 52 | uint8_t bit_index; /* current bit index */ 53 | 54 | #if HEATSHRINK_DYNAMIC_ALLOC 55 | /* Fields that are only used if dynamically allocated. */ 56 | uint8_t window_sz2; /* window buffer bits */ 57 | uint8_t lookahead_sz2; /* lookahead bits */ 58 | uint16_t input_buffer_size; /* input buffer size */ 59 | 60 | /* Input buffer, then expansion window buffer */ 61 | uint8_t buffers[]; 62 | #else 63 | /* Input buffer, then expansion window buffer */ 64 | uint8_t buffers[(1 << HEATSHRINK_DECODER_WINDOW_BITS(_)) 65 | + HEATSHRINK_DECODER_INPUT_BUFFER_SIZE(_)]; 66 | #endif 67 | } heatshrink_decoder; 68 | 69 | #if HEATSHRINK_DYNAMIC_ALLOC 70 | /* Allocate a decoder with an input buffer of INPUT_BUFFER_SIZE bytes, 71 | * an expansion buffer size of 2^WINDOW_SZ2, and a lookahead 72 | * size of 2^lookahead_sz2. (The window buffer and lookahead sizes 73 | * must match the settings used when the data was compressed.) 74 | * Returns NULL on error. */ 75 | heatshrink_decoder *heatshrink_decoder_alloc(uint16_t input_buffer_size, 76 | uint8_t expansion_buffer_sz2, uint8_t lookahead_sz2); 77 | 78 | /* Free a decoder. */ 79 | void heatshrink_decoder_free(heatshrink_decoder *hsd); 80 | #endif 81 | 82 | /* Reset a decoder. */ 83 | void heatshrink_decoder_reset(heatshrink_decoder *hsd); 84 | 85 | /* Sink at most SIZE bytes from IN_BUF into the decoder. *INPUT_SIZE is set to 86 | * indicate how many bytes were actually sunk (in case a buffer was filled). */ 87 | HSD_sink_res heatshrink_decoder_sink(heatshrink_decoder *hsd, 88 | uint8_t *in_buf, size_t size, size_t *input_size); 89 | 90 | /* Poll for output from the decoder, copying at most OUT_BUF_SIZE bytes into 91 | * OUT_BUF (setting *OUTPUT_SIZE to the actual amount copied). */ 92 | HSD_poll_res heatshrink_decoder_poll(heatshrink_decoder *hsd, 93 | uint8_t *out_buf, size_t out_buf_size, size_t *output_size); 94 | 95 | /* Notify the dencoder that the input stream is finished. 96 | * If the return value is HSDR_FINISH_MORE, there is still more output, so 97 | * call heatshrink_decoder_poll and repeat. */ 98 | HSD_finish_res heatshrink_decoder_finish(heatshrink_decoder *hsd); 99 | 100 | #endif 101 | --------------------------------------------------------------------------------