0001: //** Copyright Statement ***************************************************
0002: //The Salmon Open Framework for Internet Applications (SOFIA)
0003: // Copyright (C) 1999 - 2002, Salmon LLC
0004: //
0005: // This program is free software; you can redistribute it and/or
0006: // modify it under the terms of the GNU General Public License version 2
0007: // as published by the Free Software Foundation;
0008: //
0009: // This program is distributed in the hope that it will be useful,
0010: // but WITHOUT ANY WARRANTY; without even the implied warranty of
0011: // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
0012: // GNU General Public License for more details.
0013: //
0014: // You should have received a copy of the GNU General Public License
0015: // along with this program; if not, write to the Free Software
0016: // Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
0017: //
0018: // For more information please visit http://www.salmonllc.com
0019: //** End Copyright Statement ***************************************************
0020: package com.salmonllc.util;
0021:
0022: // ImageEncoder - abstract class for writing out an image
0023: //
0024: // Copyright (C) 1996 by Jef Poskanzer <jef@acme.com>. All rights reserved.
0025: //
0026: // Redistribution and use in source and binary forms, with or without
0027: // modification, are permitted provided that the following conditions
0028: // are met:
0029: // 1. Redistributions of source code must retain the above copyright
0030: // notice, this list of conditions and the following disclaimer.
0031: // 2. Redistributions in binary form must reproduce the above copyright
0032: // notice, this list of conditions and the following disclaimer in the
0033: // documentation and/or other materials provided with the distribution.
0034: //
0035: // THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
0036: // ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
0037: // IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
0038: // ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
0039: // FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
0040: // DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
0041: // OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
0042: // HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
0043: // LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
0044: // OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
0045: // SUCH DAMAGE.
0046: //
0047: // Visit the ACME Labs Java page for up-to-date versions of this and other
0048: // fine Java utilities: http://www.acme.com/java/
0049:
0050: import java.util.*;
0051: import java.io.*;
0052: import java.awt.Image;
0053: import java.awt.image.*;
0054: import java.awt.*;
0055:
0056: /**
0057: * Class for writing out an image as a gif.
0058: */
0059:
0060: public class GifEncoder implements ImageConsumer {
0061:
0062: protected OutputStream out;
0063:
0064: private ImageProducer producer;
0065: private int width = -1;
0066: private int height = -1;
0067: private int hintflags = 0;
0068: private boolean started = false;
0069: private boolean encoding;
0070: private IOException iox;
0071: private static final ColorModel rgbModel = ColorModel
0072: .getRGBdefault();
0073: private Hashtable props = null;
0074:
0075: private boolean accumulate = false;
0076: private int[] accumulator;
0077:
0078: private boolean interlace = false;
0079:
0080: int[][] rgbPixels;
0081:
0082: IntHashtable colorHash;
0083:
0084: // Adapted from ppmtogif, which is based on GIFENCOD by David
0085: // Rowley <mgardi@watdscu.waterloo.edu>. Lempel-Zim compression
0086: // based on "compress".
0087:
0088: int Width, Height;
0089: boolean Interlace;
0090: int curx, cury;
0091: int CountDown;
0092: int Pass = 0;
0093:
0094: static final int EOF = -1;
0095:
0096: // GIFCOMPR.C - GIF Image compression routines
0097: //
0098: // Lempel-Ziv compression based on 'compress'. GIF modifications by
0099: // David Rowley (mgardi@watdcsu.waterloo.edu)
0100:
0101: // General DEFINEs
0102:
0103: static final int BITS = 12;
0104:
0105: static final int HSIZE = 5003; // 80% occupancy
0106:
0107: // GIF Image compression - modified 'compress'
0108: //
0109: // Based on: compress.c - File compression ala IEEE Computer, June 1984.
0110: //
0111: // By Authors: Spencer W. Thomas (decvax!harpo!utah-cs!utah-gr!thomas)
0112: // Jim McKie (decvax!mcvax!jim)
0113: // Steve Davies (decvax!vax135!petsd!peora!srd)
0114: // Ken Turkowski (decvax!decwrl!turtlevax!ken)
0115: // James A. Woods (decvax!ihnp4!ames!jaw)
0116: // Joe Orost (decvax!vax135!petsd!joe)
0117:
0118: int n_bits; // number of bits/code
0119: int maxbits = BITS; // user settable max # bits/code
0120: int maxcode; // maximum code, given n_bits
0121: int maxmaxcode = 1 << BITS; // should NEVER generate this code
0122:
0123: int[] htab = new int[HSIZE];
0124: int[] codetab = new int[HSIZE];
0125:
0126: int hsize = HSIZE; // for dynamic table sizing
0127:
0128: int free_ent = 0; // first unused entry
0129:
0130: // block compression parameters -- after all codes are used up,
0131: // and compression rate changes, start over.
0132: boolean clear_flg = false;
0133:
0134: // Algorithm: use open addressing double hashing (no chaining) on the
0135: // prefix code / next character combination. We do a variant of Knuth's
0136: // algorithm D (vol. 3, sec. 6.4) along with G. Knott's relatively-prime
0137: // secondary probe. Here, the modular division first probe is gives way
0138: // to a faster exclusive-or manipulation. Also do block compression with
0139: // an adaptive reset, whereby the code table is cleared when the compression
0140: // ratio decreases, but after the table fills. The variable-length output
0141: // codes are re-sized at this point, and a special CLEAR code is generated
0142: // for the decompressor. Late addition: construct the table according to
0143: // file size for noticeable speed improvement on small files. Please direct
0144: // questions about this implementation to ames!jaw.
0145:
0146: int g_init_bits;
0147:
0148: int ClearCode;
0149: int EOFCode;
0150:
0151: // output
0152: //
0153: // Output the given code.
0154: // Inputs:
0155: // code: A n_bits-bit integer. If == -1, then EOF. This assumes
0156: // that n_bits =< wordsize - 1.
0157: // Outputs:
0158: // Outputs code to the file.
0159: // Assumptions:
0160: // Chars are 8 bits long.
0161: // Algorithm:
0162: // Maintain a BITS character long buffer (so that 8 codes will
0163: // fit in it exactly). Use the VAX insv instruction to insert each
0164: // code in turn. When the buffer fills up empty it and start over.
0165:
0166: int cur_accum = 0;
0167: int cur_bits = 0;
0168:
0169: int masks[] = { 0x0000, 0x0001, 0x0003, 0x0007, 0x000F, 0x001F,
0170: 0x003F, 0x007F, 0x00FF, 0x01FF, 0x03FF, 0x07FF, 0x0FFF,
0171: 0x1FFF, 0x3FFF, 0x7FFF, 0xFFFF };
0172:
0173: // GIF Specific routines
0174:
0175: // Number of characters so far in this 'packet'
0176: int a_count;
0177:
0178: // Define the storage for the packet accumulator
0179: byte[] accum = new byte[256];
0180:
0181: class gifHashitem {
0182: public int rgb;
0183: public int count;
0184: public int index;
0185: public boolean isTransparent;
0186:
0187: public gifHashitem(int rgb, int count, int index,
0188: boolean isTransparent) {
0189: this .rgb = rgb;
0190: this .count = count;
0191: this .index = index;
0192: this .isTransparent = isTransparent;
0193: }
0194: }
0195:
0196: class IntHashtableEntry {
0197: int hash;
0198: int key;
0199: Object value;
0200: IntHashtableEntry next;
0201:
0202: protected Object clone() {
0203: IntHashtableEntry entry = new IntHashtableEntry();
0204: entry.hash = hash;
0205: entry.key = key;
0206: entry.value = value;
0207: entry.next = (next != null) ? (IntHashtableEntry) next
0208: .clone() : null;
0209: return entry;
0210: }
0211: }
0212:
0213: class IntHashtable extends Dictionary implements Cloneable {
0214: private IntHashtableEntry table[];
0215: private int count;
0216: private int threshold;
0217: private float loadFactor;
0218:
0219: public IntHashtable() {
0220: this (101, 0.75f);
0221: }
0222:
0223: public IntHashtable(int initialCapacity) {
0224: this (initialCapacity, 0.75f);
0225: }
0226:
0227: public IntHashtable(int initialCapacity, float loadFactor) {
0228: if (initialCapacity <= 0 || loadFactor <= 0.0)
0229: throw new IllegalArgumentException();
0230: this .loadFactor = loadFactor;
0231: table = new IntHashtableEntry[initialCapacity];
0232: threshold = (int) (initialCapacity * loadFactor);
0233: }
0234:
0235: public synchronized void clear() {
0236: IntHashtableEntry tab[] = table;
0237: for (int index = tab.length; --index >= 0;)
0238: tab[index] = null;
0239: count = 0;
0240: }
0241:
0242: public synchronized Object clone() {
0243: try {
0244: IntHashtable t = (IntHashtable) super .clone();
0245: t.table = new IntHashtableEntry[table.length];
0246: for (int i = table.length; i-- > 0;)
0247: t.table[i] = (table[i] != null) ? (IntHashtableEntry) table[i]
0248: .clone()
0249: : null;
0250: return t;
0251: } catch (CloneNotSupportedException e) {
0252: throw new InternalError();
0253: }
0254: }
0255:
0256: public synchronized boolean contains(Object value) {
0257: if (value == null)
0258: throw new NullPointerException();
0259: IntHashtableEntry tab[] = table;
0260: for (int i = tab.length; i-- > 0;) {
0261: for (IntHashtableEntry e = tab[i]; e != null; e = e.next) {
0262: if (e.value.equals(value))
0263: return true;
0264: }
0265: }
0266: return false;
0267: }
0268:
0269: public synchronized boolean containsKey(int key) {
0270: IntHashtableEntry tab[] = table;
0271: int hash = key;
0272: int index = (hash & 0x7FFFFFFF) % tab.length;
0273: for (IntHashtableEntry e = tab[index]; e != null; e = e.next) {
0274: if (e.hash == hash && e.key == key)
0275: return true;
0276: }
0277: return false;
0278: }
0279:
0280: public synchronized Enumeration elements() {
0281: return new IntHashtableEnumerator(table, false);
0282: }
0283:
0284: public synchronized Object get(int key) {
0285: IntHashtableEntry tab[] = table;
0286: int hash = key;
0287: int index = (hash & 0x7FFFFFFF) % tab.length;
0288: for (IntHashtableEntry e = tab[index]; e != null; e = e.next) {
0289: if (e.hash == hash && e.key == key)
0290: return e.value;
0291: }
0292: return null;
0293: }
0294:
0295: public Object get(Object okey) {
0296: if (!(okey instanceof Integer))
0297: throw new InternalError("key is not an Integer");
0298: Integer ikey = (Integer) okey;
0299: int key = ikey.intValue();
0300: return get(key);
0301: }
0302:
0303: public boolean isEmpty() {
0304: return count == 0;
0305: }
0306:
0307: public synchronized Enumeration keys() {
0308: return new IntHashtableEnumerator(table, true);
0309: }
0310:
0311: public synchronized Object put(int key, Object value) {
0312: if (value == null)
0313: throw new NullPointerException();
0314:
0315: IntHashtableEntry tab[] = table;
0316: int hash = key;
0317: int index = (hash & 0x7FFFFFFF) % tab.length;
0318: for (IntHashtableEntry e = tab[index]; e != null; e = e.next) {
0319: if (e.hash == hash && e.key == key) {
0320: Object old = e.value;
0321: e.value = value;
0322: return old;
0323: }
0324: }
0325:
0326: if (count >= threshold) {
0327: rehash();
0328: return put(key, value);
0329: }
0330:
0331: IntHashtableEntry e = new IntHashtableEntry();
0332: e.hash = hash;
0333: e.key = key;
0334: e.value = value;
0335: e.next = tab[index];
0336: tab[index] = e;
0337: ++count;
0338: return null;
0339: }
0340:
0341: public Object put(Object okey, Object value) {
0342: if (!(okey instanceof Integer))
0343: throw new InternalError("key is not an Integer");
0344: Integer ikey = (Integer) okey;
0345: int key = ikey.intValue();
0346: return put(key, value);
0347: }
0348:
0349: protected void rehash() {
0350: int oldCapacity = table.length;
0351: IntHashtableEntry oldTable[] = table;
0352:
0353: int newCapacity = oldCapacity * 2 + 1;
0354: IntHashtableEntry newTable[] = new IntHashtableEntry[newCapacity];
0355:
0356: threshold = (int) (newCapacity * loadFactor);
0357: table = newTable;
0358:
0359: for (int i = oldCapacity; i-- > 0;) {
0360: for (IntHashtableEntry old = oldTable[i]; old != null;) {
0361: IntHashtableEntry e = old;
0362: old = old.next;
0363:
0364: int index = (e.hash & 0x7FFFFFFF) % newCapacity;
0365: e.next = newTable[index];
0366: newTable[index] = e;
0367: }
0368: }
0369: }
0370:
0371: public synchronized Object remove(int key) {
0372: IntHashtableEntry tab[] = table;
0373: int hash = key;
0374: int index = (hash & 0x7FFFFFFF) % tab.length;
0375: for (IntHashtableEntry e = tab[index], prev = null; e != null; prev = e, e = e.next) {
0376: if (e.hash == hash && e.key == key) {
0377: if (prev != null)
0378: prev.next = e.next;
0379: else
0380: tab[index] = e.next;
0381: --count;
0382: return e.value;
0383: }
0384: }
0385: return null;
0386: }
0387:
0388: public Object remove(Object okey) {
0389: if (!(okey instanceof Integer))
0390: throw new InternalError("key is not an Integer");
0391: Integer ikey = (Integer) okey;
0392: int key = ikey.intValue();
0393: return remove(key);
0394: }
0395:
0396: public int size() {
0397: return count;
0398: }
0399:
0400: public synchronized String toString() {
0401: int max = size() - 1;
0402: StringBuffer buf = new StringBuffer();
0403: Enumeration k = keys();
0404: Enumeration e = elements();
0405: buf.append("{");
0406:
0407: for (int i = 0; i <= max; ++i) {
0408: String s1 = k.nextElement().toString();
0409: String s2 = e.nextElement().toString();
0410: buf.append(s1 + "=" + s2);
0411: if (i < max)
0412: buf.append(", ");
0413: }
0414: buf.append("}");
0415: return buf.toString();
0416: }
0417: }
0418:
0419: class IntHashtableEnumerator implements Enumeration {
0420: boolean keys;
0421: int index;
0422: IntHashtableEntry table[];
0423: IntHashtableEntry entry;
0424:
0425: IntHashtableEnumerator(IntHashtableEntry table[], boolean keys) {
0426: this .table = table;
0427: this .keys = keys;
0428: this .index = table.length;
0429: }
0430:
0431: public boolean hasMoreElements() {
0432: if (entry != null)
0433: return true;
0434: while (index-- > 0)
0435: if ((entry = table[index]) != null)
0436: return true;
0437: return false;
0438: }
0439:
0440: public Object nextElement() {
0441: if (entry == null)
0442: while ((index-- > 0)
0443: && ((entry = table[index]) == null))
0444: ;
0445: if (entry != null) {
0446: IntHashtableEntry e = entry;
0447: entry = e.next;
0448: return keys ? new Integer(e.key) : e.value;
0449: }
0450: throw new NoSuchElementException("IntHashtableEnumerator");
0451: }
0452: }
0453:
0454: class TransparentFilter extends RGBImageFilter {
0455: int transparentRGB;
0456:
0457: public TransparentFilter(Color color) {
0458: transparentRGB = color.getRGB() & 0xFFFFFF;
0459: canFilterIndexColorModel = true;
0460: }
0461:
0462: public int filterRGB(int x, int y, int rgb) {
0463: if ((rgb & 0xFFFFFF) == transparentRGB)
0464: return 0;
0465:
0466: return rgb;
0467: }
0468: }
0469:
0470: /// Constructor.
0471: // @param producer The ImageProducer to encode.
0472: // @param out The stream to write the bytes to.
0473: private GifEncoder(ImageProducer producer, OutputStream out)
0474: throws IOException {
0475: this .producer = producer;
0476: this .out = out;
0477: }
0478:
0479: /**
0480: * Constructor
0481: * @param img The image to encode.
0482: * @param out The stream to write the bytes to.
0483: */
0484: public GifEncoder(Image img, OutputStream out) throws IOException {
0485: this (img.getSource(), out);
0486: }
0487:
0488: /**
0489: * Constructor from Image with interlace setting.
0490: * @param img The image to encode.
0491: * @param out The stream to write the GIF to.
0492: * @param interlace Whether to interlace.
0493: */
0494: public GifEncoder(Image img, OutputStream out, boolean interlace)
0495: throws IOException {
0496: this (img, out);
0497: this .interlace = interlace;
0498: }
0499:
0500: /** Constructor from Image with interlace setting.
0501: * @param img The image to encode.
0502: * @param out The stream to write the GIF to.
0503: * @param interlace Whether to interlace.
0504: * @param transparentColor The color to use for transparency
0505: */
0506: public GifEncoder(Image img, OutputStream out, boolean interlace,
0507: Color transparentColor) throws IOException {
0508: RGBImageFilter f = new TransparentFilter(transparentColor);
0509: this .producer = new FilteredImageSource(img.getSource(), f);
0510: this .out = out;
0511: this .interlace = interlace;
0512: }
0513:
0514: // Bump the 'curx' and 'cury' to point to the next pixel
0515: void BumpPixel() {
0516: // Bump the current X position
0517: ++curx;
0518:
0519: // If we are at the end of a scan line, set curx back to the beginning
0520: // If we are interlaced, bump the cury to the appropriate spot,
0521: // otherwise, just increment it.
0522: if (curx == Width) {
0523: curx = 0;
0524:
0525: if (!Interlace)
0526: ++cury;
0527: else {
0528: switch (Pass) {
0529: case 0:
0530: cury += 8;
0531: if (cury >= Height) {
0532: ++Pass;
0533: cury = 4;
0534: }
0535: break;
0536:
0537: case 1:
0538: cury += 8;
0539: if (cury >= Height) {
0540: ++Pass;
0541: cury = 2;
0542: }
0543: break;
0544:
0545: case 2:
0546: cury += 4;
0547: if (cury >= Height) {
0548: ++Pass;
0549: cury = 1;
0550: }
0551: break;
0552:
0553: case 3:
0554: cury += 2;
0555: break;
0556: }
0557: }
0558: }
0559: }
0560:
0561: // Set up the 'byte output' routine
0562: void char_init() {
0563: a_count = 0;
0564: }
0565:
0566: // Add a character to the end of the current packet, and if it is 254
0567: // characters, flush the packet to disk.
0568: void char_out(byte c, OutputStream outs) throws IOException {
0569: accum[a_count++] = c;
0570: if (a_count >= 254)
0571: flush_char(outs);
0572: }
0573:
0574: // Clear out the hash table
0575:
0576: // table clear for block compress
0577: void cl_block(OutputStream outs) throws IOException {
0578: cl_hash(hsize);
0579: free_ent = ClearCode + 2;
0580: clear_flg = true;
0581:
0582: output(ClearCode, outs);
0583: }
0584:
0585: // reset code table
0586: void cl_hash(int hsize) {
0587: for (int i = 0; i < hsize; ++i)
0588: htab[i] = -1;
0589: }
0590:
0591: void compress(int init_bits, OutputStream outs) throws IOException {
0592: int fcode;
0593: int i /* = 0 */;
0594: int c;
0595: int ent;
0596: int disp;
0597: int hsize_reg;
0598: int hshift;
0599:
0600: // Set up the globals: g_init_bits - initial number of bits
0601: g_init_bits = init_bits;
0602:
0603: // Set up the necessary values
0604: clear_flg = false;
0605: n_bits = g_init_bits;
0606: maxcode = MAXCODE(n_bits);
0607:
0608: ClearCode = 1 << (init_bits - 1);
0609: EOFCode = ClearCode + 1;
0610: free_ent = ClearCode + 2;
0611:
0612: char_init();
0613:
0614: ent = GIFNextPixel();
0615:
0616: hshift = 0;
0617: for (fcode = hsize; fcode < 65536; fcode *= 2)
0618: ++hshift;
0619: hshift = 8 - hshift; // set hash code range bound
0620:
0621: hsize_reg = hsize;
0622: cl_hash(hsize_reg); // clear hash table
0623:
0624: output(ClearCode, outs);
0625:
0626: outer_loop: while ((c = GIFNextPixel()) != EOF) {
0627: fcode = (c << maxbits) + ent;
0628: i = (c << hshift) ^ ent; // xor hashing
0629:
0630: if (htab[i] == fcode) {
0631: ent = codetab[i];
0632: continue;
0633: } else if (htab[i] >= 0) // non-empty slot
0634: {
0635: disp = hsize_reg - i; // secondary hash (after G. Knott)
0636: if (i == 0)
0637: disp = 1;
0638: do {
0639: if ((i -= disp) < 0)
0640: i += hsize_reg;
0641:
0642: if (htab[i] == fcode) {
0643: ent = codetab[i];
0644: continue outer_loop;
0645: }
0646: } while (htab[i] >= 0);
0647: }
0648: output(ent, outs);
0649: ent = c;
0650: if (free_ent < maxmaxcode) {
0651: codetab[i] = free_ent++; // code -> hashtable
0652: htab[i] = fcode;
0653: } else
0654: cl_block(outs);
0655: }
0656: // Put out the final code.
0657: output(ent, outs);
0658: output(EOFCode, outs);
0659: }
0660:
0661: // Our own methods.
0662:
0663: /**
0664: * Call this method after initialization to do the encoding
0665: */
0666: public synchronized void encode() throws IOException {
0667: encoding = true;
0668: iox = null;
0669: producer.startProduction(this );
0670: while (encoding)
0671: try {
0672: wait();
0673: } catch (InterruptedException e) {
0674: }
0675: if (iox != null)
0676: throw iox;
0677: }
0678:
0679: void encodeDone() throws IOException {
0680: int transparentIndex = -1;
0681: int transparentRgb = -1;
0682: // Put all the pixels into a hash table.
0683: colorHash = new IntHashtable();
0684: int index = 0;
0685: for (int row = 0; row < height; ++row) {
0686: for (int col = 0; col < width; ++col) {
0687: int rgb = rgbPixels[row][col];
0688: boolean isTransparent = ((rgb >>> 24) < 0x80);
0689: if (isTransparent) {
0690: if (transparentIndex < 0) {
0691: // First transparent color; remember it.
0692: transparentIndex = index;
0693: transparentRgb = rgb;
0694: } else if (rgb != transparentRgb) {
0695: // A second transparent color; replace it with
0696: // the first one.
0697: rgbPixels[row][col] = rgb = transparentRgb;
0698: }
0699: }
0700: gifHashitem item = (gifHashitem) colorHash.get(rgb);
0701: if (item == null) {
0702: if (index >= 256)
0703: throw new IOException(
0704: "too many colors for a GIF");
0705: item = new gifHashitem(rgb, 1, index, isTransparent);
0706: ++index;
0707: colorHash.put(rgb, item);
0708: } else
0709: ++item.count;
0710: }
0711: }
0712:
0713: // Figure out how many bits to use.
0714: int logColors;
0715: if (index <= 2)
0716: logColors = 1;
0717: else if (index <= 4)
0718: logColors = 2;
0719: else if (index <= 16)
0720: logColors = 4;
0721: else
0722: logColors = 8;
0723:
0724: // Turn colors into colormap entries.
0725: int mapSize = 1 << logColors;
0726: byte[] reds = new byte[mapSize];
0727: byte[] grns = new byte[mapSize];
0728: byte[] blus = new byte[mapSize];
0729: for (Enumeration e = colorHash.elements(); e.hasMoreElements();) {
0730: gifHashitem item = (gifHashitem) e.nextElement();
0731: reds[item.index] = (byte) ((item.rgb >> 16) & 0xff);
0732: grns[item.index] = (byte) ((item.rgb >> 8) & 0xff);
0733: blus[item.index] = (byte) (item.rgb & 0xff);
0734: }
0735:
0736: GIFEncode(out, width, height, interlace, (byte) 0,
0737: transparentIndex, logColors, reds, grns, blus);
0738: }
0739:
0740: private void encodeFinish() throws IOException {
0741: if (accumulate) {
0742: encodePixels(0, 0, width, height, accumulator, 0, width);
0743: accumulator = null;
0744: accumulate = false;
0745: }
0746: }
0747:
0748: void encodePixels(int x, int y, int w, int h, int[] rgbPixels,
0749: int off, int scansize) throws IOException {
0750: // Save the pixels.
0751: for (int row = 0; row < h; ++row)
0752: System.arraycopy(rgbPixels, row * scansize + off,
0753: this .rgbPixels[y + row], x, w);
0754:
0755: }
0756:
0757: private void encodePixelsWrapper(int x, int y, int w, int h,
0758: int[] rgbPixels, int off, int scansize) throws IOException {
0759: if (!started) {
0760: started = true;
0761: encodeStart(width, height);
0762: if ((hintflags & TOPDOWNLEFTRIGHT) == 0) {
0763: accumulate = true;
0764: accumulator = new int[width * height];
0765: }
0766: }
0767: if (accumulate)
0768: for (int row = 0; row < h; ++row)
0769: System.arraycopy(rgbPixels, row * scansize + off,
0770: accumulator, (y + row) * width + x, w);
0771: else
0772: encodePixels(x, y, w, h, rgbPixels, off, scansize);
0773: }
0774:
0775: void encodeStart(int width, int height) throws IOException {
0776: this .width = width;
0777: this .height = height;
0778: rgbPixels = new int[height][width];
0779: }
0780:
0781: // Flush the packet to disk, and reset the accumulator
0782: void flush_char(OutputStream outs) throws IOException {
0783: if (a_count > 0) {
0784: outs.write(a_count);
0785: outs.write(accum, 0, a_count);
0786: a_count = 0;
0787: }
0788: }
0789:
0790: byte GetPixel(int x, int y) throws IOException {
0791: gifHashitem item = (gifHashitem) colorHash.get(rgbPixels[y][x]);
0792: if (item == null)
0793: throw new IOException("color not found");
0794: return (byte) item.index;
0795: }
0796:
0797: void GIFEncode(OutputStream outs, int Width, int Height,
0798: boolean Interlace, byte Background, int Transparent,
0799: int BitsPerPixel, byte[] Red, byte[] Green, byte[] Blue)
0800: throws IOException {
0801: byte B;
0802: int LeftOfs, TopOfs;
0803: int ColorMapSize;
0804: int InitCodeSize;
0805: int i;
0806:
0807: this .Width = Width;
0808: this .Height = Height;
0809: this .Interlace = Interlace;
0810: ColorMapSize = 1 << BitsPerPixel;
0811: LeftOfs = TopOfs = 0;
0812:
0813: // Calculate number of bits we are expecting
0814: CountDown = Width * Height;
0815:
0816: // Indicate which pass we are on (if interlace)
0817: Pass = 0;
0818:
0819: // The initial code size
0820: if (BitsPerPixel <= 1)
0821: InitCodeSize = 2;
0822: else
0823: InitCodeSize = BitsPerPixel;
0824:
0825: // Set up the current x and y position
0826: curx = 0;
0827: cury = 0;
0828:
0829: // Write the Magic header
0830: writeString(outs, "GIF89a");
0831:
0832: // Write out the screen width and height
0833: Putword(Width, outs);
0834: Putword(Height, outs);
0835:
0836: // Indicate that there is a global colour map
0837: B = (byte) 0x80; // Yes, there is a color map
0838: // OR in the resolution
0839: B |= (byte) ((8 - 1) << 4);
0840: // Not sorted
0841: // OR in the Bits per Pixel
0842: B |= (byte) ((BitsPerPixel - 1));
0843:
0844: // Write it out
0845: Putbyte(B, outs);
0846:
0847: // Write out the Background colour
0848: Putbyte(Background, outs);
0849:
0850: // Pixel aspect ratio - 1:1.
0851: //Putbyte( (byte) 49, outs );
0852: // Java's GIF reader currently has a bug, if the aspect ratio byte is
0853: // not zero it throws an ImageFormatException. It doesn't know that
0854: // 49 means a 1:1 aspect ratio. Well, whatever, zero works with all
0855: // the other decoders I've tried so it probably doesn't hurt.
0856: Putbyte((byte) 0, outs);
0857:
0858: // Write out the Global Colour Map
0859: for (i = 0; i < ColorMapSize; ++i) {
0860: Putbyte(Red[i], outs);
0861: Putbyte(Green[i], outs);
0862: Putbyte(Blue[i], outs);
0863: }
0864:
0865: // Write out extension for transparent colour index, if necessary.
0866: if (Transparent != -1) {
0867: Putbyte((byte) '!', outs);
0868: Putbyte((byte) 0xf9, outs);
0869: Putbyte((byte) 4, outs);
0870: Putbyte((byte) 1, outs);
0871: Putbyte((byte) 0, outs);
0872: Putbyte((byte) 0, outs);
0873: Putbyte((byte) Transparent, outs);
0874: Putbyte((byte) 0, outs);
0875: }
0876:
0877: // Write an Image separator
0878: Putbyte((byte) ',', outs);
0879:
0880: // Write the Image header
0881: Putword(LeftOfs, outs);
0882: Putword(TopOfs, outs);
0883: Putword(Width, outs);
0884: Putword(Height, outs);
0885:
0886: // Write out whether or not the image is interlaced
0887: if (Interlace)
0888: Putbyte((byte) 0x40, outs);
0889: else
0890: Putbyte((byte) 0x00, outs);
0891:
0892: // Write out the initial code size
0893: Putbyte((byte) InitCodeSize, outs);
0894:
0895: // Go and actually compress the data
0896: compress(InitCodeSize + 1, outs);
0897:
0898: // Write out a Zero-length packet (to end the series)
0899: Putbyte((byte) 0, outs);
0900:
0901: // Write the GIF file terminator
0902: Putbyte((byte) ';', outs);
0903: }
0904:
0905: // Return the next pixel from the image
0906: int GIFNextPixel() throws IOException {
0907: byte r;
0908:
0909: if (CountDown == 0)
0910: return EOF;
0911:
0912: --CountDown;
0913:
0914: r = GetPixel(curx, cury);
0915:
0916: BumpPixel();
0917:
0918: return r & 0xff;
0919: }
0920:
0921: public void imageComplete(int status) {
0922: producer.removeConsumer(this );
0923: if (status == ImageConsumer.IMAGEABORTED)
0924: iox = new IOException("image aborted");
0925: else {
0926: try {
0927: encodeFinish();
0928: encodeDone();
0929: } catch (IOException e) {
0930: iox = e;
0931: }
0932: }
0933: stop();
0934: }
0935:
0936: final int MAXCODE(int n_bits) {
0937: return (1 << n_bits) - 1;
0938: }
0939:
0940: void output(int code, OutputStream outs) throws IOException {
0941: cur_accum &= masks[cur_bits];
0942:
0943: if (cur_bits > 0)
0944: cur_accum |= (code << cur_bits);
0945: else
0946: cur_accum = code;
0947:
0948: cur_bits += n_bits;
0949:
0950: while (cur_bits >= 8) {
0951: char_out((byte) (cur_accum & 0xff), outs);
0952: cur_accum >>= 8;
0953: cur_bits -= 8;
0954: }
0955:
0956: // If the next entry is going to be too big for the code size,
0957: // then increase it, if possible.
0958: if (free_ent > maxcode || clear_flg) {
0959: if (clear_flg) {
0960: maxcode = MAXCODE(n_bits = g_init_bits);
0961: clear_flg = false;
0962: } else {
0963: ++n_bits;
0964: if (n_bits == maxbits)
0965: maxcode = maxmaxcode;
0966: else
0967: maxcode = MAXCODE(n_bits);
0968: }
0969: }
0970:
0971: if (code == EOFCode) {
0972: // At EOF, write the rest of the buffer.
0973: while (cur_bits > 0) {
0974: char_out((byte) (cur_accum & 0xff), outs);
0975: cur_accum >>= 8;
0976: cur_bits -= 8;
0977: }
0978:
0979: flush_char(outs);
0980: }
0981: }
0982:
0983: // Write out a byte to the GIF file
0984: void Putbyte(byte b, OutputStream outs) throws IOException {
0985: outs.write(b);
0986: }
0987:
0988: // Write out a word to the GIF file
0989: void Putword(int w, OutputStream outs) throws IOException {
0990: Putbyte((byte) (w & 0xff), outs);
0991: Putbyte((byte) ((w >> 8) & 0xff), outs);
0992: }
0993:
0994: public void setColorModel(ColorModel model) {
0995: // Ignore.
0996: }
0997:
0998: // Methods from ImageConsumer.
0999:
1000: public void setDimensions(int width, int height) {
1001: this .width = width;
1002: this .height = height;
1003: }
1004:
1005: public void setHints(int hintflags) {
1006: this .hintflags = hintflags;
1007: }
1008:
1009: public void setPixels(int x, int y, int w, int h, ColorModel model,
1010: byte[] pixels, int off, int scansize) {
1011: int[] rgbPixels = new int[w];
1012: for (int row = 0; row < h; ++row) {
1013: int rowOff = off + row * scansize;
1014: for (int col = 0; col < w; ++col)
1015: rgbPixels[col] = model
1016: .getRGB(pixels[rowOff + col] & 0xff);
1017: try {
1018: encodePixelsWrapper(x, y + row, w, 1, rgbPixels, 0, w);
1019: } catch (IOException e) {
1020: iox = e;
1021: stop();
1022: return;
1023: }
1024: }
1025: }
1026:
1027: public void setPixels(int x, int y, int w, int h, ColorModel model,
1028: int[] pixels, int off, int scansize) {
1029: if (model == rgbModel) {
1030: try {
1031: encodePixelsWrapper(x, y, w, h, pixels, off, scansize);
1032: } catch (IOException e) {
1033: iox = e;
1034: stop();
1035: return;
1036: }
1037: } else {
1038: int[] rgbPixels = new int[w];
1039: for (int row = 0; row < h; ++row) {
1040: int rowOff = off + row * scansize;
1041: for (int col = 0; col < w; ++col)
1042: rgbPixels[col] = model.getRGB(pixels[rowOff + col]);
1043: try {
1044: encodePixelsWrapper(x, y + row, w, 1, rgbPixels, 0,
1045: w);
1046: } catch (IOException e) {
1047: iox = e;
1048: stop();
1049: return;
1050: }
1051: }
1052: }
1053: }
1054:
1055: public void setProperties(Hashtable props) {
1056: this .props = props;
1057: }
1058:
1059: private synchronized void stop() {
1060: encoding = false;
1061: notifyAll();
1062: }
1063:
1064: static void writeString(OutputStream out, String str)
1065: throws IOException {
1066: byte[] buf = str.getBytes();
1067: out.write(buf);
1068: }
1069: }
|