rlecompress.c (6141B)
1// This is free and unencumbered software released into the public domain. 2// For more information, please refer to <https://unlicense.org> 3// bbbbbr 2020 4 5#include <stdio.h> 6//#include <string.h> 7#include <stdlib.h> 8#include <stdbool.h> 9#include <stdint.h> 10#include "rlecompress.h" 11 12 13static uint8_t * FinBuf = NULL; 14static uint32_t Fsize_in = 0; 15static uint32_t FinIndex = 0; 16 17static uint8_t ** pp_FoutBuf = NULL; 18static uint8_t * FoutBuf = NULL; 19static uint32_t Fsize_out = 0; 20static uint32_t FoutIndex = 0; 21 22 23#define RLE_MASK_LEN 0x7F 24#define RLE_MASK_TYPE 0x80 25#define RLE_TYPE_RAND 0x00 26#define RLE_TYPE_REPEAT 0x80 27#define RLE_MAX_LEN 127 28#define RLE_CTRL_END 0x00 29#define RLE_MAX_BLOCK 127 30#define RLE_CHANGE_COST 2 31 32 33static uint8_t rle_queued[128]; 34static int rle_queue_idx = 0; 35static int run_len = 0; 36 37 38// Initialize the buffer vars 39static void initbufs(uint8_t * inBuf, uint32_t size_in, uint8_t ** pp_outBuf, uint32_t size_out) { 40 41 FinBuf = inBuf; 42 Fsize_in = size_in; 43 FinIndex = 0; 44 45 pp_FoutBuf = pp_outBuf; 46 FoutBuf = *pp_outBuf; 47 Fsize_out = size_out; 48 FoutIndex = 0; 49} 50 51 52static void check_write_size(int len) { 53 54 // Grow output buffer if needed 55 if ((FoutIndex + len) >= Fsize_out) { 56 uint8_t * p_tmp = *pp_FoutBuf; 57 58 // Reallocate to twice as large 59 Fsize_out = Fsize_out * 2; 60 *pp_FoutBuf = (void *)realloc(*pp_FoutBuf, Fsize_out); 61 62 // If realloc failed, free original buffer before quitting 63 if (!(*pp_FoutBuf)) { 64 printf("Error: Failed to grow memory for output buffer!\n"); 65 if (p_tmp) free(p_tmp); 66 p_tmp = NULL; 67 exit(EXIT_FAILURE); 68 } else 69 FoutBuf = *pp_FoutBuf; // Update working pointer 70 } 71} 72 73 74 75static void write_end_of_data(void) { 76 77 check_write_size(1); // writing 1 control byte 78 FoutBuf[FoutIndex++] = RLE_CTRL_END; // Write sequence control byte 79} 80 81 82static void write_run_repeat(int len, uint8_t value) { 83 84 if (len) { 85 check_write_size(2); // writing 1 repeated value bytes + 1 control byte 86 // Write sequence control byte (length + type) 87 // Convert length to twos complement to identify it as repeat run 88 FoutBuf[FoutIndex++] = ((len & RLE_MASK_LEN) ^ 0xFFu) + 1u; 89 FoutBuf[FoutIndex++] = value; 90 } 91} 92 93 94static void rle_commit() { 95 96 int idx = 0; 97 98 if (rle_queue_idx > 0) { 99 check_write_size(rle_queue_idx + 1); // writing len bytes + 1 control byte 100 // Write sequence control byte (length + type) 101 FoutBuf[FoutIndex++] = (rle_queue_idx & RLE_MASK_LEN) | RLE_TYPE_RAND; 102 while (rle_queue_idx > 0) { 103 FoutBuf[FoutIndex++] = rle_queued[idx++]; 104 rle_queue_idx--; 105 } 106 } 107 rle_queue_idx = 0; // redundant 108} 109 110 111 112// Convert buffer inBuf to rlecompress rle encoding and write out to outBuf 113// Returns converted length 114uint32_t rlecompress_buf(uint8_t * inBuf, uint32_t size_in, uint8_t ** pp_outBuf, uint32_t size_out) { 115 116 uint8_t last, current; 117 initbufs(inBuf, size_in, pp_outBuf, size_out); 118 119 last = 0; 120 run_len = 0; 121 rle_queue_idx = 0; 122 123 while (FinIndex < Fsize_in) { 124 125 current = FinBuf[FinIndex++]; 126 127 if (current != last) { 128 // The run stopped, if switching to repeat is worthwhile then 129 // flush preceding random data 130 if (run_len > RLE_CHANGE_COST) { 131 rle_commit(); 132 write_run_repeat(run_len, last); 133 } else { 134 // Otherwise treat repeat data as random and queue it 135 // with flushing as needed 136 while(run_len--) { 137 if (rle_queue_idx >= RLE_MAX_BLOCK) { 138 rle_commit(); 139 } 140 rle_queued[rle_queue_idx++] = last; 141 } 142 } 143 run_len = 1; 144 last = current; 145 } 146 else { 147 // Flush pending repeat if max length is encountered 148 if (run_len >= RLE_MAX_BLOCK) { 149 rle_commit(); 150 write_run_repeat(run_len, last); 151 run_len = 0; 152 } 153 run_len++; 154 } 155 156 } 157 158 // If switching to repeat is worthwhile then flush preceding random data 159 if (run_len > RLE_CHANGE_COST) { 160 rle_commit(); 161 write_run_repeat(run_len, last); 162 } else { 163 // Otherwise treat repeat data as random and queue it 164 // with flushing as needed 165 while(run_len--) { 166 if (rle_queue_idx >= RLE_MAX_BLOCK) { 167 rle_commit(); 168 } 169 rle_queued[rle_queue_idx++] = last; 170 } 171 } 172 173 // Flush any trailing data 174 rle_commit(); 175 write_end_of_data(); 176 177 return FoutIndex; 178 179} 180 181 182 183static void write_single_byte(uint8_t data) { 184 185 check_write_size(1); 186 187 FoutBuf[FoutIndex++] = data; 188} 189 190 191static uint8_t read_single_byte(void) { 192 193 if (FinIndex >= Fsize_in) { 194 printf("Error: Read past end of input buffer!\n"); 195 exit(EXIT_FAILURE); 196 } 197 198 return (FinBuf[FinIndex++]); 199} 200 201 202 203// Decompress buffer inBuf from gbcompress rle encoding and write to outBuf 204// Returns converted length 205uint32_t rledecompress_buf(uint8_t * inBuf, uint32_t size_in, uint8_t ** pp_outBuf, uint32_t size_out) { 206 207 initbufs(inBuf, size_in, pp_outBuf, size_out); 208 209 uint8_t token, rle_len, value; 210 211 while (FinIndex < size_in) { 212 213 token = read_single_byte(); 214 215 // Check for EOF token, exit if encountered 216 if (token == RLE_CTRL_END) 217 break; 218 else if ((token & RLE_MASK_TYPE) == RLE_TYPE_RAND) { 219 rle_len = token & RLE_MASK_LEN; 220 while (rle_len--) 221 write_single_byte(read_single_byte()); 222 } 223 else { // if ((token & RLE_MASK_TYPE) == RLE_TYPE_REPEAT) { 224 rle_len = ((token ^ 0xFF) + 1) & RLE_MASK_LEN; // length is two's complement 225 value = read_single_byte(); 226 while (rle_len--) 227 write_single_byte(value); 228 } 229 } 230 231 return FoutIndex; 232}