155 lines
4.3 KiB
C
155 lines
4.3 KiB
C
/*
|
|
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 <assert.h>
|
|
#include <stdbool.h>
|
|
#include <stddef.h>
|
|
#include <stdint.h>
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
|
|
/* 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;
|
|
}
|