/* LZW compression that is required for GIF implementation. https://en.wikipedia.org/wiki/Lempel%E2%80%93Ziv%E2%80%93Welch */ #include "lzw.h" #include "bits.h" #include #include #include #include #include #include /* References: */ /* https://www.w3.org/Graphics/GIF/spec-gif89a.txt */ /* https://www.daubnet.com/en/file-format-gif */ /* After it is reached the table is supposed to be reset */ /* As this is known, the upper boundary of memory used is known */ #define COM_LZW_CODEPOINT_LIMIT 8192 #define COM_TABLE_BIT_WIDTH_LIMIT 12 /* typical GIF impl */ #define COM_LZW_OUTPUT_CAP_GROWTH 256 #define COM_LZW_TABLE_CAP_GROW 128 struct com_lzw_table com_lzw_infer_table(const char *datain, uint32_t sizein) { struct com_lzw_table_entry *table = NULL; uint32_t table_init_size = 0; uint32_t table_size = 0; uint32_t table_cap = 0; /* Infer initial table from the data */ for (uint32_t i = 0; i < sizein && table_init_size < 255; i++) { bool code_found = false; char code = datain[i]; /* Try searching the code */ for (uint32_t t = 0; t < table_init_size; t++) { if (table[t].code == code) { code_found = true; break; } } /* Append non-existing codepoint */ if (!code_found) { if (table_size >= table_cap) { /* TODO: Ref to prev table gets missed, memory leak scenario */ if (!(table = realloc(table, sizeof(struct com_lzw_table_entry) * (table_cap + COM_LZW_TABLE_CAP_GROW)))) goto ERR_ALLOC_INIT_TABLE; table_cap += COM_LZW_TABLE_CAP_GROW; } table[table_size] = (struct com_lzw_table_entry){.code = code}; table_init_size++; table_size++; } } return (struct com_lzw_table){ .table = table, .cap = table_cap, .size = table_size, .init_size = table_init_size, .continuous = false, }; ERR_ALLOC_INIT_TABLE: if (table_cap > 0) free(table); return (struct com_lzw_table){0}; } void com_lzw_free_table(struct com_lzw_table *table) { if (!table->table) return; free(table->table); table->size = 0; table->cap = 0; table->init_size = 0; } /* For GIFs, color should already be collapsed to indices at this point */ bool com_lzw_compress(const struct com_lzw_table *table, const char *datain, uint32_t sizein, char **dataout, uint32_t *sizeout) { assert(datain && dataout && sizeout && sizein > 0); assert(*dataout == NULL && *sizeout == 0); /* TODO: Make sure the table is cleared */ /* Encode step */ uint8_t codesize = com_bits_needed(table->init_size) + 1; assert(codesize < COM_TABLE_BIT_WIDTH_LIMIT); uint32_t cur_table_idx = 0; /* Head table in which vector we should be looking into */ uint32_t feedback = 0; /* How many bytes feeded from datain to the current string */ char *output = NULL; uint32_t output_size = 0; uint32_t output_cap = 0; uint8_t output_bitshift = 0; for (uint32_t i = 0; i < sizein; i++) { char code = datain[i]; if (feedback == 0) { /* Should not be possible not to find the first code */ for (uint32_t t = 0; t < table->init_size; t++) { if (table->table[t].code == code) { cur_table_idx = t; feedback++; } } } else { // for (uint32_t v = 0; v < table->table[cur_table_idx].tree_vector_size; // v++) { // if (table->table[table->table[cur_table_idx].tree_vector[v]].code == // code) { // cur_table_idx = table->table[cur_table_idx].tree_vector[v]; // feedback++; // continue; // } // } /* Emit output, drop the string */ assert(cur_table_idx < (1 << codesize)); assert(cur_table_idx < table->size); if (output_size == output_cap && output_bitshift + codesize > 8) { /* TODO: catch alloc failure */ output = realloc(output, output_cap + COM_LZW_OUTPUT_CAP_GROWTH); output_cap += COM_LZW_OUTPUT_CAP_GROWTH; } // uint16_t code = cur_table_idx; // uint8_t bits_to_write = codesize; // while (bits_to_write > 0) { // bits_to_write -= // } /* Return to prev char as it wasn't processed */ feedback = 0; i--; } } *sizeout = output_size; *dataout = output; return true; }