Java tutorial
/* * (C) 2004 - Geotechnical Software Services * * This code is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This code is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this program; if not, write to the Free * Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, * MA 02111-1307, USA. */ package no.geosoft.cc.io; import java.io.*; import java.awt.*; import java.awt.image.*; /** * Class for converting images to GIF files. * * <p> * Contribution: * <ul> * <li>Sverre H. Huseby (gifsave.c on which this is based)</li> * <li>Adam Doppelt (Initial Java port)</li> * <li>Greg Faron (Initial java port)</li> * </ul> * * @author <a href="mailto:jacob.dreyer@geosoft.no">Jacob Dreyer</a> */ public class GifEncoder { private short imageWidth_, imageHeight_; private int nColors_; private byte[] pixels_ = null; private byte[] colors_ = null; /** * Constructing a GIF encoder. * * @param image The image to encode. The image must be * completely loaded. * @throws AWTException If memory is exhausted or image contains * more than 256 colors. */ public GifEncoder(Image image) throws AWTException { imageWidth_ = (short) image.getWidth(null); imageHeight_ = (short) image.getHeight(null); int values[] = new int[imageWidth_ * imageHeight_]; PixelGrabber grabber = new PixelGrabber(image, 0, 0, imageWidth_, imageHeight_, values, 0, imageWidth_); try { if (grabber.grabPixels() != true) throw new AWTException("Grabber returned false: " + grabber.status()); } catch (InterruptedException exception) { } byte[][] r = new byte[imageWidth_][imageHeight_]; byte[][] g = new byte[imageWidth_][imageHeight_]; byte[][] b = new byte[imageWidth_][imageHeight_]; int index = 0; for (int y = 0; y < imageHeight_; y++) { for (int x = 0; x < imageWidth_; x++, index++) { r[x][y] = (byte) ((values[index] >> 16) & 0xFF); g[x][y] = (byte) ((values[index] >> 8) & 0xFF); b[x][y] = (byte) ((values[index] >> 0) & 0xFF); } } toIndexColor(r, g, b); } /** * Create a GIF encoder. r[i][j] refers to the pixel at * column i, row j. * * @param r Red intensity values. * @param g Green intensity values. * @param b Blue intensity values. * @throws AWTException If memory is exhausted or image contains * more than 256 colors. */ public GifEncoder(byte[][] r, byte[][] g, byte[][] b) throws AWTException { imageWidth_ = (short) (r.length); imageHeight_ = (short) (r[0].length); toIndexColor(r, g, b); } /** * Write image to GIF file. * * @param image Image to write. * @param file File to erite to. */ public static void writeFile(Image image, File file) throws AWTException, IOException { GifEncoder gifEncoder = new GifEncoder(image); gifEncoder.write(new FileOutputStream(file)); } /** * Write AWT/Swing component to GIF file. * * @param image Image to write. * @param file File to erite to. */ public static void writeFile(Component component, File file) throws AWTException, IOException { Image image = component.createImage(component.getWidth(), component.getHeight()); Graphics graphics = image.getGraphics(); component.printAll(graphics); GifEncoder.writeFile(image, file); } /** * Writes the image out to a stream in GIF format. * This will be a single GIF87a image, non-interlaced, with no * background color. * * @param stream The stream to which to output. * @throws IOException Thrown if a write operation fails. */ public void write(OutputStream stream) throws IOException { writeString(stream, "GIF87a"); writeScreenDescriptor(stream); stream.write(colors_, 0, colors_.length); writeImageDescriptor(stream, imageWidth_, imageHeight_, ','); byte codeSize = bitsNeeded(nColors_); if (codeSize == 1) codeSize++; stream.write(codeSize); writeLzwCompressed(stream, codeSize, pixels_); stream.write(0); writeImageDescriptor(stream, (short) 0, (short) 0, ';'); stream.flush(); stream.close(); } /** * Converts rgb desrcription of image to colour * number description used by GIF. * * @param r Red array of pixels. * @param g Green array of pixels. * @param b Blue array of pixels. * @throws AWTException * Thrown if too many different colours in image. */ private void toIndexColor(byte[][] r, byte[][] g, byte[][] b) throws AWTException { pixels_ = new byte[imageWidth_ * imageHeight_]; colors_ = new byte[256 * 3]; int colornum = 0; for (int x = 0; x < imageWidth_; x++) { for (int y = 0; y < imageHeight_; y++) { int search; for (search = 0; search < colornum; search++) { if (colors_[search * 3 + 0] == r[x][y] && colors_[search * 3 + 1] == g[x][y] && colors_[search * 3 + 2] == b[x][y]) { break; } } if (search > 255) throw new AWTException("Too many colors."); // Row major order y=row x=col pixels_[y * imageWidth_ + x] = (byte) search; if (search == colornum) { colors_[search * 3 + 0] = r[x][y]; // [col][row] colors_[search * 3 + 1] = g[x][y]; colors_[search * 3 + 2] = b[x][y]; colornum++; } } } nColors_ = 1 << bitsNeeded(colornum); byte copy[] = new byte[nColors_ * 3]; System.arraycopy(colors_, 0, copy, 0, nColors_ * 3); colors_ = copy; } private byte bitsNeeded(int n) { if (n-- == 0) return 0; byte nBitsNeeded = 1; while ((n >>= 1) != 0) nBitsNeeded++; return nBitsNeeded; } private void writeWord(OutputStream stream, short w) throws IOException { stream.write(w & 0xFF); stream.write((w >> 8) & 0xFF); } private void writeString(OutputStream stream, String string) throws IOException { for (int i = 0; i < string.length(); i++) stream.write((byte) (string.charAt(i))); } private void writeScreenDescriptor(OutputStream stream) throws IOException { writeWord(stream, imageWidth_); writeWord(stream, imageHeight_); byte flag = 0; // Global color table size byte globalColorTableSize = (byte) (bitsNeeded(nColors_) - 1); flag |= globalColorTableSize & 7; // Global color table flag byte globalColorTableFlag = 1; flag |= (globalColorTableFlag & 1) << 7; // Sort flag byte sortFlag = 0; flag |= (sortFlag & 1) << 3; // Color resolution byte colorResolution = 7; flag |= (colorResolution & 7) << 4; byte backgroundColorIndex = 0; byte pixelAspectRatio = 0; stream.write(flag); stream.write(backgroundColorIndex); stream.write(pixelAspectRatio); } private void writeImageDescriptor(OutputStream stream, short width, short height, char separator) throws IOException { stream.write(separator); short leftPosition = 0; short topPosition = 0; writeWord(stream, leftPosition); writeWord(stream, topPosition); writeWord(stream, width); writeWord(stream, height); byte flag = 0; // Local color table size byte localColorTableSize = 0; flag |= (localColorTableSize & 7); // Reserved byte reserved = 0; flag |= (reserved & 3) << 3; // Sort flag byte sortFlag = 0; flag |= (sortFlag & 1) << 5; // Interlace flag byte interlaceFlag = 0; flag |= (interlaceFlag & 1) << 6; // Local color table flag byte localColorTableFlag = 0; flag |= (localColorTableFlag & 1) << 7; stream.write(flag); } private void writeLzwCompressed(OutputStream stream, int codeSize, byte toCompress[]) throws IOException { byte c; short index; int clearcode, endofinfo, numbits, limit, errcode; short prefix = (short) 0xFFFF; BitFile bitFile = new BitFile(stream); LzwStringTable strings = new LzwStringTable(); clearcode = 1 << codeSize; endofinfo = clearcode + 1; numbits = codeSize + 1; limit = (1 << numbits) - 1; strings.clearTable(codeSize); bitFile.writeBits(clearcode, numbits); for (int loop = 0; loop < toCompress.length; loop++) { c = toCompress[loop]; if ((index = strings.findCharString(prefix, c)) != -1) prefix = index; else { bitFile.writeBits(prefix, numbits); if (strings.addCharString(prefix, c) > limit) { if (++numbits > 12) { bitFile.writeBits(clearcode, numbits - 1); strings.clearTable(codeSize); numbits = codeSize + 1; } limit = (1 << numbits) - 1; } prefix = (short) ((short) c & 0xFF); } } if (prefix != -1) bitFile.writeBits(prefix, numbits); bitFile.writeBits(endofinfo, numbits); bitFile.flush(); } /** * Used to compress the image by looking for repeating * elements. */ private class LzwStringTable { private final static int RES_CODES = 2; private final static short HASH_FREE = (short) 0xFFFF; private final static short NEXT_FIRST = (short) 0xFFFF; private final static int MAXBITS = 12; private final static int MAXSTR = (1 << MAXBITS); private final static short HASHSIZE = 9973; private final static short HASHSTEP = 2039; private byte strChr_[]; private short strNxt_[]; private short strHsh_[]; private short nStrings_; LzwStringTable() { strChr_ = new byte[MAXSTR]; strNxt_ = new short[MAXSTR]; strHsh_ = new short[HASHSIZE]; } int addCharString(short index, byte b) { int hshidx; if (nStrings_ >= MAXSTR) return 0xFFFF; hshidx = hash(index, b); while (strHsh_[hshidx] != HASH_FREE) hshidx = (hshidx + HASHSTEP) % HASHSIZE; strHsh_[hshidx] = nStrings_; strChr_[nStrings_] = b; strNxt_[nStrings_] = (index != HASH_FREE) ? index : NEXT_FIRST; return nStrings_++; } short findCharString(short index, byte b) { int hshidx, nxtidx; if (index == HASH_FREE) return b; hshidx = hash(index, b); while ((nxtidx = strHsh_[hshidx]) != HASH_FREE) { if (strNxt_[nxtidx] == index && strChr_[nxtidx] == b) return (short) nxtidx; hshidx = (hshidx + HASHSTEP) % HASHSIZE; } return (short) 0xFFFF; } void clearTable(int codesize) { nStrings_ = 0; for (int q = 0; q < HASHSIZE; q++) strHsh_[q] = HASH_FREE; int w = (1 << codesize) + RES_CODES; for (int q = 0; q < w; q++) this.addCharString((short) 0xFFFF, (byte) q); } int hash(short index, byte lastbyte) { return ((int) ((short) (lastbyte << 8) ^ index) & 0xFFFF) % HASHSIZE; } } private class BitFile { private OutputStream stream_ = null; private byte[] buffer_; private int streamIndex_, bitsLeft_; BitFile(OutputStream stream) { stream_ = stream; buffer_ = new byte[256]; streamIndex_ = 0; bitsLeft_ = 8; } void flush() throws IOException { int nBytes = streamIndex_ + ((bitsLeft_ == 8) ? 0 : 1); if (nBytes > 0) { stream_.write(nBytes); stream_.write(buffer_, 0, nBytes); buffer_[0] = 0; streamIndex_ = 0; bitsLeft_ = 8; } } void writeBits(int bits, int nBits) throws IOException { int nBitsWritten = 0; int nBytes = 255; do { if ((streamIndex_ == 254 && bitsLeft_ == 0) || streamIndex_ > 254) { stream_.write(nBytes); stream_.write(buffer_, 0, nBytes); buffer_[0] = 0; streamIndex_ = 0; bitsLeft_ = 8; } if (nBits <= bitsLeft_) { buffer_[streamIndex_] |= (bits & ((1 << nBits) - 1)) << (8 - bitsLeft_); nBitsWritten += nBits; bitsLeft_ -= nBits; nBits = 0; } else { buffer_[streamIndex_] |= (bits & ((1 << bitsLeft_) - 1)) << (8 - bitsLeft_); nBitsWritten += bitsLeft_; bits >>= bitsLeft_; nBits -= bitsLeft_; buffer_[++streamIndex_] = 0; bitsLeft_ = 8; } } while (nBits != 0); } } }