/Users/lyon/j4p/src/ip/gif/stills/WriteGIF.java
|
1 /**
2 * WriteGIF is a class that takes an Image and saves it to
3 * a GIF format file.
4 *
5 * Based upon gifsave.c, which was written and released by
6 * Sverre H. Huseby. Ported to Java by Adam Doppelt of Brown
7 * University.
8 * Modified and integrated with the ImageProc program for
9 * CS411X, advanced Java Programming at the University of
10 * Bridgeport by Victor Silva
11 * Modified again by D. Lyon 7/8/01
12 *
13 */
14 package ip.gif.stills;
15
16 import java.io.FileOutputStream;
17
18 public class WriteGIF {
19 short width_, height_;
20 int numColors_;
21 byte pixels_[], colors_[];
22
23 ScreenDescriptor sd_;
24 ImageDescriptor id_;
25
26 public static void toFile(java.awt.Image img, String fname) {
27 // encode the image as a GIF
28 try {
29 WriteGIF wg = new WriteGIF(img);
30 wg.toOutputStream(
31 new java.io.BufferedOutputStream(
32 new FileOutputStream(fname))
33 );
34 } catch (Exception e) {
35 System.out.println("Save GIF Exception!");
36 }
37 }
38
39 public WriteGIF(java.awt.Image image) throws java.awt.AWTException {
40 width_ = (short) image.getWidth(null);
41 height_ = (short) image.getHeight(null);
42
43 int values[] = new int[width_ * height_];
44 java.awt.image.PixelGrabber grabber = new java.awt.image.PixelGrabber(
45 image, 0, 0, width_, height_, values, 0, width_);
46
47 try {
48 if (grabber.grabPixels() != true)
49 throw new java.awt.AWTException("Grabber returned false: " +
50 grabber.status());
51 } catch (InterruptedException e) {
52 }
53 ;
54
55 //--------------------------------------------------------------
56 // TODO -> Possible Speed up - do it a row at a time, a la ACME
57 //--------------------------------------------------------------
58 byte r[][] = new byte[width_][height_];
59 byte g[][] = new byte[width_][height_];
60 byte b[][] = new byte[width_][height_];
61 int index = 0;
62
63 for (int y = 0; y < height_; ++y) {
64 for (int x = 0; x < width_; ++x) {
65 r[x][y] = (byte) ((values[index] >> 16) & 0xFF);
66 g[x][y] = (byte) ((values[index] >> 8) & 0xFF);
67 b[x][y] = (byte) ((values[index]) & 0xFF);
68 ++index;
69 }
70 }
71 toIndexedColor(r, g, b);
72 }
73
74
75 public void toOutputStream(java.io.OutputStream os)
76 throws java.io.IOException {
77 BitUtils.writeString(os, "GIF87a");
78 ScreenDescriptor sd =
79 new ScreenDescriptor(
80 width_, height_, numColors_);
81 sd.write(os);
82 os.write(colors_, 0, colors_.length);
83 ImageDescriptor id =
84 new ImageDescriptor(width_, height_, ',');
85 id.toOutputStream(os);
86
87 byte codesize = BitUtils.bitsNeeded(numColors_);
88 if (codesize == 1) ++codesize;
89
90 os.write(codesize);
91
92 LZWCompressor.compress(os, codesize, pixels_);
93 os.write(0);
94
95 id = new ImageDescriptor((byte) 0, (byte) 0, ';');
96 id.toOutputStream(os);
97 os.flush();
98 }
99
100 void toIndexedColor(byte r[][], byte g[][], byte b[][])
101 throws java.awt.AWTException {
102 pixels_ = new byte[width_ * height_];
103 colors_ = new byte[256 * 3];
104 int colornum = 0;
105 for (int x = 0; x < width_; ++x) {
106 for (int y = 0; y < height_; ++y) {
107 int search;
108 for (search = 0; search < colornum; ++search) {
109 if (colors_[search * 3] == r[x][y] &&
110 colors_[search * 3 + 1] == g[x][y] &&
111 colors_[search * 3 + 2] == b[x][y]) {
112 break;
113 }
114 }
115
116 // If there are more than 256 colors invoke
117 // the color quantization procedure.
118 //quantization();
119 if (search > 255) {
120 throw new java.awt.AWTException("Too many colors.");
121 }
122
123 pixels_[y * width_ + x] = (byte) search;
124
125 if (search == colornum) {
126 colors_[search * 3] = r[x][y];
127 colors_[search * 3 + 1] = g[x][y];
128 colors_[search * 3 + 2] = b[x][y];
129 ++colornum;
130 }
131 }
132 }
133 numColors_ = 1 << BitUtils.bitsNeeded(colornum);
134 byte copy[] = new byte[numColors_ * 3];
135 System.arraycopy(colors_, 0, copy, 0, numColors_ * 3);
136 colors_ = copy;
137 }
138 }
139
140 class BitFile {
141 java.io.OutputStream output_;
142 byte buffer_[];
143 int index_, bitsLeft_;
144
145 public BitFile(java.io.OutputStream output) {
146 output_ = output;
147 buffer_ = new byte[256];
148 index_ = 0;
149 bitsLeft_ = 8;
150 }
151
152 public void flush() throws java.io.IOException {
153 int numBytes = index_ + (bitsLeft_ == 8 ? 0 : 1);
154 if (numBytes > 0) {
155 output_.write(numBytes);
156 output_.write(buffer_, 0, numBytes);
157 buffer_[0] = 0;
158 index_ = 0;
159 bitsLeft_ = 8;
160 }
161 }
162
163 public void writeBits(int bits, int numbits)
164 throws java.io.IOException {
165 int bitsWritten = 0;
166 int numBytes = 255;
167 do {
168 if ((index_ == 254 && bitsLeft_ == 0)
169 || index_ > 254) {
170 output_.write(numBytes);
171 output_.write(buffer_, 0, numBytes);
172
173 buffer_[0] = 0;
174 index_ = 0;
175 bitsLeft_ = 8;
176 }
177
178 if (numbits <= bitsLeft_) {
179 buffer_[index_] |= (bits & ((1 << numbits) - 1)) <<
180 (8 - bitsLeft_);
181 bitsWritten += numbits;
182 bitsLeft_ -= numbits;
183 numbits = 0;
184 } else {
185 buffer_[index_] |= (bits & ((1 << bitsLeft_) - 1)) <<
186 (8 - bitsLeft_);
187 bitsWritten += bitsLeft_;
188 bits >>= bitsLeft_;
189 numbits -= bitsLeft_;
190 buffer_[++index_] = 0;
191 bitsLeft_ = 8;
192 }
193 } while (numbits != 0);
194 }
195 }
196
197 class LZWStringTable {
198 private final static int RES_CODES = 2;
199 private final static short HASH_FREE = (short) 0xFFFF;
200 private final static short NEXT_FIRST = (short) 0xFFFF;
201 private final static int MAXBITS = 12;
202 private final static int MAXSTR = (1 << MAXBITS);
203 private final static short HASHSIZE = 9973;
204 private final static short HASHSTEP = 2039;
205
206 byte strChr_[];
207 short strNxt_[];
208 short strHsh_[];
209 short numStrings_;
210
211 public LZWStringTable() {
212 strChr_ = new byte[MAXSTR];
213 strNxt_ = new short[MAXSTR];
214 strHsh_ = new short[HASHSIZE];
215 }
216
217 public int addCharString(short index, byte b) {
218 int hshidx;
219
220 if (numStrings_ >= MAXSTR)
221 return 0xFFFF;
222
223 hshidx = hash(index, b);
224 while (strHsh_[hshidx] != HASH_FREE)
225 hshidx = (hshidx + HASHSTEP) % HASHSIZE;
226
227 strHsh_[hshidx] = numStrings_;
228 strChr_[numStrings_] = b;
229 strNxt_[numStrings_] = (index != HASH_FREE) ? index : NEXT_FIRST;
230
231 return numStrings_++;
232 }
233
234 public short findCharString(short index, byte b) {
235 int hshidx, nxtidx;
236
237 if (index == HASH_FREE)
238 return b;
239
240 hshidx = hash(index, b);
241 while ((nxtidx = strHsh_[hshidx]) != HASH_FREE) {
242 if (strNxt_[nxtidx] == index
243 && strChr_[nxtidx] == b)
244 return (short) nxtidx;
245 hshidx = (hshidx + HASHSTEP) % HASHSIZE;
246 }
247
248 return (short) 0xFFFF;
249 }
250
251 public void clearTable(int codesize) {
252 numStrings_ = 0;
253
254 for (int q = 0; q < HASHSIZE; q++)
255 strHsh_[q] = HASH_FREE;
256
257 int w = (1 << codesize) + RES_CODES;
258 for (int q = 0; q < w; q++)
259 addCharString((short) 0xFFFF, (byte) q);
260 }
261
262 static public int hash(short index, byte lastbyte) {
263 return (
264 (int) (
265 (short) (lastbyte << 8) ^ index
266 ) & 0xFFFF
267 ) % HASHSIZE;
268 }
269 }
270
271 class LZWCompressor {
272 public static void compress(
273 java.io.OutputStream os, int codesize,
274 byte toCompress[]) throws java.io.IOException {
275 byte c;
276 short index;
277 int clearcode, endofinfo, numbits, limit, errcode;
278 short prefix = (short) 0xFFFF;
279
280 BitFile bitFile = new BitFile(os);
281 LZWStringTable strings = new LZWStringTable();
282
283 clearcode = 1 << codesize;
284 endofinfo = clearcode + 1;
285
286 numbits = codesize + 1;
287 limit = (1 << numbits) - 1;
288
289 strings.clearTable(codesize);
290 bitFile.writeBits(clearcode, numbits);
291
292 for (int loop = 0; loop < toCompress.length; ++loop) {
293 c = toCompress[loop];
294 if ((index =
295 strings.findCharString(prefix, c)) != -1)
296 prefix = index;
297 else {
298 bitFile.writeBits(prefix, numbits);
299 if (strings.addCharString(prefix, c) > limit) {
300 if (++numbits > 12) {
301 bitFile.writeBits(clearcode, numbits - 1);
302 strings.clearTable(codesize);
303 numbits = codesize + 1;
304 }
305 limit = (1 << numbits) - 1;
306 }
307
308 prefix = (short) ((short) c & 0xFF);
309 }
310 }
311
312 if (prefix != -1) {
313 bitFile.writeBits(prefix, numbits);
314 }
315
316 bitFile.writeBits(endofinfo, numbits);
317 bitFile.flush();
318 }
319 }
320
321 class ScreenDescriptor {
322 public short localScreenWidth_, localScreenHeight_;
323 private byte byte_;
324 public byte backgroundColorIndex_, pixelAspectRatio_;
325
326 public ScreenDescriptor(
327 short width, short height, int numColors) {
328 localScreenWidth_ = width;
329 localScreenHeight_ = height;
330 SetGlobalColorTableSize(
331 (byte) (BitUtils.bitsNeeded(numColors) - 1));
332 SetGlobalColorTableFlag((byte) 1);
333 SetSortFlag((byte) 0);
334 SetColorResolution((byte) 7);
335 backgroundColorIndex_ = 0;
336 pixelAspectRatio_ = 0;
337 }
338
339 public void write(java.io.OutputStream os)
340 throws java.io.IOException {
341 BitUtils.writeWord(os, localScreenWidth_);
342 BitUtils.writeWord(os, localScreenHeight_);
343 os.write(byte_);
344 os.write(backgroundColorIndex_);
345 os.write(pixelAspectRatio_);
346 }
347
348 public void SetGlobalColorTableSize(byte num) {
349 byte_ |= (num & 7);
350 }
351
352 public void SetSortFlag(byte num) {
353 byte_ |= (num & 1) << 3;
354 }
355
356 public void SetColorResolution(byte num) {
357 byte_ |= (num & 7) << 4;
358 }
359
360 public void SetGlobalColorTableFlag(byte num) {
361 byte_ |= (num & 1) << 7;
362 }
363 }
364
365 class ImageDescriptor {
366 public byte separator_;
367 public short leftPosition_, topPosition_, width_, height_;
368 private byte byte_;
369
370 public ImageDescriptor(
371 short width, short height, char separator) {
372 separator_ = (byte) separator;
373 leftPosition_ = 0;
374 topPosition_ = 0;
375 width_ = width;
376 height_ = height;
377 setLocalColorTableSize((byte) 0);
378 setReserved((byte) 0);
379 setSortFlag((byte) 0);
380 setInterlaceFlag((byte) 0);
381 setLocalColorTableFlag((byte) 0);
382 }
383
384 public void toOutputStream(java.io.OutputStream os)
385 throws java.io.IOException {
386 os.write(separator_);
387 BitUtils.writeWord(os, leftPosition_);
388 BitUtils.writeWord(os, topPosition_);
389 BitUtils.writeWord(os, width_);
390 BitUtils.writeWord(os, height_);
391 os.write(byte_);
392 }
393
394 public void setLocalColorTableSize(byte num) {
395 byte_ |= (num & 7);
396 }
397
398 public void setReserved(byte num) {
399 byte_ |= (num & 3) << 3;
400 }
401
402 public void setSortFlag(byte num) {
403 byte_ |= (num & 1) << 5;
404 }
405
406 public void setInterlaceFlag(byte num) {
407 byte_ |= (num & 1) << 6;
408 }
409
410 public void setLocalColorTableFlag(byte num) {
411 byte_ |= (num & 1) << 7;
412 }
413 }
414
415 class BitUtils {
416 public static byte bitsNeeded(int n) {
417 byte ret = 1;
418
419 if (n-- == 0) return 0;
420
421 while ((n >>= 1) != 0)
422 ++ret;
423
424 return ret;
425 }
426
427 public static void writeWord(
428 java.io.OutputStream os, short w)
429 throws java.io.IOException {
430 os.write(w & 0xFF);
431 os.write((w >> 8) & 0xFF);
432 }
433
434 static void writeString(
435 java.io.OutputStream os, String string)
436 throws java.io.IOException {
437 for (int i = 0; i < string.length(); ++i)
438 os.write((byte) (string.charAt(i)));
439 }
440 }
441