17 | *
18 | * Example: 19 | * AnimatedGifEncoder e = new AnimatedGifEncoder(); 20 | * e.start(outputFileName); 21 | * e.setDelay(1000); // 1 frame per sec 22 | * e.addFrame(image1); 23 | * e.addFrame(image2); 24 | * e.finish(); 25 | *26 | *
27 | * No copyright asserted on the source code of this class. May be used for any
28 | * purpose, however, refer to the Unisys LZW patent for restrictions on use of
29 | * the associated LZWEncoder class. Please forward any corrections to
30 | * kweiner@fmsware.com.
31 | *
32 | * @author Kevin Weiner, FM Software
33 | * @version 1.03 November 2003
34 | */
35 |
36 | public class GifEncoder {
37 | private static final String TAG = "AnimatedGifEncoder";
38 |
39 | // The minimum % of an images pixels that must be transparent for us to set a transparent index automatically.
40 | private static final double MIN_TRANSPARENT_PERCENTAGE = 4d;
41 |
42 | protected int width; // image size
43 |
44 | protected int height;
45 |
46 | private Integer transparent = null; // transparent color if given
47 |
48 | private boolean hasTransparentPixels;
49 |
50 | protected int transIndex; // transparent index in color table
51 | protected int repeat = -1; // no repeat
52 | protected int delay = 0; // frame delay (hundredths)
53 | protected boolean started = false; // ready to output frames
54 | protected OutputStream out;
55 | protected Bitmap image; // current frame
56 | protected byte[] pixels; // BGR byte array from frame
57 | protected byte[] indexedPixels; // converted frame indexed to palette
58 | protected int colorDepth; // number of bit planes
59 | protected byte[] colorTab; // RGB palette
60 | protected boolean[] usedEntry = new boolean[256]; // active palette entries
61 | protected int palSize = 7; // color table size (bits-1)
62 | protected int dispose = -1; // disposal code (-1 = use default)
63 | protected boolean closeStream = false; // close stream when finished
64 | protected boolean firstFrame = true;
65 | protected boolean sizeSet = false; // if false, get size from first frame
66 | protected int sample = 10; // default sample interval for quantizer
67 |
68 | /**
69 | * Sets the delay time between each frame, or changes it for subsequent frames
70 | * (applies to last frame added).
71 | *
72 | * @param ms int delay time in milliseconds
73 | */
74 | public void setDelay(int ms) {
75 | delay = Math.round(ms / 10.0f);
76 | }
77 |
78 | /**
79 | * Sets the GIF frame disposal code for the last added frame and any
80 | * subsequent frames. Default is 0 if no transparent color has been set,
81 | * otherwise 2.
82 | *
83 | * @param code int disposal code.
84 | */
85 | public void setDispose(int code) {
86 | if (code >= 0) {
87 | dispose = code;
88 | }
89 | }
90 |
91 | /**
92 | * Sets the number of times the set of GIF frames should be played. Default is
93 | * 1; 0 means play indefinitely. Must be invoked before the first image is
94 | * added.
95 | *
96 | * @param iter int number of iterations.
97 | */
98 | public void setRepeat(int iter) {
99 | if (iter >= 0) {
100 | repeat = iter;
101 | }
102 | }
103 |
104 | /**
105 | * Sets the transparent color for the last added frame and any subsequent
106 | * frames. Since all colors are subject to modification in the quantization
107 | * process, the color in the final palette for each frame closest to the given
108 | * color becomes the transparent color for that frame. May be set to null to
109 | * indicate no transparent color.
110 | *
111 | * @param color Color to be treated as transparent on display.
112 | */
113 | public void setTransparent(int color) {
114 | transparent = color;
115 | }
116 |
117 | /**
118 | * Adds next GIF frame. The frame is not written immediately, but is actually
119 | * deferred until the next frame is received so that timing data can be
120 | * inserted. Invoking finish()
flushes all frames. If
121 | * setSize
was not invoked, the size of the first image is used
122 | * for all subsequent frames.
123 | *
124 | * @param im BufferedImage containing frame to write.
125 | * @return true if successful.
126 | */
127 | public boolean addFrame(Bitmap im) {
128 | if ((im == null) || !started) {
129 | return false;
130 | }
131 | boolean ok = true;
132 | try {
133 | if (!sizeSet) {
134 | // use first frame's size
135 | setSize(im.getWidth(), im.getHeight());
136 | }
137 | image = im;
138 | getImagePixels(); // convert to correct format if necessary
139 | analyzePixels(); // build color table & map pixels
140 | if (firstFrame) {
141 | writeLSD(); // logical screen descriptior
142 | writePalette(); // global color table
143 | if (repeat >= 0) {
144 | // use NS app extension to indicate reps
145 | writeNetscapeExt();
146 | }
147 | }
148 | writeGraphicCtrlExt(); // write graphic control extension
149 | writeImageDesc(); // image descriptor
150 | if (!firstFrame) {
151 | writePalette(); // local color table
152 | }
153 | writePixels(); // encode and write pixel data
154 | firstFrame = false;
155 | } catch (IOException e) {
156 | ok = false;
157 | }
158 |
159 | return ok;
160 | }
161 |
162 | /**
163 | * Flushes any pending data and closes output file. If writing to an
164 | * OutputStream, the stream is not closed.
165 | */
166 | public boolean finish() {
167 | if (!started)
168 | return false;
169 | boolean ok = true;
170 | started = false;
171 | try {
172 | out.write(0x3b); // gif trailer
173 | out.flush();
174 | if (closeStream) {
175 | out.close();
176 | }
177 | } catch (IOException e) {
178 | ok = false;
179 | }
180 |
181 | // reset for subsequent use
182 | transIndex = 0;
183 | out = null;
184 | image = null;
185 | pixels = null;
186 | indexedPixels = null;
187 | colorTab = null;
188 | closeStream = false;
189 | firstFrame = true;
190 |
191 | return ok;
192 | }
193 |
194 | /**
195 | * Sets frame rate in frames per second. Equivalent to
196 | * setDelay(1000/fps)
.
197 | *
198 | * @param fps float frame rate (frames per second)
199 | */
200 | public void setFrameRate(float fps) {
201 | if (fps != 0f) {
202 | delay = Math.round(100f / fps);
203 | }
204 | }
205 |
206 | /**
207 | * Sets quality of color quantization (conversion of images to the maximum 256
208 | * colors allowed by the GIF specification). Lower values (minimum = 1)
209 | * produce better colors, but slow processing significantly. 10 is the
210 | * default, and produces good color mapping at reasonable speeds. Values
211 | * greater than 20 do not yield significant improvements in speed.
212 | *
213 | * @param quality int greater than 0.
214 | */
215 | public void setQuality(int quality) {
216 | if (quality < 1)
217 | quality = 1;
218 | sample = quality;
219 | }
220 |
221 | /**
222 | * Sets the GIF frame size. The default size is the size of the first frame
223 | * added if this method is not invoked.
224 | *
225 | * @param w int frame width.
226 | * @param h int frame width.
227 | */
228 | public void setSize(int w, int h) {
229 | if (started && !firstFrame)
230 | return;
231 | width = w;
232 | height = h;
233 | if (width < 1)
234 | width = 320;
235 | if (height < 1)
236 | height = 240;
237 | sizeSet = true;
238 | }
239 |
240 | /**
241 | * Initiates GIF file creation on the given stream. The stream is not closed
242 | * automatically.
243 | *
244 | * @param os OutputStream on which GIF images are written.
245 | * @return false if initial write failed.
246 | */
247 | public boolean start(OutputStream os) {
248 | if (os == null)
249 | return false;
250 | boolean ok = true;
251 | closeStream = false;
252 | out = os;
253 | try {
254 | writeString("GIF89a"); // header
255 | } catch (IOException e) {
256 | ok = false;
257 | }
258 | return started = ok;
259 | }
260 |
261 | /**
262 | * Initiates writing of a GIF file with the specified name.
263 | *
264 | * @param file String containing output file name.
265 | * @return false if open or initial write failed.
266 | */
267 | public boolean start(String file) {
268 | boolean ok = true;
269 | try {
270 | out = new BufferedOutputStream(new FileOutputStream(file));
271 | ok = start(out);
272 | closeStream = true;
273 | } catch (IOException e) {
274 | e.printStackTrace();
275 | ok = false;
276 | }
277 | return started = ok;
278 | }
279 |
280 | /**
281 | * Analyzes image colors and creates color map.
282 | */
283 | protected void analyzePixels() {
284 | int len = pixels.length;
285 | int nPix = len / 3;
286 | indexedPixels = new byte[nPix];
287 | NeuQuant nq = new NeuQuant(pixels, len, sample);
288 | // initialize quantizer
289 | colorTab = nq.process(); // create reduced palette
290 | // convert map from BGR to RGB
291 | for (int i = 0; i < colorTab.length; i += 3) {
292 | byte temp = colorTab[i];
293 | colorTab[i] = colorTab[i + 2];
294 | colorTab[i + 2] = temp;
295 | usedEntry[i / 3] = false;
296 | }
297 | // map image pixels to new palette
298 | int k = 0;
299 | for (int i = 0; i < nPix; i++) {
300 | int index = nq.map(pixels[k++] & 0xff, pixels[k++] & 0xff, pixels[k++] & 0xff);
301 | usedEntry[index] = true;
302 | indexedPixels[i] = (byte) index;
303 | }
304 | pixels = null;
305 | colorDepth = 8;
306 | palSize = 7;
307 | // get closest match to transparent color if specified
308 | if (transparent != null) {
309 | transIndex = findClosest(transparent);
310 | } else if (hasTransparentPixels) {
311 | transIndex = findClosest(Color.TRANSPARENT);
312 | }
313 | }
314 |
315 | /**
316 | * Returns index of palette color closest to c
317 | */
318 | private int findClosest(int color) {
319 | if (colorTab == null)
320 | return -1;
321 | int r = Color.red(color);
322 | int g = Color.green(color);
323 | int b = Color.blue(color);
324 | int minpos = 0;
325 | int dmin = 256 * 256 * 256;
326 | int len = colorTab.length;
327 | for (int i = 0; i < len; ) {
328 | int dr = r - (colorTab[i++] & 0xff);
329 | int dg = g - (colorTab[i++] & 0xff);
330 | int db = b - (colorTab[i] & 0xff);
331 | int d = dr * dr + dg * dg + db * db;
332 | int index = i / 3;
333 | if (usedEntry[index] && (d < dmin)) {
334 | dmin = d;
335 | minpos = index;
336 | }
337 | i++;
338 | }
339 | return minpos;
340 | }
341 |
342 | /**
343 | * Extracts image pixels into byte array "pixels"
344 | */
345 | protected void getImagePixels() {
346 | int w = image.getWidth();
347 | int h = image.getHeight();
348 |
349 | if ((w != width) || (h != height)) {
350 | // create new image with right size/format
351 | Bitmap temp = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
352 | Canvas canvas = new Canvas(temp);
353 | canvas.drawBitmap(temp, 0, 0, null);
354 | image = temp;
355 | }
356 | int[] pixelsInt = new int[w * h];
357 | image.getPixels(pixelsInt, 0, w, 0, 0, w, h);
358 |
359 | // The algorithm requires 3 bytes per pixel as RGB.
360 | pixels = new byte[pixelsInt.length * 3];
361 |
362 | int pixelsIndex = 0;
363 | hasTransparentPixels = false;
364 | int totalTransparentPixels = 0;
365 | for (final int pixel : pixelsInt) {
366 | if (pixel == Color.TRANSPARENT) {
367 | totalTransparentPixels++;
368 | }
369 | pixels[pixelsIndex++] = (byte) (pixel & 0xFF);
370 | pixels[pixelsIndex++] = (byte) ((pixel >> 8) & 0xFF);
371 | pixels[pixelsIndex++] = (byte) ((pixel >> 16) & 0xFF);
372 | }
373 |
374 | double transparentPercentage = 100 * totalTransparentPixels / (double) pixelsInt.length;
375 | // Assume images with greater where more than n% of the pixels are transparent actually have transparency.
376 | // See issue #214.
377 | hasTransparentPixels = transparentPercentage > MIN_TRANSPARENT_PERCENTAGE;
378 | if (Log.isLoggable(TAG, Log.DEBUG)) {
379 | Log.d(TAG, "got pixels for frame with " + transparentPercentage + "% transparent pixels");
380 | }
381 | }
382 |
383 | /**
384 | * Writes Graphic Control Extension
385 | */
386 | protected void writeGraphicCtrlExt() throws IOException {
387 | out.write(0x21); // extension introducer
388 | out.write(0xf9); // GCE label
389 | out.write(4); // data block size
390 | int transp, disp;
391 | if (transparent == null && !hasTransparentPixels) {
392 | transp = 0;
393 | disp = 0; // dispose = no action
394 | } else {
395 | transp = 1;
396 | disp = 2; // force clear if using transparent color
397 | }
398 | if (dispose >= 0) {
399 | disp = dispose & 7; // user override
400 | }
401 | disp <<= 2;
402 |
403 | // packed fields
404 | out.write(0 | // 1:3 reserved
405 | disp | // 4:6 disposal
406 | 0 | // 7 user input - 0 = none
407 | transp); // 8 transparency flag
408 |
409 | writeShort(delay); // delay x 1/100 sec
410 | out.write(transIndex); // transparent color index
411 | out.write(0); // block terminator
412 | }
413 |
414 | /**
415 | * Writes Image Descriptor
416 | */
417 | protected void writeImageDesc() throws IOException {
418 | out.write(0x2c); // image separator
419 | writeShort(0); // image position x,y = 0,0
420 | writeShort(0);
421 | writeShort(width); // image size
422 | writeShort(height);
423 | // packed fields
424 | if (firstFrame) {
425 | // no LCT - GCT is used for first (or only) frame
426 | out.write(0);
427 | } else {
428 | // specify normal LCT
429 | out.write(0x80 | // 1 local color table 1=yes
430 | 0 | // 2 interlace - 0=no
431 | 0 | // 3 sorted - 0=no
432 | 0 | // 4-5 reserved
433 | palSize); // 6-8 size of color table
434 | }
435 | }
436 |
437 | /**
438 | * Writes Logical Screen Descriptor
439 | */
440 | protected void writeLSD() throws IOException {
441 | // logical screen size
442 | writeShort(width);
443 | writeShort(height);
444 | // packed fields
445 | out.write((0x80 | // 1 : global color table flag = 1 (gct used)
446 | 0x70 | // 2-4 : color resolution = 7
447 | 0x00 | // 5 : gct sort flag = 0
448 | palSize)); // 6-8 : gct size
449 | out.write(0); // background color index
450 | out.write(0); // pixel aspect ratio - assume 1:1
451 | }
452 |
453 | /**
454 | * Writes Netscape application extension to define repeat count.
455 | */
456 | protected void writeNetscapeExt() throws IOException {
457 | out.write(0x21); // extension introducer
458 | out.write(0xff); // app extension label
459 | out.write(11); // block size
460 | writeString("NETSCAPE" + "2.0"); // app id + auth code
461 | out.write(3); // sub-block size
462 | out.write(1); // loop sub-block id
463 | writeShort(repeat); // loop count (extra iterations, 0=repeat forever)
464 | out.write(0); // block terminator
465 | }
466 |
467 | /**
468 | * Writes color table
469 | */
470 | protected void writePalette() throws IOException {
471 | out.write(colorTab, 0, colorTab.length);
472 | int n = (3 * 256) - colorTab.length;
473 | for (int i = 0; i < n; i++) {
474 | out.write(0);
475 | }
476 | }
477 |
478 | /**
479 | * Encodes and writes pixel data
480 | */
481 | private void writePixels() throws IOException {
482 | LZWEncoder encoder = new LZWEncoder(width, height, indexedPixels, colorDepth);
483 | encoder.encode(out);
484 | }
485 |
486 | /**
487 | * Write 16-bit value to output stream, LSB first
488 | */
489 | private void writeShort(int value) throws IOException {
490 | out.write(value & 0xff);
491 | out.write((value >> 8) & 0xff);
492 | }
493 |
494 | /**
495 | * Writes string to output stream
496 | */
497 | protected void writeString(String s) throws IOException {
498 | for (int i = 0; i < s.length(); i++) {
499 | out.write((byte) s.charAt(i));
500 | }
501 | }
502 | }
503 |
--------------------------------------------------------------------------------
/gifmaker/src/main/java/com/march/gifmaker/base/LZWEncoder.java:
--------------------------------------------------------------------------------
1 | package com.march.gifmaker.base;
2 |
3 | import java.io.IOException;
4 | import java.io.OutputStream;
5 |
6 | public class LZWEncoder {
7 |
8 | private static final int EOF = -1;
9 |
10 | private int imgW, imgH;
11 |
12 | private byte[] pixAry;
13 |
14 | private int initCodeSize;
15 |
16 | private int remaining;
17 |
18 | private int curPixel;
19 |
20 | // GIFCOMPR.C - GIF Image compression routines
21 | //
22 | // Lempel-Ziv compression based on 'compress'. GIF modifications by
23 | // David Rowley (mgardi@watdcsu.waterloo.edu)
24 |
25 | // General DEFINEs
26 |
27 | static final int BITS = 12;
28 |
29 | static final int HSIZE = 5003; // 80% occupancy
30 |
31 | // GIF Image compression - modified 'compress'
32 | //
33 | // Based on: compress.c - File compression ala IEEE Computer, June 1984.
34 | //
35 | // By Authors: Spencer W. Thomas (decvax!harpo!utah-cs!utah-gr!thomas)
36 | // Jim McKie (decvax!mcvax!jim)
37 | // Steve Davies (decvax!vax135!petsd!peora!srd)
38 | // Ken Turkowski (decvax!decwrl!turtlevax!ken)
39 | // James A. Woods (decvax!ihnp4!ames!jaw)
40 | // Joe Orost (decvax!vax135!petsd!joe)
41 |
42 | int n_bits; // number of bits/code
43 |
44 | int maxbits = BITS; // user settable max # bits/code
45 |
46 | int maxcode; // maximum code, given n_bits
47 |
48 | int maxmaxcode = 1 << BITS; // should NEVER generate this code
49 |
50 | int[] htab = new int[HSIZE];
51 |
52 | int[] codetab = new int[HSIZE];
53 |
54 | int hsize = HSIZE; // for dynamic table sizing
55 |
56 | int free_ent = 0; // first unused entry
57 |
58 | // block compression parameters -- after all codes are used up,
59 | // and compression rate changes, start over.
60 | boolean clear_flg = false;
61 |
62 | // Algorithm: use open addressing double hashing (no chaining) on the
63 | // prefix code / next character combination. We do a variant of Knuth's
64 | // algorithm D (vol. 3, sec. 6.4) along with G. Knott's relatively-prime
65 | // secondary probe. Here, the modular division first probe is gives way
66 | // to a faster exclusive-or manipulation. Also do block compression with
67 | // an adaptive reset, whereby the code table is cleared when the compression
68 | // ratio decreases, but after the table fills. The variable-length output
69 | // codes are re-sized at this point, and a special CLEAR code is generated
70 | // for the decompressor. Late addition: construct the table according to
71 | // file size for noticeable speed improvement on small files. Please direct
72 | // questions about this implementation to ames!jaw.
73 |
74 | int g_init_bits;
75 |
76 | int ClearCode;
77 |
78 | int EOFCode;
79 |
80 | // output
81 | //
82 | // Output the given code.
83 | // Inputs:
84 | // code: A n_bits-bit integer. If == -1, then EOF. This assumes
85 | // that n_bits =< wordsize - 1.
86 | // Outputs:
87 | // Outputs code to the file.
88 | // Assumptions:
89 | // Chars are 8 bits long.
90 | // Algorithm:
91 | // Maintain a BITS character long buffer (so that 8 codes will
92 | // fit in it exactly). Use the VAX insv instruction to insert each
93 | // code in turn. When the buffer fills up empty it and start over.
94 |
95 | int cur_accum = 0;
96 |
97 | int cur_bits = 0;
98 |
99 | int masks[] = {0x0000, 0x0001, 0x0003, 0x0007, 0x000F, 0x001F, 0x003F, 0x007F, 0x00FF, 0x01FF,
100 | 0x03FF, 0x07FF, 0x0FFF, 0x1FFF, 0x3FFF, 0x7FFF, 0xFFFF};
101 |
102 | // Number of characters so far in this 'packet'
103 | int a_count;
104 |
105 | // Define the storage for the packet accumulator
106 | byte[] accum = new byte[256];
107 |
108 | // ----------------------------------------------------------------------------
109 | public LZWEncoder(int width, int height, byte[] pixels, int color_depth) {
110 | imgW = width;
111 | imgH = height;
112 | pixAry = pixels;
113 | initCodeSize = Math.max(2, color_depth);
114 | }
115 |
116 | // Add a character to the end of the current packet, and if it is 254
117 | // characters, flush the packet to disk.
118 | void char_out(byte c, OutputStream outs) throws IOException {
119 | accum[a_count++] = c;
120 | if (a_count >= 254)
121 | flush_char(outs);
122 | }
123 |
124 | // Clear out the hash table
125 |
126 | // table clear for block compress
127 | void cl_block(OutputStream outs) throws IOException {
128 | cl_hash(hsize);
129 | free_ent = ClearCode + 2;
130 | clear_flg = true;
131 |
132 | output(ClearCode, outs);
133 | }
134 |
135 | // reset code table
136 | void cl_hash(int hsize) {
137 | for (int i = 0; i < hsize; ++i)
138 | htab[i] = -1;
139 | }
140 |
141 | void compress(int init_bits, OutputStream outs) throws IOException {
142 | int fcode;
143 | int i /* = 0 */;
144 | int c;
145 | int ent;
146 | int disp;
147 | int hsize_reg;
148 | int hshift;
149 |
150 | // Set up the globals: g_init_bits - initial number of bits
151 | g_init_bits = init_bits;
152 |
153 | // Set up the necessary values
154 | clear_flg = false;
155 | n_bits = g_init_bits;
156 | maxcode = MAXCODE(n_bits);
157 |
158 | ClearCode = 1 << (init_bits - 1);
159 | EOFCode = ClearCode + 1;
160 | free_ent = ClearCode + 2;
161 |
162 | a_count = 0; // clear packet
163 |
164 | ent = nextPixel();
165 |
166 | hshift = 0;
167 | for (fcode = hsize; fcode < 65536; fcode *= 2)
168 | ++hshift;
169 | hshift = 8 - hshift; // set hash code range bound
170 |
171 | hsize_reg = hsize;
172 | cl_hash(hsize_reg); // clear hash table
173 |
174 | output(ClearCode, outs);
175 |
176 | outer_loop:
177 | while ((c = nextPixel()) != EOF) {
178 | fcode = (c << maxbits) + ent;
179 | i = (c << hshift) ^ ent; // xor hashing
180 |
181 | if (htab[i] == fcode) {
182 | ent = codetab[i];
183 | continue;
184 | } else if (htab[i] >= 0) // non-empty slot
185 | {
186 | disp = hsize_reg - i; // secondary hash (after G. Knott)
187 | if (i == 0)
188 | disp = 1;
189 | do {
190 | if ((i -= disp) < 0)
191 | i += hsize_reg;
192 |
193 | if (htab[i] == fcode) {
194 | ent = codetab[i];
195 | continue outer_loop;
196 | }
197 | } while (htab[i] >= 0);
198 | }
199 | output(ent, outs);
200 | ent = c;
201 | if (free_ent < maxmaxcode) {
202 | codetab[i] = free_ent++; // code -> hashtable
203 | htab[i] = fcode;
204 | } else
205 | cl_block(outs);
206 | }
207 | // Put out the final code.
208 | output(ent, outs);
209 | output(EOFCode, outs);
210 | }
211 |
212 | // ----------------------------------------------------------------------------
213 | public void encode(OutputStream os) throws IOException {
214 | os.write(initCodeSize); // write "initial code size" byte
215 |
216 | remaining = imgW * imgH; // reset navigation variables
217 | curPixel = 0;
218 |
219 | compress(initCodeSize + 1, os); // compress and write the pixel data
220 |
221 | os.write(0); // write block terminator
222 | }
223 |
224 | // Flush the packet to disk, and reset the accumulator
225 | void flush_char(OutputStream outs) throws IOException {
226 | if (a_count > 0) {
227 | outs.write(a_count);
228 | outs.write(accum, 0, a_count);
229 | a_count = 0;
230 | }
231 | }
232 |
233 | final int MAXCODE(int n_bits) {
234 | return (1 << n_bits) - 1;
235 | }
236 |
237 | // ----------------------------------------------------------------------------
238 | // Return the next pixel from the image
239 | // ----------------------------------------------------------------------------
240 | private int nextPixel() {
241 | if (remaining == 0)
242 | return EOF;
243 |
244 | --remaining;
245 |
246 | byte pix = pixAry[curPixel++];
247 |
248 | return pix & 0xff;
249 | }
250 |
251 | void output(int code, OutputStream outs) throws IOException {
252 | cur_accum &= masks[cur_bits];
253 |
254 | if (cur_bits > 0)
255 | cur_accum |= (code << cur_bits);
256 | else
257 | cur_accum = code;
258 |
259 | cur_bits += n_bits;
260 |
261 | while (cur_bits >= 8) {
262 | char_out((byte) (cur_accum & 0xff), outs);
263 | cur_accum >>= 8;
264 | cur_bits -= 8;
265 | }
266 |
267 | // If the next entry is going to be too big for the code size,
268 | // then increase it, if possible.
269 | if (free_ent > maxcode || clear_flg) {
270 | if (clear_flg) {
271 | maxcode = MAXCODE(n_bits = g_init_bits);
272 | clear_flg = false;
273 | } else {
274 | ++n_bits;
275 | if (n_bits == maxbits)
276 | maxcode = maxmaxcode;
277 | else
278 | maxcode = MAXCODE(n_bits);
279 | }
280 | }
281 |
282 | if (code == EOFCode) {
283 | // At EOF, write the rest of the buffer.
284 | while (cur_bits > 0) {
285 | char_out((byte) (cur_accum & 0xff), outs);
286 | cur_accum >>= 8;
287 | cur_bits -= 8;
288 | }
289 |
290 | flush_char(outs);
291 | }
292 | }
293 | }
294 |
--------------------------------------------------------------------------------
/gifmaker/src/main/java/com/march/gifmaker/base/NeuQuant.java:
--------------------------------------------------------------------------------
1 | package com.march.gifmaker.base;
2 |
3 | /*
4 | * NeuQuant Neural-Net Quantization Algorithm
5 | * ------------------------------------------
6 | *
7 | * Copyright (c) 1994 Anthony Dekker
8 | *
9 | * NEUQUANT Neural-Net quantization algorithm by Anthony Dekker, 1994. See
10 | * "Kohonen neural networks for optimal colour quantization" in "Network:
11 | * Computation in Neural Systems" Vol. 5 (1994) pp 351-367. for a discussion of
12 | * the algorithm.
13 | *
14 | * Any party obtaining a copy of these files from the author, directly or
15 | * indirectly, is granted, free of charge, a full and unrestricted irrevocable,
16 | * world-wide, paid up, royalty-free, nonexclusive right and license to deal in
17 | * this software and documentation files (the "Software"), including without
18 | * limitation the rights to use, copy, modify, merge, publish, distribute,
19 | * sublicense, and/or sell copies of the Software, and to permit persons who
20 | * receive copies from any such party to do so, with the only requirement being
21 | * that this copyright notice remain intact.
22 | */
23 |
24 | // Ported to Java 12/00 K Weiner
25 | class NeuQuant {
26 |
27 | protected static final int netsize = 256; /* number of colours used */
28 |
29 | /* four primes near 500 - assume no image has a length so large */
30 | /* that it is divisible by all four primes */
31 | protected static final int prime1 = 499;
32 |
33 | protected static final int prime2 = 491;
34 |
35 | protected static final int prime3 = 487;
36 |
37 | protected static final int prime4 = 503;
38 |
39 | protected static final int minpicturebytes = (3 * prime4);
40 |
41 | /* minimum size for input image */
42 |
43 | /*
44 | * Program Skeleton ---------------- [select samplefac in range 1..30] [read
45 | * image from input file] pic = (unsigned char*) malloc(3*width*height);
46 | * initnet(pic,3*width*height,samplefac); learn(); unbiasnet(); [write output
47 | * image header, using writecolourmap(f)] inxbuild(); write output image using
48 | * inxsearch(b,g,r)
49 | */
50 |
51 | /*
52 | * Network Definitions -------------------
53 | */
54 |
55 | protected static final int maxnetpos = (netsize - 1);
56 |
57 | protected static final int netbiasshift = 4; /* bias for colour values */
58 |
59 | protected static final int ncycles = 100; /* no. of learning cycles */
60 |
61 | /* defs for freq and bias */
62 | protected static final int intbiasshift = 16; /* bias for fractions */
63 |
64 | protected static final int intbias = (((int) 1) << intbiasshift);
65 |
66 | protected static final int gammashift = 10; /* gamma = 1024 */
67 |
68 | protected static final int gamma = (((int) 1) << gammashift);
69 |
70 | protected static final int betashift = 10;
71 |
72 | protected static final int beta = (intbias >> betashift); /* beta = 1/1024 */
73 |
74 | protected static final int betagamma = (intbias << (gammashift - betashift));
75 |
76 | /* defs for decreasing radius factor */
77 | protected static final int initrad = (netsize >> 3); /*
78 | * for 256 cols, radius
79 | * starts
80 | */
81 |
82 | protected static final int radiusbiasshift = 6; /* at 32.0 biased by 6 bits */
83 |
84 | protected static final int radiusbias = (((int) 1) << radiusbiasshift);
85 |
86 | protected static final int initradius = (initrad * radiusbias); /*
87 | * and
88 | * decreases
89 | * by a
90 | */
91 |
92 | protected static final int radiusdec = 30; /* factor of 1/30 each cycle */
93 |
94 | /* defs for decreasing alpha factor */
95 | protected static final int alphabiasshift = 10; /* alpha starts at 1.0 */
96 |
97 | protected static final int initalpha = (((int) 1) << alphabiasshift);
98 |
99 | protected int alphadec; /* biased by 10 bits */
100 |
101 | /* radbias and alpharadbias used for radpower calculation */
102 | protected static final int radbiasshift = 8;
103 |
104 | protected static final int radbias = (((int) 1) << radbiasshift);
105 |
106 | protected static final int alpharadbshift = (alphabiasshift + radbiasshift);
107 |
108 | protected static final int alpharadbias = (((int) 1) << alpharadbshift);
109 |
110 | /*
111 | * Types and Global Variables --------------------------
112 | */
113 |
114 | protected byte[] thepicture; /* the input image itself */
115 |
116 | protected int lengthcount; /* lengthcount = H*W*3 */
117 |
118 | protected int samplefac; /* sampling factor 1..30 */
119 |
120 | // typedef int pixel[4]; /* BGRc */
121 | protected int[][] network; /* the network itself - [netsize][4] */
122 |
123 | protected int[] netindex = new int[256];
124 |
125 | /* for network lookup - really 256 */
126 |
127 | protected int[] bias = new int[netsize];
128 |
129 | /* bias and freq arrays for learning */
130 | protected int[] freq = new int[netsize];
131 |
132 | protected int[] radpower = new int[initrad];
133 |
134 | /* radpower for precomputation */
135 |
136 | /*
137 | * Initialise network in range (0,0,0) to (255,255,255) and set parameters
138 | * -----------------------------------------------------------------------
139 | */
140 | public NeuQuant(byte[] thepic, int len, int sample) {
141 |
142 | int i;
143 | int[] p;
144 |
145 | thepicture = thepic;
146 | lengthcount = len;
147 | samplefac = sample;
148 |
149 | network = new int[netsize][];
150 | for (i = 0; i < netsize; i++) {
151 | network[i] = new int[4];
152 | p = network[i];
153 | p[0] = p[1] = p[2] = (i << (netbiasshift + 8)) / netsize;
154 | freq[i] = intbias / netsize; /* 1/netsize */
155 | bias[i] = 0;
156 | }
157 | }
158 |
159 | public byte[] colorMap() {
160 | byte[] map = new byte[3 * netsize];
161 | int[] index = new int[netsize];
162 | for (int i = 0; i < netsize; i++)
163 | index[network[i][3]] = i;
164 | int k = 0;
165 | for (int i = 0; i < netsize; i++) {
166 | int j = index[i];
167 | map[k++] = (byte) (network[j][0]);
168 | map[k++] = (byte) (network[j][1]);
169 | map[k++] = (byte) (network[j][2]);
170 | }
171 | return map;
172 | }
173 |
174 | /*
175 | * Insertion sort of network and building of netindex[0..255] (to do after
176 | * unbias)
177 | * -------------------------------------------------------------------------------
178 | */
179 | public void inxbuild() {
180 |
181 | int i, j, smallpos, smallval;
182 | int[] p;
183 | int[] q;
184 | int previouscol, startpos;
185 |
186 | previouscol = 0;
187 | startpos = 0;
188 | for (i = 0; i < netsize; i++) {
189 | p = network[i];
190 | smallpos = i;
191 | smallval = p[1]; /* index on g */
192 | /* find smallest in i..netsize-1 */
193 | for (j = i + 1; j < netsize; j++) {
194 | q = network[j];
195 | if (q[1] < smallval) { /* index on g */
196 | smallpos = j;
197 | smallval = q[1]; /* index on g */
198 | }
199 | }
200 | q = network[smallpos];
201 | /* swap p (i) and q (smallpos) entries */
202 | if (i != smallpos) {
203 | j = q[0];
204 | q[0] = p[0];
205 | p[0] = j;
206 | j = q[1];
207 | q[1] = p[1];
208 | p[1] = j;
209 | j = q[2];
210 | q[2] = p[2];
211 | p[2] = j;
212 | j = q[3];
213 | q[3] = p[3];
214 | p[3] = j;
215 | }
216 | /* smallval entry is now in position i */
217 | if (smallval != previouscol) {
218 | netindex[previouscol] = (startpos + i) >> 1;
219 | for (j = previouscol + 1; j < smallval; j++)
220 | netindex[j] = i;
221 | previouscol = smallval;
222 | startpos = i;
223 | }
224 | }
225 | netindex[previouscol] = (startpos + maxnetpos) >> 1;
226 | for (j = previouscol + 1; j < 256; j++)
227 | netindex[j] = maxnetpos; /* really 256 */
228 | }
229 |
230 | /*
231 | * Main Learning Loop ------------------
232 | */
233 | public void learn() {
234 |
235 | int i, j, b, g, r;
236 | int radius, rad, alpha, step, delta, samplepixels;
237 | byte[] p;
238 | int pix, lim;
239 |
240 | if (lengthcount < minpicturebytes)
241 | samplefac = 1;
242 | alphadec = 30 + ((samplefac - 1) / 3);
243 | p = thepicture;
244 | pix = 0;
245 | lim = lengthcount;
246 | samplepixels = lengthcount / (3 * samplefac);
247 | delta = samplepixels / ncycles;
248 | alpha = initalpha;
249 | radius = initradius;
250 |
251 | rad = radius >> radiusbiasshift;
252 | if (rad <= 1)
253 | rad = 0;
254 | for (i = 0; i < rad; i++)
255 | radpower[i] = alpha * (((rad * rad - i * i) * radbias) / (rad * rad));
256 |
257 | // fprintf(stderr,"beginning 1D learning: initial radius=%d\n", rad);
258 |
259 | if (lengthcount < minpicturebytes)
260 | step = 3;
261 | else if ((lengthcount % prime1) != 0)
262 | step = 3 * prime1;
263 | else {
264 | if ((lengthcount % prime2) != 0)
265 | step = 3 * prime2;
266 | else {
267 | if ((lengthcount % prime3) != 0)
268 | step = 3 * prime3;
269 | else
270 | step = 3 * prime4;
271 | }
272 | }
273 |
274 | i = 0;
275 | while (i < samplepixels) {
276 | b = (p[pix + 0] & 0xff) << netbiasshift;
277 | g = (p[pix + 1] & 0xff) << netbiasshift;
278 | r = (p[pix + 2] & 0xff) << netbiasshift;
279 | j = contest(b, g, r);
280 |
281 | altersingle(alpha, j, b, g, r);
282 | if (rad != 0)
283 | alterneigh(rad, j, b, g, r); /* alter neighbours */
284 |
285 | pix += step;
286 | if (pix >= lim)
287 | pix -= lengthcount;
288 |
289 | i++;
290 | if (delta == 0)
291 | delta = 1;
292 | if (i % delta == 0) {
293 | alpha -= alpha / alphadec;
294 | radius -= radius / radiusdec;
295 | rad = radius >> radiusbiasshift;
296 | if (rad <= 1)
297 | rad = 0;
298 | for (j = 0; j < rad; j++)
299 | radpower[j] = alpha * (((rad * rad - j * j) * radbias) / (rad * rad));
300 | }
301 | }
302 | // fprintf(stderr,"finished 1D learning: final alpha=%f
303 | // !\n",((float)alpha)/initalpha);
304 | }
305 |
306 | /*
307 | * Search for BGR values 0..255 (after net is unbiased) and return colour
308 | * index
309 | * ----------------------------------------------------------------------------
310 | */
311 | public int map(int b, int g, int r) {
312 |
313 | int i, j, dist, a, bestd;
314 | int[] p;
315 | int best;
316 |
317 | bestd = 1000; /* biggest possible dist is 256*3 */
318 | best = -1;
319 | i = netindex[g]; /* index on g */
320 | j = i - 1; /* start at netindex[g] and work outwards */
321 |
322 | while ((i < netsize) || (j >= 0)) {
323 | if (i < netsize) {
324 | p = network[i];
325 | dist = p[1] - g; /* inx key */
326 | if (dist >= bestd)
327 | i = netsize; /* stop iter */
328 | else {
329 | i++;
330 | if (dist < 0)
331 | dist = -dist;
332 | a = p[0] - b;
333 | if (a < 0)
334 | a = -a;
335 | dist += a;
336 | if (dist < bestd) {
337 | a = p[2] - r;
338 | if (a < 0)
339 | a = -a;
340 | dist += a;
341 | if (dist < bestd) {
342 | bestd = dist;
343 | best = p[3];
344 | }
345 | }
346 | }
347 | }
348 | if (j >= 0) {
349 | p = network[j];
350 | dist = g - p[1]; /* inx key - reverse dif */
351 | if (dist >= bestd)
352 | j = -1; /* stop iter */
353 | else {
354 | j--;
355 | if (dist < 0)
356 | dist = -dist;
357 | a = p[0] - b;
358 | if (a < 0)
359 | a = -a;
360 | dist += a;
361 | if (dist < bestd) {
362 | a = p[2] - r;
363 | if (a < 0)
364 | a = -a;
365 | dist += a;
366 | if (dist < bestd) {
367 | bestd = dist;
368 | best = p[3];
369 | }
370 | }
371 | }
372 | }
373 | }
374 | return (best);
375 | }
376 |
377 | public byte[] process() {
378 | learn();
379 | unbiasnet();
380 | inxbuild();
381 | return colorMap();
382 | }
383 |
384 | /*
385 | * Unbias network to give byte values 0..255 and record position i to prepare
386 | * for sort
387 | * -----------------------------------------------------------------------------------
388 | */
389 | public void unbiasnet() {
390 |
391 | int i, j;
392 |
393 | for (i = 0; i < netsize; i++) {
394 | network[i][0] >>= netbiasshift;
395 | network[i][1] >>= netbiasshift;
396 | network[i][2] >>= netbiasshift;
397 | network[i][3] = i; /* record colour no */
398 | }
399 | }
400 |
401 | /*
402 | * Move adjacent neurons by precomputed alpha*(1-((i-j)^2/[r]^2)) in
403 | * radpower[|i-j|]
404 | * ---------------------------------------------------------------------------------
405 | */
406 | protected void alterneigh(int rad, int i, int b, int g, int r) {
407 |
408 | int j, k, lo, hi, a, m;
409 | int[] p;
410 |
411 | lo = i - rad;
412 | if (lo < -1)
413 | lo = -1;
414 | hi = i + rad;
415 | if (hi > netsize)
416 | hi = netsize;
417 |
418 | j = i + 1;
419 | k = i - 1;
420 | m = 1;
421 | while ((j < hi) || (k > lo)) {
422 | a = radpower[m++];
423 | if (j < hi) {
424 | p = network[j++];
425 | try {
426 | p[0] -= (a * (p[0] - b)) / alpharadbias;
427 | p[1] -= (a * (p[1] - g)) / alpharadbias;
428 | p[2] -= (a * (p[2] - r)) / alpharadbias;
429 | } catch (Exception e) {
430 | } // prevents 1.3 miscompilation
431 | }
432 | if (k > lo) {
433 | p = network[k--];
434 | try {
435 | p[0] -= (a * (p[0] - b)) / alpharadbias;
436 | p[1] -= (a * (p[1] - g)) / alpharadbias;
437 | p[2] -= (a * (p[2] - r)) / alpharadbias;
438 | } catch (Exception e) {
439 | }
440 | }
441 | }
442 | }
443 |
444 | /*
445 | * Move neuron i towards biased (b,g,r) by factor alpha
446 | * ----------------------------------------------------
447 | */
448 | protected void altersingle(int alpha, int i, int b, int g, int r) {
449 |
450 | /* alter hit neuron */
451 | int[] n = network[i];
452 | n[0] -= (alpha * (n[0] - b)) / initalpha;
453 | n[1] -= (alpha * (n[1] - g)) / initalpha;
454 | n[2] -= (alpha * (n[2] - r)) / initalpha;
455 | }
456 |
457 | /*
458 | * Search for biased BGR values ----------------------------
459 | */
460 | protected int contest(int b, int g, int r) {
461 |
462 | /* finds closest neuron (min dist) and updates freq */
463 | /* finds best neuron (min dist-bias) and returns position */
464 | /* for frequently chosen neurons, freq[i] is high and bias[i] is negative */
465 | /* bias[i] = gamma*((1/netsize)-freq[i]) */
466 |
467 | int i, dist, a, biasdist, betafreq;
468 | int bestpos, bestbiaspos, bestd, bestbiasd;
469 | int[] n;
470 |
471 | bestd = ~(((int) 1) << 31);
472 | bestbiasd = bestd;
473 | bestpos = -1;
474 | bestbiaspos = bestpos;
475 |
476 | for (i = 0; i < netsize; i++) {
477 | n = network[i];
478 | dist = n[0] - b;
479 | if (dist < 0)
480 | dist = -dist;
481 | a = n[1] - g;
482 | if (a < 0)
483 | a = -a;
484 | dist += a;
485 | a = n[2] - r;
486 | if (a < 0)
487 | a = -a;
488 | dist += a;
489 | if (dist < bestd) {
490 | bestd = dist;
491 | bestpos = i;
492 | }
493 | biasdist = dist - ((bias[i]) >> (intbiasshift - netbiasshift));
494 | if (biasdist < bestbiasd) {
495 | bestbiasd = biasdist;
496 | bestbiaspos = i;
497 | }
498 | betafreq = (freq[i] >> betashift);
499 | freq[i] -= betafreq;
500 | bias[i] += (betafreq << gammashift);
501 | }
502 | freq[bestpos] += beta;
503 | bias[bestpos] -= betagamma;
504 | return (bestbiaspos);
505 | }
506 | }
507 |
--------------------------------------------------------------------------------
/gifmaker/src/main/java/com/march/gifmaker/extend/LZWEncoderOrderHolder.java:
--------------------------------------------------------------------------------
1 | package com.march.gifmaker.extend;
2 |
3 | import com.march.gifmaker.base.LZWEncoder;
4 |
5 | import java.io.ByteArrayOutputStream;
6 |
7 | /**
8 | * CreateAt : 7/12/17
9 | * Describe :
10 | * 持有当前编码完成的 LZWEncoder 和 该次编码顺序,后面用来拼合输出流
11 | *
12 | * @author chendong
13 | */
14 | public class LZWEncoderOrderHolder implements Comparable