|
| 1 | +/** |
| 2 | + * This class handles LZW encoding |
| 3 | + * Adapted from Jef Poskanzer's Java port by way of J. M. G. Elliott. |
| 4 | + * @author Kevin Weiner (original Java version - kweiner@fmsware.com) |
| 5 | + * @author Thibault Imbert (AS3 version - bytearray.org) |
| 6 | + * @author Kevin Kwok (JavaScript version - https://github.com/antimatter15/jsgif) |
| 7 | + * @version 0.1 AS3 implementation |
| 8 | + */ |
| 9 | + |
| 10 | +LZWEncoder = function() { |
| 11 | + |
| 12 | + var exports = {}; |
| 13 | + var EOF = -1; |
| 14 | + var imgW; |
| 15 | + var imgH; |
| 16 | + var pixAry; |
| 17 | + var initCodeSize; |
| 18 | + var remaining; |
| 19 | + var curPixel; |
| 20 | + |
| 21 | + // GIFCOMPR.C - GIF Image compression routines |
| 22 | + // Lempel-Ziv compression based on 'compress'. GIF modifications by |
| 23 | + // David Rowley (mgardi@watdcsu.waterloo.edu) |
| 24 | + // General DEFINEs |
| 25 | + |
| 26 | + var BITS = 12; |
| 27 | + var HSIZE = 5003; // 80% occupancy |
| 28 | + |
| 29 | + // GIF Image compression - modified 'compress' |
| 30 | + // Based on: compress.c - File compression ala IEEE Computer, June 1984. |
| 31 | + // By Authors: Spencer W. Thomas (decvax!harpo!utah-cs!utah-gr!thomas) |
| 32 | + // Jim McKie (decvax!mcvax!jim) |
| 33 | + // Steve Davies (decvax!vax135!petsd!peora!srd) |
| 34 | + // Ken Turkowski (decvax!decwrl!turtlevax!ken) |
| 35 | + // James A. Woods (decvax!ihnp4!ames!jaw) |
| 36 | + // Joe Orost (decvax!vax135!petsd!joe) |
| 37 | + |
| 38 | + var n_bits; // number of bits/code |
| 39 | + var maxbits = BITS; // user settable max # bits/code |
| 40 | + var maxcode; // maximum code, given n_bits |
| 41 | + var maxmaxcode = 1 << BITS; // should NEVER generate this code |
| 42 | + var htab = []; |
| 43 | + var codetab = []; |
| 44 | + var hsize = HSIZE; // for dynamic table sizing |
| 45 | + var free_ent = 0; // first unused entry |
| 46 | + |
| 47 | + // block compression parameters -- after all codes are used up, |
| 48 | + // and compression rate changes, start over. |
| 49 | + |
| 50 | + var clear_flg = false; |
| 51 | + |
| 52 | + // Algorithm: use open addressing double hashing (no chaining) on the |
| 53 | + // prefix code / next character combination. We do a variant of Knuth's |
| 54 | + // algorithm D (vol. 3, sec. 6.4) along with G. Knott's relatively-prime |
| 55 | + // secondary probe. Here, the modular division first probe is gives way |
| 56 | + // to a faster exclusive-or manipulation. Also do block compression with |
| 57 | + // an adaptive reset, whereby the code table is cleared when the compression |
| 58 | + // ratio decreases, but after the table fills. The variable-length output |
| 59 | + // codes are re-sized at this point, and a special CLEAR code is generated |
| 60 | + // for the decompressor. Late addition: construct the table according to |
| 61 | + // file size for noticeable speed improvement on small files. Please direct |
| 62 | + // questions about this implementation to ames!jaw. |
| 63 | + |
| 64 | + var g_init_bits; |
| 65 | + var ClearCode; |
| 66 | + var EOFCode; |
| 67 | + |
| 68 | + // output |
| 69 | + // Output the given code. |
| 70 | + // Inputs: |
| 71 | + // code: A n_bits-bit integer. If == -1, then EOF. This assumes |
| 72 | + // that n_bits =< wordsize - 1. |
| 73 | + // Outputs: |
| 74 | + // Outputs code to the file. |
| 75 | + // Assumptions: |
| 76 | + // Chars are 8 bits long. |
| 77 | + // Algorithm: |
| 78 | + // Maintain a BITS character long buffer (so that 8 codes will |
| 79 | + // fit in it exactly). Use the VAX insv instruction to insert each |
| 80 | + // code in turn. When the buffer fills up empty it and start over. |
| 81 | + |
| 82 | + var cur_accum = 0; |
| 83 | + var cur_bits = 0; |
| 84 | + var masks = [0x0000, 0x0001, 0x0003, 0x0007, 0x000F, 0x001F, 0x003F, 0x007F, 0x00FF, 0x01FF, 0x03FF, 0x07FF, 0x0FFF, 0x1FFF, 0x3FFF, 0x7FFF, 0xFFFF]; |
| 85 | + |
| 86 | + // Number of characters so far in this 'packet' |
| 87 | + var a_count; |
| 88 | + |
| 89 | + // Define the storage for the packet accumulator |
| 90 | + var accum = []; |
| 91 | + |
| 92 | + var LZWEncoder = exports.LZWEncoder = function LZWEncoder(width, height, pixels, color_depth) { |
| 93 | + imgW = width; |
| 94 | + imgH = height; |
| 95 | + pixAry = pixels; |
| 96 | + initCodeSize = Math.max(2, color_depth); |
| 97 | + }; |
| 98 | + |
| 99 | + // Add a character to the end of the current packet, and if it is 254 |
| 100 | + // characters, flush the packet to disk. |
| 101 | + var char_out = function char_out(c, outs) { |
| 102 | + accum[a_count++] = c; |
| 103 | + if (a_count >= 254) flush_char(outs); |
| 104 | + }; |
| 105 | + |
| 106 | + // Clear out the hash table |
| 107 | + // table clear for block compress |
| 108 | + |
| 109 | + var cl_block = function cl_block(outs) { |
| 110 | + cl_hash(hsize); |
| 111 | + free_ent = ClearCode + 2; |
| 112 | + clear_flg = true; |
| 113 | + output(ClearCode, outs); |
| 114 | + }; |
| 115 | + |
| 116 | + // reset code table |
| 117 | + var cl_hash = function cl_hash(hsize) { |
| 118 | + for (var i = 0; i < hsize; ++i) htab[i] = -1; |
| 119 | + }; |
| 120 | + |
| 121 | + var compress = exports.compress = function compress(init_bits, outs) { |
| 122 | + |
| 123 | + var fcode; |
| 124 | + var i; /* = 0 */ |
| 125 | + var c; |
| 126 | + var ent; |
| 127 | + var disp; |
| 128 | + var hsize_reg; |
| 129 | + var hshift; |
| 130 | + |
| 131 | + // Set up the globals: g_init_bits - initial number of bits |
| 132 | + g_init_bits = init_bits; |
| 133 | + |
| 134 | + // Set up the necessary values |
| 135 | + clear_flg = false; |
| 136 | + n_bits = g_init_bits; |
| 137 | + maxcode = MAXCODE(n_bits); |
| 138 | + |
| 139 | + ClearCode = 1 << (init_bits - 1); |
| 140 | + EOFCode = ClearCode + 1; |
| 141 | + free_ent = ClearCode + 2; |
| 142 | + |
| 143 | + a_count = 0; // clear packet |
| 144 | + |
| 145 | + ent = nextPixel(); |
| 146 | + |
| 147 | + hshift = 0; |
| 148 | + for (fcode = hsize; fcode < 65536; fcode *= 2) |
| 149 | + ++hshift; |
| 150 | + hshift = 8 - hshift; // set hash code range bound |
| 151 | + |
| 152 | + hsize_reg = hsize; |
| 153 | + cl_hash(hsize_reg); // clear hash table |
| 154 | + |
| 155 | + output(ClearCode, outs); |
| 156 | + |
| 157 | + outer_loop: while ((c = nextPixel()) != EOF) { |
| 158 | + fcode = (c << maxbits) + ent; |
| 159 | + i = (c << hshift) ^ ent; // xor hashing |
| 160 | + |
| 161 | + if (htab[i] == fcode) { |
| 162 | + ent = codetab[i]; |
| 163 | + continue; |
| 164 | + } |
| 165 | + |
| 166 | + else if (htab[i] >= 0) { // non-empty slot |
| 167 | + |
| 168 | + disp = hsize_reg - i; // secondary hash (after G. Knott) |
| 169 | + if (i === 0) disp = 1; |
| 170 | + |
| 171 | + do { |
| 172 | + if ((i -= disp) < 0) |
| 173 | + i += hsize_reg; |
| 174 | + |
| 175 | + if (htab[i] == fcode) { |
| 176 | + ent = codetab[i]; |
| 177 | + continue outer_loop; |
| 178 | + } |
| 179 | + } while (htab[i] >= 0); |
| 180 | + } |
| 181 | + |
| 182 | + output(ent, outs); |
| 183 | + ent = c; |
| 184 | + if (free_ent < maxmaxcode) { |
| 185 | + codetab[i] = free_ent++; // code -> hashtable |
| 186 | + htab[i] = fcode; |
| 187 | + } |
| 188 | + else cl_block(outs); |
| 189 | + } |
| 190 | + |
| 191 | + // Put out the final code. |
| 192 | + output(ent, outs); |
| 193 | + output(EOFCode, outs); |
| 194 | + }; |
| 195 | + |
| 196 | + // ---------------------------------------------------------------------------- |
| 197 | + var encode = exports.encode = function encode(os) { |
| 198 | + os.writeByte(initCodeSize); // write "initial code size" byte |
| 199 | + remaining = imgW * imgH; // reset navigation variables |
| 200 | + curPixel = 0; |
| 201 | + compress(initCodeSize + 1, os); // compress and write the pixel data |
| 202 | + os.writeByte(0); // write block terminator |
| 203 | + }; |
| 204 | + |
| 205 | + // Flush the packet to disk, and reset the accumulator |
| 206 | + var flush_char = function flush_char(outs) { |
| 207 | + if (a_count > 0) { |
| 208 | + outs.writeByte(a_count); |
| 209 | + outs.writeBytes(accum, 0, a_count); |
| 210 | + a_count = 0; |
| 211 | + } |
| 212 | + }; |
| 213 | + |
| 214 | + var MAXCODE = function MAXCODE(n_bits) { |
| 215 | + return (1 << n_bits) - 1; |
| 216 | + }; |
| 217 | + |
| 218 | + // ---------------------------------------------------------------------------- |
| 219 | + // Return the next pixel from the image |
| 220 | + // ---------------------------------------------------------------------------- |
| 221 | + |
| 222 | + var nextPixel = function nextPixel() { |
| 223 | + if (remaining === 0) return EOF; |
| 224 | + --remaining; |
| 225 | + var pix = pixAry[curPixel++]; |
| 226 | + return pix & 0xff; |
| 227 | + }; |
| 228 | + |
| 229 | + var output = function output(code, outs) { |
| 230 | + |
| 231 | + cur_accum &= masks[cur_bits]; |
| 232 | + |
| 233 | + if (cur_bits > 0) cur_accum |= (code << cur_bits); |
| 234 | + else cur_accum = code; |
| 235 | + |
| 236 | + cur_bits += n_bits; |
| 237 | + |
| 238 | + while (cur_bits >= 8) { |
| 239 | + char_out((cur_accum & 0xff), outs); |
| 240 | + cur_accum >>= 8; |
| 241 | + cur_bits -= 8; |
| 242 | + } |
| 243 | + |
| 244 | + // If the next entry is going to be too big for the code size, |
| 245 | + // then increase it, if possible. |
| 246 | + |
| 247 | + if (free_ent > maxcode || clear_flg) { |
| 248 | + |
| 249 | + if (clear_flg) { |
| 250 | + |
| 251 | + maxcode = MAXCODE(n_bits = g_init_bits); |
| 252 | + clear_flg = false; |
| 253 | + |
| 254 | + } else { |
| 255 | + |
| 256 | + ++n_bits; |
| 257 | + if (n_bits == maxbits) maxcode = maxmaxcode; |
| 258 | + else maxcode = MAXCODE(n_bits); |
| 259 | + } |
| 260 | + } |
| 261 | + |
| 262 | + if (code == EOFCode) { |
| 263 | + |
| 264 | + // At EOF, write the rest of the buffer. |
| 265 | + while (cur_bits > 0) { |
| 266 | + char_out((cur_accum & 0xff), outs); |
| 267 | + cur_accum >>= 8; |
| 268 | + cur_bits -= 8; |
| 269 | + } |
| 270 | + |
| 271 | + flush_char(outs); |
| 272 | + } |
| 273 | + }; |
| 274 | + |
| 275 | + LZWEncoder.apply(this, arguments); |
| 276 | + return exports; |
| 277 | +}; |
0 commit comments