Details | Last modification | View Log | RSS feed
Rev | Author | Line No. | Line |
---|---|---|---|
2 | daniel-mar | 1 | /** |
2 | * LZW Encoder for Imagination Pilots Entertainment 32-bit games (IPE32) |
||
3 | * - Where's Waldo? Exploring Geography |
||
4 | * - Eraser Turnabout by Imagination Pilots |
||
5 | * - Virtual K'Nex by Imagination Pilots |
||
6 | * ART file packer and unpacker by Daniel Marschall, ViaThinkSoft (C) 2018 |
||
7 | * Revision: 2018-02-15 |
||
8 | * |
||
9 | * Based on : Basic LZW Data Compression program published in DDJ October 1989 issue. |
||
10 | * by Mark R. Nelson |
||
11 | * http://collaboration.cmc.ec.gc.ca/science/rpn/biblio/ddj/Website/articles/DDJ/1989/8910/8910b/8910b.htm |
||
12 | * Updated by: Shawn M. Regan, January 1990 |
||
13 | * http://mirror.bagelwood.com/textfiles/computers/regan.lst |
||
14 | * Updated by: Daniel Marschall, 11 February 2018 |
||
15 | * https://misc.daniel-marschall.de/code/c/lzw.c |
||
16 | * Changed for IPE32: - Simplified |
||
17 | * - Thread safe |
||
18 | * - MAX_BITS = 13 |
||
19 | **/ |
||
20 | |||
21 | #include <stdio.h> |
||
22 | #include <stdlib.h> |
||
23 | #include <stdint.h> |
||
24 | |||
25 | #include "utils.h" |
||
26 | #include "ipe32_lzw_encoder.h" |
||
27 | |||
28 | #define INIT_BITS 9 |
||
29 | #define MAX_BITS 13 /* Do not exceed 14 with this program */ |
||
30 | #define HASHING_SHIFT (MAX_BITS - 8) |
||
31 | |||
32 | #if MAX_BITS == 14 /* Set the table size. Must be a prime */ |
||
33 | #define TABLE_SIZE 18041 /* number somewhat larger than 2^MAX_BITS.*/ |
||
34 | #elif MAX_BITS == 13 |
||
35 | #define TABLE_SIZE 9029 |
||
36 | #else |
||
37 | #define TABLE_SIZE 5021 |
||
38 | #endif |
||
39 | |||
40 | #define CLEAR_TABLE 256 /* Code to flush the string table */ |
||
41 | #define TERMINATOR 257 /* To mark EOF Condition, instead of MAX_VALUE */ |
||
42 | #define FIRST_CODE 258 /* First available code for code_value table */ |
||
43 | #define CHECK_TIME 100 /* Check comp ratio every CHECK_TIME chars input */ |
||
44 | |||
45 | #define MAXVAL(n) (( 1 <<( n )) -1) /* max_value formula macro */ |
||
46 | |||
47 | unsigned int find_match(Ipe32LZWEncoder *encoder, int hash_prefix, unsigned int hash_character) { |
||
48 | int index, offset; |
||
49 | |||
50 | index = (hash_character << HASHING_SHIFT) ^ hash_prefix; |
||
51 | |||
52 | offset = (index == 0) ? 1 : TABLE_SIZE - index; |
||
53 | |||
54 | while (1) { |
||
55 | if (encoder->code_value[index] == -1) { |
||
56 | return(index); |
||
57 | } |
||
58 | if (encoder->prefix_code[index] == hash_prefix && encoder->append_character[index] == hash_character) { |
||
59 | return(index); |
||
60 | } |
||
61 | index -= offset; |
||
62 | if (index < 0) { |
||
63 | index += TABLE_SIZE; |
||
64 | } |
||
65 | } |
||
66 | } |
||
67 | |||
68 | void output_code(Ipe32LZWEncoder *encoder, unsigned int code, unsigned char* outBuf, size_t* compressedPos) { |
||
69 | encoder->output_bit_buffer |= (uint32_t) code << (32 - encoder->num_bits - encoder->output_bit_count); |
||
70 | encoder->output_bit_count += encoder->num_bits; |
||
71 | while (encoder->output_bit_count >= 8) { |
||
72 | outBuf[(*compressedPos)] = encoder->output_bit_buffer >> 24; // putc(output_bit_buffer >> 24, output); |
||
73 | (*compressedPos)++; |
||
74 | |||
75 | encoder->output_bit_buffer <<= 8; |
||
76 | encoder->output_bit_count -= 8; |
||
77 | encoder->bytes_out++; |
||
78 | } |
||
79 | } |
||
80 | |||
81 | void reset_output_buffer(Ipe32LZWEncoder *encoder) { |
||
82 | encoder->output_bit_count = 0; |
||
83 | encoder->output_bit_buffer = 0; |
||
84 | } |
||
85 | |||
86 | void ipe32lzw_reset_encoder(Ipe32LZWEncoder *encoder) { |
||
87 | encoder->num_bits = INIT_BITS; |
||
88 | encoder->max_code = MAXVAL(encoder->num_bits); /* Initialize max_value & max_code */ |
||
89 | encoder->bytes_in = 0; |
||
90 | encoder->bytes_out = 0; |
||
91 | encoder->checkpoint = CHECK_TIME; |
||
92 | |||
93 | // For some reason, the output buffer doesn't get correctly flushed when |
||
94 | // a new compression is started. So I made the static symbols global |
||
95 | // and clear them here. |
||
96 | reset_output_buffer(encoder); |
||
97 | } |
||
98 | |||
99 | // Returns: Bytes written, or -1 if compression failed |
||
100 | int ipe32lzw_encode(Ipe32LZWEncoder *encoder, unsigned char* compressedData, const size_t compressedBufLen, unsigned char* uncompressedData, const size_t uncompressedSize) { |
||
101 | unsigned int next_code=FIRST_CODE; |
||
102 | unsigned int index; |
||
103 | int i, /* All purpose integer */ |
||
104 | ratio_new, /* New compression ratio as a percentage */ |
||
105 | ratio_old=100; /* Original ratio at 100% */ |
||
106 | |||
107 | ipe32lzw_reset_encoder(encoder); |
||
108 | |||
109 | for (i=0; i<TABLE_SIZE; ++i) { /* Initialize the string table first */ |
||
110 | encoder->code_value[i]=-1; |
||
111 | } |
||
112 | |||
113 | /* Get the first code */ |
||
114 | if (compressedBufLen == 0) return -1; |
||
115 | unsigned int string_code = uncompressedData[0]; //string_code=getc(input); |
||
116 | size_t uncompressedPos = 1; |
||
117 | size_t compressedPos = 0; |
||
118 | #define OUTPUT(code) { if (compressedPos == compressedBufLen) return -1; output_code(encoder,code,compressedData,&compressedPos); } |
||
119 | |||
120 | /* This is the main compression loop. Notice when the table is full we try |
||
121 | * to increment the code size. Only when num_bits == MAX_BITS and the code |
||
122 | * value table is full do we start to monitor the compression ratio. |
||
123 | */ |
||
124 | while (uncompressedPos < uncompressedSize) { // while((character=getc(input)) != (unsigned)EOF) { |
||
125 | unsigned int character = uncompressedData[uncompressedPos++]; |
||
126 | |||
127 | ++encoder->bytes_in; |
||
128 | index=find_match(encoder,string_code,character); |
||
129 | if (encoder->code_value[index] != -1) { |
||
130 | string_code=encoder->code_value[index]; |
||
131 | } else { |
||
132 | if (next_code <= encoder->max_code) { |
||
133 | encoder->code_value[index]=next_code++; |
||
134 | encoder->prefix_code[index]=string_code; |
||
135 | encoder->append_character[index]=character; |
||
136 | } |
||
137 | OUTPUT(string_code); /* Send out current code */ |
||
138 | string_code=character; |
||
139 | if (next_code > encoder->max_code) { /* Is table Full? */ |
||
140 | if (encoder->num_bits < MAX_BITS) { /* Any more bits? */ |
||
141 | encoder->max_code = MAXVAL(++encoder->num_bits); /* Increment code size then */ |
||
142 | } else if (encoder->bytes_in > encoder->checkpoint) { /* At checkpoint? */ |
||
143 | if (encoder->num_bits == MAX_BITS) { |
||
144 | ratio_new = encoder->bytes_out*100/encoder->bytes_in; /* New compression ratio */ |
||
145 | if (ratio_new > ratio_old) { /* Has ratio degraded? */ |
||
146 | OUTPUT(CLEAR_TABLE); /* YES,flush string table */ |
||
147 | encoder->num_bits=INIT_BITS; |
||
148 | next_code=FIRST_CODE; /* Reset to FIRST_CODE */ |
||
149 | encoder->max_code = MAXVAL(encoder->num_bits); /* Re-Initialize this stuff */ |
||
150 | encoder->bytes_in = encoder->bytes_out = 0; |
||
151 | ratio_old = 100; /* Reset compression ratio */ |
||
152 | for (i=0; i<TABLE_SIZE; ++i) { /* Reset code value array */ |
||
153 | encoder->code_value[i]=-1; |
||
154 | } |
||
155 | } else { /* NO, then save new */ |
||
156 | ratio_old = ratio_new; /* compression ratio */ |
||
157 | } |
||
158 | } |
||
159 | encoder->checkpoint = encoder->bytes_in + CHECK_TIME; /* Set new checkpoint */ |
||
160 | } |
||
161 | } |
||
162 | } |
||
163 | } |
||
164 | OUTPUT(string_code); /* Output the last code */ |
||
165 | if (next_code == encoder->max_code) { /* Handles special case for bit */ |
||
166 | ++encoder->num_bits; /* increment on EOF */ |
||
167 | } |
||
168 | OUTPUT(TERMINATOR); /* Output the end of buffer code */ |
||
169 | OUTPUT(0); /* Flush the output buffer */ |
||
170 | OUTPUT(0); |
||
171 | OUTPUT(0); |
||
172 | |||
173 | return compressedPos; |
||
174 | } |
||
175 | |||
176 | void ipe32lzw_init_encoder(Ipe32LZWEncoder *encoder) { |
||
177 | /* The three buffers for the compression phase. */ |
||
178 | encoder->code_value=malloc(TABLE_SIZE*sizeof(unsigned int)); |
||
179 | encoder->prefix_code=malloc(TABLE_SIZE*sizeof(unsigned int)); |
||
180 | encoder->append_character=malloc(TABLE_SIZE*sizeof(unsigned char)); |
||
181 | ipe32lzw_reset_encoder(encoder); |
||
182 | } |
||
183 | |||
184 | void ipe32lzw_free_encoder(Ipe32LZWEncoder *encoder) { |
||
185 | free(encoder->code_value); /* Needed only for compression */ |
||
186 | free(encoder->prefix_code); |
||
187 | free(encoder->append_character); |
||
188 | } |
||
189 | |||
190 | Ipe32LZWEncoder* new_ipe32lzw_encoder(void) { |
||
191 | return (Ipe32LZWEncoder*)app_zero_alloc(sizeof(Ipe32LZWEncoder)); |
||
192 | } |