source, String outputPath, OnGifMakerListener listener) {
83 | mStartWorkTimeStamp = System.currentTimeMillis();
84 | this.mOutputPath = outputPath;
85 | this.mOnGifMakerListener = listener;
86 | this.mTotalWorkSize = source.size();
87 | this.mCurrentWorkSize = mTotalWorkSize;
88 |
89 | for (int i = 0; i < source.size(); i++) {
90 | Bitmap bmp = source.get(i);
91 | if (bmp == null) {
92 | continue;
93 | }
94 | mExecutor.execute(new EncodeGifRunnable(bmp, i));
95 | }
96 | }
97 |
98 | /**
99 | * 编码一帧
100 | */
101 | private class EncodeGifRunnable implements Runnable {
102 |
103 | int mOrder; // 当前顺序
104 | Bitmap mBitmap; // 当前位图
105 | ThreadGifEncoder mThreadGifEncoder; // 当前编码器
106 | ByteArrayOutputStream mCurrentOutputStream; // 当前数据输出流
107 |
108 | EncodeGifRunnable(Bitmap bitmap, int order) {
109 | mCurrentOutputStream = new ByteArrayOutputStream();
110 | mThreadGifEncoder = new ThreadGifEncoder();
111 | mThreadGifEncoder.setQuality(100);
112 | mThreadGifEncoder.setDelay(mDelayTime);
113 | mThreadGifEncoder.start(mCurrentOutputStream, order);
114 | mThreadGifEncoder.setFirstFrame(order == 0);
115 | mThreadGifEncoder.setRepeat(0);
116 | mBitmap = bitmap;
117 | mOrder = order;
118 | }
119 |
120 | @Override
121 | public void run() {
122 | try {
123 | Log.e(TAG, "开始编码第" + mOrder + "张");
124 | LZWEncoderOrderHolder holder = mThreadGifEncoder.addFrame(mBitmap, mOrder);
125 | mThreadGifEncoder.finishThread(mOrder == (mTotalWorkSize - 1), holder.getLZWEncoder());
126 | holder.setByteArrayOutputStream(mCurrentOutputStream);
127 | mEncodeOrders.add(holder);
128 | logCostTime(mOrder, mBitmap);
129 | Util.recycleBitmaps(mBitmap);
130 | workDone();
131 | } catch (Exception e) {
132 | e.printStackTrace();
133 | }
134 | }
135 |
136 | }
137 |
138 | // 完成一帧
139 | private synchronized void workDone() throws IOException {
140 | mCurrentWorkSize--;
141 | if (mCurrentWorkSize == 0) {
142 | //排序 默认从小到大
143 | Collections.sort(mEncodeOrders);
144 | for (LZWEncoderOrderHolder myLZWEncoder : mEncodeOrders) {
145 | mFinalOutputStream.write(myLZWEncoder.getByteArrayOutputStream().toByteArray());
146 | }
147 | // mFinalOutputStream.write(0x3b); // gif traile
148 | byte[] data = mFinalOutputStream.toByteArray();
149 | File file = new File(mOutputPath);
150 | if (!file.getParentFile().exists()) {
151 | file.getParentFile().mkdirs();
152 | }
153 | BufferedOutputStream bosToFile = new BufferedOutputStream(new FileOutputStream(file));
154 | bosToFile.write(data);
155 | bosToFile.flush();
156 | bosToFile.close();
157 | logCostTime(-1, null);
158 | mHandler.sendEmptyMessage(100);
159 | }
160 | }
161 |
162 |
163 | private void logCostTime(int order, Bitmap bitmap) {
164 | long currentTimeMillis = System.currentTimeMillis();
165 | long timeCost = currentTimeMillis - mStartWorkTimeStamp;
166 | String msg = String.format(Locale.CHINA, "%d.%d s", timeCost / 1000, timeCost % 1000);
167 |
168 | Log.i(TAG, (order == -1 ? "合成完成" : "完成第" + order + "帧") + ",耗时:" + msg + (bitmap == null ? "" : (" - bitmap [" + bitmap.getWidth() + "," + bitmap.getHeight() + "]")));
169 | }
170 |
171 | }
172 |
--------------------------------------------------------------------------------
/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/base/GifEncoder.java:
--------------------------------------------------------------------------------
1 | package com.march.gifmaker.base;
2 |
3 | import android.graphics.Bitmap;
4 | import android.graphics.Canvas;
5 | import android.graphics.Color;
6 | import android.util.Log;
7 |
8 | import java.io.BufferedOutputStream;
9 | import java.io.FileOutputStream;
10 | import java.io.IOException;
11 | import java.io.OutputStream;
12 |
13 | /**
14 | * Class AnimatedGifEncoder - Encodes a GIF file consisting of one or more
15 | * frames.
16 | *
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 |
--------------------------------------------------------------------------------
/backup/giflen/sourcecode/gifflen.cpp:
--------------------------------------------------------------------------------
1 | /* GIFFLEN.CPP
2 | * Native code used by the Giffle app
3 | * Performs color quantization and GIF encoding
4 | *
5 | * Mic, 2009
6 | */
7 |
8 | /* NeuQuant Neural-Net Quantization Algorithm
9 | * ------------------------------------------
10 | *
11 | * Copyright (c) 1994 Anthony Dekker
12 | *
13 | * NEUQUANT Neural-Net quantization algorithm by Anthony Dekker, 1994.
14 | * See "Kohonen neural networks for optimal colour quantization"
15 | * in "Network: Computation in Neural Systems" Vol. 5 (1994) pp 351-367.
16 | * for a discussion of the algorithm.
17 | * See also http://members.ozemail.com.au/~dekker/NEUQUANT.HTML
18 | *
19 | * Any party obtaining a copy of these files from the author, directly or
20 | * indirectly, is granted, free of charge, a full and unrestricted irrevocable,
21 | * world-wide, paid up, royalty-free, nonexclusive right and license to deal
22 | * in this software and documentation files (the "Software"), including without
23 | * limitation the rights to use, copy, modify, merge, publish, distribute, sublicense,
24 | * and/or sell copies of the Software, and to permit persons who receive
25 | * copies from any such party to do so, with the only requirement being
26 | * that this copyright notice remain intact.
27 | */
28 |
29 | /* The GIF encoder is based on the SAVE_PIC library written by:
30 | * Christopher Street
31 | * chris_street@usa.net
32 | */
33 |
34 |
35 | #include
36 | #include "neuquant.h"
37 | #include
38 | #include
39 |
40 | #define PIXEL_SIZE 4
41 |
42 |
43 | int imgw,imgh;
44 | int optCol=256, optQuality=100, optDelay=4;
45 | unsigned char *data32bpp = NULL;
46 | NeuQuant *neuQuant = NULL;
47 | DIB inDIB, *outDIB;
48 | JavaVM *gJavaVM;
49 | static char s[128];
50 | unsigned int netsize;
51 | FILE *pGif = NULL;
52 |
53 |
54 | extern "C"
55 | {
56 | JNIEXPORT jint JNICALL Java_org_jiggawatt_giffle_Giffle_Init(JNIEnv *ioEnv, jobject ioThis, jstring gifName,
57 | jint w, jint h, jint numColors, jint quality, jint frameDelay);
58 | JNIEXPORT void JNICALL Java_org_jiggawatt_giffle_Giffle_Close(JNIEnv *ioEnv, jobject ioThis);
59 | JNIEXPORT jint JNICALL Java_org_jiggawatt_giffle_Giffle_AddFrame(JNIEnv *ioEnv, jobject ioThis, jintArray inArray);
60 | };
61 |
62 |
63 | int max_bits(int);
64 | int GIF_LZW_compressor(DIB *,unsigned int,FILE *,int);
65 |
66 |
67 |
68 |
69 | jint JNI_OnLoad(JavaVM* vm, void* reserved)
70 | {
71 | JNIEnv *env;
72 | gJavaVM = vm;
73 | if (vm->GetEnv((void**) &env, JNI_VERSION_1_4) != JNI_OK) {
74 | return -1;
75 | }
76 | return JNI_VERSION_1_4;
77 | }
78 |
79 |
80 | JNIEXPORT jint JNICALL Java_org_jiggawatt_giffle_Giffle_Init(JNIEnv *ioEnv, jobject ioThis, jstring gifName,
81 | jint w, jint h, jint numColors, jint quality, jint frameDelay)
82 | {
83 | const char *str;
84 | str = ioEnv->GetStringUTFChars(gifName, NULL);
85 | if (str == NULL)
86 | {
87 | return -1; /* OutOfMemoryError already thrown */
88 | }
89 |
90 | __android_log_write(ANDROID_LOG_VERBOSE, "gifflen",str);
91 |
92 | if ((pGif = fopen(str, "wb")) == NULL)
93 | {
94 | ioEnv->ReleaseStringUTFChars(gifName, str);
95 | return -2;
96 | }
97 |
98 | ioEnv->ReleaseStringUTFChars(gifName, str);
99 |
100 | optDelay = frameDelay;
101 | optCol = numColors;
102 | optQuality = quality;
103 | imgw = w;
104 | imgh = h;
105 |
106 | __android_log_write(ANDROID_LOG_VERBOSE, "gifflen","Allocating memory for input DIB");
107 | data32bpp = new unsigned char[imgw * imgh * PIXEL_SIZE];
108 |
109 | inDIB.bits = data32bpp;
110 | inDIB.width = imgw;
111 | inDIB.height = imgh;
112 | inDIB.bitCount = 32;
113 | inDIB.pitch = imgw * PIXEL_SIZE;
114 | inDIB.palette = NULL;
115 |
116 | __android_log_write(ANDROID_LOG_VERBOSE, "gifflen","Allocating memory for output DIB");
117 | outDIB = new DIB(imgw, imgh, 8);
118 | outDIB->palette = new unsigned char[768];
119 |
120 | neuQuant = new NeuQuant();
121 |
122 | // Output the GIF header and Netscape extension
123 | fwrite("GIF89a", 1, 6, pGif);
124 | s[0] = w & 0xFF; s[1] = w / 0x100;
125 | s[2] = h & 0xFF; s[3] = h / 0x100;
126 | s[4] = 0x50 + max_bits(numColors) - 1;
127 | s[5] = s[6] = 0;
128 | s[7] = 0x21; s[8] = 0xFF; s[9] = 0x0B;
129 | fwrite(s, 1, 10, pGif);
130 | fwrite("NETSCAPE2.0", 1, 11, pGif);
131 | s[0] = 3; s[1] = 1; s[2] = s[3] = s[4] = 0;
132 | fwrite(s, 1, 5, pGif);
133 |
134 | return 0;
135 | }
136 |
137 |
138 | JNIEXPORT void JNICALL Java_org_jiggawatt_giffle_Giffle_Close(JNIEnv *ioEnv, jobject ioThis)
139 | {
140 | if (data32bpp)
141 | {
142 | delete [] data32bpp;
143 | data32bpp = NULL;
144 | }
145 | if (outDIB)
146 | {
147 | if (outDIB->palette) delete [] outDIB->palette;
148 | delete outDIB;
149 | outDIB = NULL;
150 | }
151 | if (pGif)
152 | {
153 | fputc(';',pGif);
154 | fclose(pGif);
155 | pGif = NULL;
156 | }
157 | if (neuQuant)
158 | {
159 | delete neuQuant;
160 | neuQuant = NULL;
161 | }
162 | }
163 |
164 |
165 |
166 | JNIEXPORT jint JNICALL Java_org_jiggawatt_giffle_Giffle_AddFrame(JNIEnv *ioEnv, jobject ioThis, jintArray inArray)
167 | {
168 | ioEnv->GetIntArrayRegion(inArray, (jint)0, (jint)(inDIB.width * inDIB.height), (jint*)(inDIB.bits));
169 |
170 | s[0] = '!'; s[1] = 0xF9; s[2] = 4;
171 | s[3] = 0; s[4] = optDelay & 0xFF; s[5] = optDelay / 0x100; s[6] = s[7] = 0;
172 | s[8] = ','; s[9] = s[10] = s[11] = s[12] = 0; s[13] = imgw & 0xFF; s[14] = imgw / 0x100;
173 | s[15] = imgh & 0xFF; s[16] = imgh / 0x100;
174 | s[17] = 0x80 + max_bits(optCol) - 1;
175 |
176 | fwrite(s, 1, 18, pGif);
177 |
178 | __android_log_write(ANDROID_LOG_VERBOSE, "gifflen","Quantising");
179 |
180 | neuQuant->quantise(outDIB, &inDIB, optCol, optQuality, 0);
181 |
182 | fwrite(outDIB->palette, 1, optCol * 3, pGif);
183 |
184 | __android_log_write(ANDROID_LOG_VERBOSE, "gifflen","Doing GIF encoding");
185 | GIF_LZW_compressor(outDIB, optCol, pGif, 0);
186 |
187 | return 0;
188 | }
189 |
190 |
191 |
192 |
193 | /*************************************************************************************************************/
194 |
195 | #define hash 11003
196 |
197 | unsigned int stat_bits;
198 | unsigned int code_in_progress;
199 | unsigned int LZWpos;
200 | char LZW[256];
201 | short int hashtree[hash][3];
202 |
203 | int find_hash(int pre,int suf)
204 | {
205 | int i,o;
206 | i = ((pre*256) ^ suf) % hash;
207 | if (i==0)
208 | o = 1;
209 | else
210 | o = hash-i;
211 | while (1)
212 | {
213 | if (hashtree[i][0]==-1)
214 | return i;
215 | else if ((hashtree[i][1]==pre) && (hashtree[i][2]==suf))
216 | return i;
217 | else
218 | {
219 | i=i-o;
220 | if (i<0)
221 | i += hash;
222 | }
223 | }
224 |
225 | return 0;
226 | }
227 |
228 |
229 | int max_bits(int num)
230 | {
231 | for (int b=0; b<14; b++)
232 | {
233 | if ((1<=num)
234 | return b;
235 | }
236 | return 0;
237 | }
238 |
239 | void append_code(FILE *handle,int code)
240 | {
241 | LZW[LZWpos++] = code;
242 | if (LZWpos==256)
243 | {
244 | LZW[0] = 255;
245 | fwrite(LZW, 1, 256, handle);
246 | LZWpos = 1;
247 | }
248 | }
249 |
250 |
251 | void write_code(FILE *handle,int no_bits,int code)
252 | {
253 | code_in_progress = code_in_progress + (code << stat_bits); // * powers2[stat_bits+1]
254 | stat_bits=stat_bits+no_bits;
255 | while (stat_bits>7)
256 | {
257 | append_code(handle,code_in_progress&255);
258 | code_in_progress >>= 8;
259 | stat_bits -= 8;
260 | }
261 | }
262 |
263 |
264 |
265 | int GIF_LZW_compressor(DIB *srcimg,unsigned int numColors,FILE *handle,int interlace)
266 | {
267 | int xdim,ydim,clear,EOI,code,bits,pre,suf,x,y,i,max,bits_color,done,rasterlen;
268 | static short int rasters[768];
269 |
270 | stat_bits = 0;
271 | code_in_progress = 0;
272 | LZWpos = 1;
273 |
274 | for (i = 0; i < hash; i++)
275 | {
276 | hashtree[i][0] = hashtree[i][1] = hashtree[i][2] = -1;
277 | }
278 | if (handle==NULL)
279 | return 0;
280 | xdim = srcimg->width;
281 | ydim = srcimg->height;
282 | bits_color = max_bits(numColors)-1;
283 | clear = (1 << (bits_color+1)); //powers2[bits_color+2]
284 | EOI = clear+1;
285 | code = EOI+1;
286 | bits = bits_color+2;
287 | max = (1 << bits); //powers2[bits+1]
288 | if (code==max)
289 | {
290 | clear = 4;
291 | EOI = 5;
292 | code = 6;
293 | bits++;
294 | max *= 2;
295 | }
296 | fputc(bits-1, handle);
297 | write_code(handle, bits, clear);
298 | rasterlen = 0;
299 | if (interlace)
300 | {
301 | for (int e=1; e<=5; e+=4)
302 | {
303 | for (int f=e; f<=ydim; f+=8)
304 | {
305 | rasters[rasterlen++] = f;
306 | }
307 | }
308 | for (int e=3; e<=ydim; e+=4)
309 | {
310 | rasters[rasterlen++] = e;
311 | }
312 | for (int e=2; e<=ydim; e+=2)
313 | {
314 | rasters[rasterlen++] = e;
315 | }
316 | }
317 | else
318 | {
319 | for (int e=1; e<=ydim; e++)
320 | rasters[rasterlen++] = e - 1;
321 | }
322 | pre = srcimg->bits[rasters[0] * xdim];
323 | x=1; y=0; done=0;
324 | if (x>=xdim)
325 | {
326 | y++; x=0;
327 | }
328 | while (1)
329 | {
330 | while (1)
331 | {
332 | if (!done)
333 | {
334 | suf = srcimg->bits[rasters[y] * xdim + x];
335 | x++;
336 | if (x>=xdim)
337 | {
338 | y++;
339 | x=0;
340 | if (y>=ydim)
341 | done = 1;
342 | }
343 | i = find_hash(pre,suf);
344 | if (hashtree[i][0]==-1)
345 | break;
346 | else
347 | pre = hashtree[i][0];
348 | }
349 | else
350 | {
351 | write_code(handle,bits,pre);
352 | write_code(handle,bits,EOI);
353 | if (stat_bits)
354 | write_code(handle,bits,0);
355 | LZW[0] = LZWpos - 1;
356 | fwrite(LZW, 1, LZWpos, handle);
357 | fputc(0, handle);
358 | return 1;
359 | }
360 | }
361 | write_code(handle,bits,pre);
362 | hashtree[i][0] = code; hashtree[i][1] = pre; hashtree[i][2] = suf;
363 | pre = suf;
364 | code++;
365 | if (code == max+1)
366 | {
367 | max *= 2;
368 | if (bits == 12)
369 | {
370 | write_code(handle,bits,clear);
371 | for (i = 0; i < hash; i++)
372 | {
373 | hashtree[i][0] = hashtree[i][1] = hashtree[i][2] = -1;
374 | }
375 | code = EOI+1;
376 | bits = bits_color+2;
377 | max = 1 << bits;
378 | if (bits == 2)
379 | {
380 | clear = 4;
381 | EOI = 5;
382 | code = 6;
383 | bits = 3;
384 | max *= 2;
385 | }
386 | }
387 | else
388 | bits++;
389 | }
390 | }
391 |
392 | return 0;
393 | }
394 |
395 |
396 |
397 | /****************************************************************************************************/
398 |
399 |
400 | /*
401 | Call this function to generate a paletted bitmap with N colors from a 24-bit bitmap.
402 |
403 | srcimage Pointer to the source (24-bit) DIB
404 | numColors The number of colors to reduce the bitmap to. Valid range is 2..256.
405 | quality Quantization quality. Valid range is 1..100.
406 | */
407 | void NeuQuant::quantise(DIB *destimage, DIB *srcimage, int numColors, int quality, int dither)
408 | {
409 | int i,j;
410 | //DIB *destimage = new DIB(srcimage->width, srcimage->height, 8);
411 | //unsigned char *pPalette = (unsigned char*)malloc(768);
412 |
413 | //destimage->palette = pPalette;
414 |
415 | quality /= 3;
416 | if (quality>30)
417 | {
418 | quality = 30;
419 | }
420 | else if (quality<1)
421 | {
422 | quality = 1;
423 | }
424 |
425 | if (numColors<2)
426 | {
427 | numColors = 2;
428 | }
429 | else if (numColors>256)
430 | {
431 | numColors = 256;
432 | }
433 |
434 | netsize = numColors;
435 | initnet(srcimage->bits, srcimage->width*srcimage->height*PIXEL_SIZE, 31-quality);
436 | learn();
437 | unbiasnet();
438 | for (i=0; ipalette[i*3 + j] = network[i][2-j];
443 | }
444 | }
445 | inxbuild();
446 |
447 | if (dither == 2)
448 | {
449 | imgw = srcimage->width; imgh = srcimage->height;
450 | }
451 |
452 | //printf("NeuQuant: Mapping colors.\n");
453 |
454 | for (i=srcimage->height-1; i>=0; i--)
455 | {
456 | if (i&1)
457 | for (j=srcimage->width-1; j>=0; j--)
458 | {
459 | destimage->bits[i*srcimage->width + j] = inxsearch(srcimage->bits[i*srcimage->width*PIXEL_SIZE + j*PIXEL_SIZE],
460 | srcimage->bits[i*srcimage->width*PIXEL_SIZE + j*PIXEL_SIZE + 1],
461 | srcimage->bits[i*srcimage->width*PIXEL_SIZE + j*PIXEL_SIZE + 2],
462 | dither,
463 | j,
464 | i);
465 | }
466 | else
467 | for (j=0; jwidth; j++)
468 | {
469 | destimage->bits[i*srcimage->width + j] = inxsearch(srcimage->bits[i*srcimage->width*PIXEL_SIZE + j*PIXEL_SIZE],
470 | srcimage->bits[i*srcimage->width*PIXEL_SIZE + j*PIXEL_SIZE + 1],
471 | srcimage->bits[i*srcimage->width*PIXEL_SIZE + j*PIXEL_SIZE + 2],
472 | dither,
473 | j,
474 | i);
475 | }
476 | }
477 |
478 | }
479 |
480 |
481 | /* Network Definitions
482 | ------------------- */
483 |
484 | #define maxnetpos (netsize-1)
485 | #define netbiasshift 4 /* bias for colour values */
486 | #define ncycles 100 /* no. of learning cycles */
487 |
488 | /* defs for freq and bias */
489 | #define intbiasshift 16 /* bias for fractions */
490 | #define intbias (((int) 1)<>betashift) /* beta = 1/1024 */
495 | #define betagamma (intbias<<(gammashift-betashift))
496 |
497 | /* defs for decreasing radius factor */
498 | #define initrad (netsize>>3) /* for 256 cols, radius starts */
499 | #define radiusbiasshift 6 /* at 32.0 biased by 6 bits */
500 | #define radiusbias (((int) 1)<>= netbiasshift; */
565 | /* Fix based on bug report by Juergen Weigert jw@suse.de */
566 | temp = (network[i][j] + (1 << (netbiasshift - 1))) >> netbiasshift;
567 | if (temp > 255) temp = 255;
568 | network[i][j] = temp;
569 | }
570 | network[i][3] = i; /* record colour no */
571 | }
572 | }
573 |
574 |
575 | /* Output colour map
576 | ----------------- */
577 |
578 | void NeuQuant::writecolourmap(FILE *f)
579 | {
580 | int i,j;
581 |
582 | for (i=2; i>=0; i--)
583 | for (j=0; j>1;
622 | for (j=previouscol+1; j>1;
628 | for (j=previouscol+1; j<256; j++) netindex[j] = maxnetpos; /* really 256 */
629 | }
630 |
631 |
632 | inline int luma_diff(int r1,int g1,int b1,int r2,int g2,int b2)
633 | {
634 | return (r1*299 + g1*587 + b1*114) - (r2*299 + g2*587 + b2*114);
635 | }
636 |
637 | /* Search for BGR values 0..255 (after net is unbiased) and return colour index
638 | ---------------------------------------------------------------------------- */
639 |
640 | int NeuQuant::inxsearch(int b,int g,int r,int dither,int xpos,int ypos)
641 | {
642 | int i,j,dist,a;
643 | int *p;
644 | int bestd,bestd_dark,bestd_bright;
645 | int bestd_r,bestd_g,bestd_b,dist_r,dist_g,dist_b;
646 | int best;
647 | int lumadiff;
648 | int darker,brighter;
649 | //float *errors = &dither_errors[xpos*ypos*3];
650 |
651 | bestd = 1000; /* biggest possible dist is 256*3 */
652 | best = -1;
653 | bestd_dark = 1000;
654 | bestd_bright = 1000;
655 | darker = brighter = -1;
656 | i = netindex[g]; /* index on g */
657 | j = i-1; /* start at netindex[g] and work outwards */
658 |
659 | if (dither == 1) {
660 | while ((i<(int)netsize) || (j>=0)) {
661 | if (i<(int)netsize) {
662 | p = network[i];
663 | dist = p[1] - g; /* inx key */
664 | lumadiff = luma_diff(p[2],p[1],p[0],r,g,b);
665 | if (dist >= bestd) i = netsize; /* stop iter */
666 | else {
667 | i++;
668 | if (dist<0) { dist = -dist; }
669 | a = p[0] - b; if (a<0) { a = -a; }
670 | dist += a;
671 | if (1) { //(dist0) && (dist=0) {
694 | p = network[j];
695 | lumadiff = luma_diff(p[2],p[1],p[0],r,g,b);
696 | dist = g - p[1]; /* inx key - reverse dif */
697 | if (dist >= bestd) j = -1; /* stop iter */
698 | else {
699 | j--;
700 | //isdark = 0;
701 | if (dist<0) { dist = -dist; }
702 | a = p[0] - b; if (a<0) {a = -a; }
703 | dist += a;
704 | if (1) { //(dist0) && (dist=0)) {
729 | if (i<(int)netsize) {
730 | p = network[i];
731 | dist = p[1] - g; /* inx key */
732 | if (dist >= bestd) i = netsize; /* stop iter */
733 | else {
734 | i++;
735 | if (dist<0) dist = -dist;
736 | a = p[0] - b; if (a<0) a = -a;
737 | dist += a;
738 | if (dist=0) {
746 | p = network[j];
747 | dist = g - p[1]; /* inx key - reverse dif */
748 | if (dist >= bestd) j = -1; /* stop iter */
749 | else {
750 | j--;
751 | if (dist<0) dist = -dist;
752 | a = p[0] - b; if (a<0) a = -a;
753 | dist += a;
754 | if (dist>(intbiasshift-netbiasshift));
812 | if (biasdist> betashift);
814 | *f++ -= betafreq;
815 | *p++ += (betafreq<netsize) hi=netsize;
849 |
850 | j = i+1;
851 | k = i-1;
852 | q = radpower;
853 | while ((jlo)) {
854 | a = (*(++q));
855 | if (jlo) {
865 | p = network[k];
866 | *p -= (a*(*p - b)) / alpharadbias;
867 | p++;
868 | *p -= (a*(*p - g)) / alpharadbias;
869 | p++;
870 | *p -= (a*(*p - r)) / alpharadbias;
871 | k--;
872 | }
873 | }
874 | }
875 |
876 |
877 | /* Main Learning Loop
878 | ------------------ */
879 |
880 | void NeuQuant::learn()
881 | {
882 | int i,j,b,g,r;
883 | int radius,rad,alpha,step,delta,samplepixels;
884 | //unsigned char *p;
885 | unsigned int *p;
886 | unsigned char *lim;
887 |
888 | alphadec = 30 + ((samplefac-1)/3);
889 | p = (unsigned int*)thepicture;
890 | lim = thepicture + lengthcount;
891 | samplepixels = lengthcount/(PIXEL_SIZE*samplefac);
892 | delta = samplepixels/ncycles;
893 | alpha = initalpha;
894 | radius = initradius;
895 |
896 | rad = radius >> radiusbiasshift;
897 | if (rad <= 1) rad = 0;
898 | for (i=0; i> 8) & 0xff) << netbiasshift;
921 | r = (((*p) >> 16) & 0xff) << netbiasshift;
922 | j = contest(b,g,r);
923 |
924 | altersingle(alpha,j,b,g,r);
925 | if (rad) alterneigh(rad,j,b,g,r); /* alter neighbours */
926 |
927 | p += step;
928 | if (p >= (unsigned int *)lim) p -= lengthcount;
929 |
930 | i++;
931 | if (i%delta == 0) {
932 | alpha -= alpha / alphadec;
933 | radius -= radius / radiusdec;
934 | rad = radius >> radiusbiasshift;
935 | if (rad <= 1) rad = 0;
936 | for (j=0; j