/******************************************************************************* * This file is part of logisim-evolution. * * logisim-evolution is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * logisim-evolution 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 General Public License for more details. * * You should have received a copy of the GNU General Public License * along with logisim-evolution. If not, see <http://www.gnu.org/licenses/>. * * Original code by Carl Burch (http://www.cburch.com), 2011. * Subsequent modifications by : * + Haute École Spécialisée Bernoise * http://www.bfh.ch * + Haute École du paysage, d'ingénierie et d'architecture de Genève * http://hepia.hesge.ch/ * + Haute École d'Ingénierie et de Gestion du Canton de Vaud * http://www.heig-vd.ch/ * The project is currently maintained by : * + REDS Institute - HEIG-VD * Yverdon-les-Bains, Switzerland * http://reds.heig-vd.ch *******************************************************************************/ /* * @(#)GIFEncoder.java 0.90 4/21/96 Adam Doppelt */ package com.cburch.logisim.util; import java.awt.AWTException; import java.awt.Image; import java.awt.image.ColorModel; import java.awt.image.PixelGrabber; import java.io.File; import java.io.FileOutputStream; import java.io.IOException; import java.io.OutputStream; import javax.swing.ProgressMonitor; /** * GIFEncoder is a class which takes an image and saves it to a stream using the * GIF file format (<A HREF="http://www.dcs.ed.ac.uk/%7Emxr/gfx/">Graphics * Interchange Format</A>). A GIFEncoder is constructed with either an AWT Image * (which must be fully loaded) or a set of RGB arrays. The image can be written * out with a call to <CODE>Write</CODE>. * <P> * * Three caveats: * <UL> * <LI>GIFEncoder will convert the image to indexed color upon construction. * This will take some time, depending on the size of the image. Also, actually * writing the image out (Write) will take time. * <P> * * <LI>The image cannot have more than 256 colors, since GIF is an 8 bit format. * For a 24 bit to 8 bit quantization algorithm, see Graphics Gems II III.2 by * Xialoin Wu. Or check out his <A * HREF="http://www.csd.uwo.ca/faculty/wu/cq.c">C source</A>. * <P> * * <LI>Since the image must be completely loaded into memory, GIFEncoder may * have problems with large images. Attempting to encode an image which will not * fit into memory will probably result in the following exception: * <P> * <CODE>java.awt.AWTException: Grabber returned false: 192</CODE> * <P> * </UL> * <P> * * GIFEncoder is based upon gifsave.c, which was written and released by: * <P> * <CENTER> Sverre H. Huseby<BR> * Bjoelsengt. 17<BR> * N-0468 Oslo<BR> * Norway * <P> * * Phone: +47 2 230539<BR> * sverrehu@ifi.uio.no * <P> * </CENTER> * * @version 0.90 21 Apr 1996 * @author <A HREF="http://www.cs.brown.edu/people/amd/">Adam Doppelt</A> */ public class GifEncoder { private static class BitFile { OutputStream output_; byte buffer_[]; int index_, bitsLeft_; BitFile(OutputStream output) { output_ = output; buffer_ = new byte[256]; index_ = 0; bitsLeft_ = 8; } void Flush() throws IOException { int numBytes = index_ + (bitsLeft_ == 8 ? 0 : 1); if (numBytes > 0) { output_.write(numBytes); output_.write(buffer_, 0, numBytes); buffer_[0] = 0; index_ = 0; bitsLeft_ = 8; } } void WriteBits(int bits, int numbits) throws IOException { // int bitsWritten = 0; int numBytes = 255; do { if ((index_ == 254 && bitsLeft_ == 0) || index_ > 254) { output_.write(numBytes); output_.write(buffer_, 0, numBytes); buffer_[0] = 0; index_ = 0; bitsLeft_ = 8; } if (numbits <= bitsLeft_) { buffer_[index_] |= (bits & ((1 << numbits) - 1)) << (8 - bitsLeft_); // bitsWritten += numbits; bitsLeft_ -= numbits; numbits = 0; } else { buffer_[index_] |= (bits & ((1 << bitsLeft_) - 1)) << (8 - bitsLeft_); // bitsWritten += bitsLeft_; bits >>= bitsLeft_; numbits -= bitsLeft_; buffer_[++index_] = 0; bitsLeft_ = 8; } } while (numbits != 0); } } private static class BitUtils { static byte BitsNeeded(int n) { byte ret = 1; if (n-- == 0) return 0; while ((n >>= 1) != 0) ++ret; return ret; } static void WriteString(OutputStream output, String string) throws IOException { for (int loop = 0; loop < string.length(); ++loop) output.write((byte) (string.charAt(loop))); } static void WriteWord(OutputStream output, short w) throws IOException { output.write(w & 0xFF); output.write((w >> 8) & 0xFF); } } private static class ImageDescriptor { byte separator_; short leftPosition_, topPosition_, width_, height_; private byte byte_; ImageDescriptor(short width, short height, char separator) { separator_ = (byte) separator; leftPosition_ = 0; topPosition_ = 0; width_ = width; height_ = height; SetLocalColorTableSize((byte) 0); SetReserved((byte) 0); SetSortFlag((byte) 0); SetInterlaceFlag((byte) 0); SetLocalColorTableFlag((byte) 0); } void SetInterlaceFlag(byte num) { byte_ |= (num & 1) << 6; } void SetLocalColorTableFlag(byte num) { byte_ |= (num & 1) << 7; } void SetLocalColorTableSize(byte num) { byte_ |= (num & 7); } void SetReserved(byte num) { byte_ |= (num & 3) << 3; } void SetSortFlag(byte num) { byte_ |= (num & 1) << 5; } void Write(OutputStream output) throws IOException { output.write(separator_); BitUtils.WriteWord(output, leftPosition_); BitUtils.WriteWord(output, topPosition_); BitUtils.WriteWord(output, width_); BitUtils.WriteWord(output, height_); output.write(byte_); } } private static class LZWCompressor { static void LZWCompress(OutputStream output, int codesize, byte toCompress[]) throws IOException { byte c; short index; int clearcode, endofinfo, numbits, limit; short prefix = (short) 0xFFFF; BitFile bitFile = new BitFile(output); 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(); } } private static class LZWStringTable { static int Hash(short index, byte lastbyte) { return ((int) ((short) (lastbyte << 8) ^ index) & 0xFFFF) % HASHSIZE; } 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; byte strChr_[]; short strNxt_[]; short strHsh_[]; short numStrings_; LZWStringTable() { strChr_ = new byte[MAXSTR]; strNxt_ = new short[MAXSTR]; strHsh_ = new short[HASHSIZE]; } int AddCharString(short index, byte b) { int hshidx; if (numStrings_ >= MAXSTR) return 0xFFFF; hshidx = Hash(index, b); while (strHsh_[hshidx] != HASH_FREE) hshidx = (hshidx + HASHSTEP) % HASHSIZE; strHsh_[hshidx] = numStrings_; strChr_[numStrings_] = b; strNxt_[numStrings_] = (index != HASH_FREE) ? index : NEXT_FIRST; return numStrings_++; } void ClearTable(int codesize) { numStrings_ = 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++) AddCharString((short) 0xFFFF, (byte) q); } 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; } } private static class MyGrabber extends PixelGrabber { ProgressMonitor monitor; int progress; int goal; MyGrabber(ProgressMonitor monitor, Image image, int x, int y, int width, int height, int[] values, int start, int scan) { super(image, x, y, width, height, values, start, scan); this.monitor = monitor; progress = 0; goal = width * height; monitor.setMinimum(0); monitor.setMaximum(goal * 21 / 20); } @Override public void setPixels(int srcX, int srcY, int srcW, int srcH, ColorModel model, int[] pixels, int srcOff, int srcScan) { progress += srcW * srcH; monitor.setProgress(progress); if (monitor.isCanceled()) { abortGrabbing(); } else { super.setPixels(srcX, srcY, srcW, srcH, model, pixels, srcOff, srcScan); } } } private static class ScreenDescriptor { short localScreenWidth_, localScreenHeight_; private byte byte_; byte backgroundColorIndex_, pixelAspectRatio_; ScreenDescriptor(short width, short height, int numColors) { localScreenWidth_ = width; localScreenHeight_ = height; SetGlobalColorTableSize((byte) (BitUtils.BitsNeeded(numColors) - 1)); SetGlobalColorTableFlag((byte) 1); SetSortFlag((byte) 0); SetColorResolution((byte) 7); backgroundColorIndex_ = 0; pixelAspectRatio_ = 0; } void SetColorResolution(byte num) { byte_ |= (num & 7) << 4; } void SetGlobalColorTableFlag(byte num) { byte_ |= (num & 1) << 7; } void SetGlobalColorTableSize(byte num) { byte_ |= (num & 7); } void SetSortFlag(byte num) { byte_ |= (num & 1) << 3; } void Write(OutputStream output) throws IOException { BitUtils.WriteWord(output, localScreenWidth_); BitUtils.WriteWord(output, localScreenHeight_); output.write(byte_); output.write(backgroundColorIndex_); output.write(pixelAspectRatio_); } } public static void toFile(Image img, File file) throws IOException, AWTException { toFile(img, file, null); } public static void toFile(Image img, File file, ProgressMonitor monitor) throws IOException, AWTException { FileOutputStream out = new FileOutputStream(file); new GifEncoder(img, monitor).write(out); out.close(); } public static void toFile(Image img, String filename) throws IOException, AWTException { toFile(img, filename, null); } public static void toFile(Image img, String filename, ProgressMonitor monitor) throws IOException, AWTException { FileOutputStream out = new FileOutputStream(filename); new GifEncoder(img, monitor).write(out); out.close(); } private short width_, height_; private int numColors_; private byte pixels_[], colors_[]; /** * Construct a GifEncoder. The constructor will convert the image to an * indexed color array. <B>This may take some time.</B> * <P> * * Each array stores intensity values for the image. In other words, r[x][y] * refers to the red intensity of the pixel at column x, row y. * <P> * * @param r * An array containing the red intensity values. * @param g * An array containing the green intensity values. * @param b * An array containing the blue intensity values. * * @exception AWTException * Will be thrown if the image contains more than 256 colors. * */ public GifEncoder(byte r[][], byte g[][], byte b[][]) throws AWTException { width_ = (short) (r.length); height_ = (short) (r[0].length); ToIndexedColor(r, g, b); } /** * Construct a GIFEncoder. The constructor will convert the image to an * indexed color array. <B>This may take some time.</B> * <P> * * @param image * The image to encode. The image <B>must</B> be completely * loaded. * @exception AWTException * Will be thrown if the pixel grab fails. This can happen if * Java runs out of memory. It may also indicate that the * image contains more than 256 colors. * */ public GifEncoder(Image image, ProgressMonitor monitor) throws AWTException { width_ = (short) image.getWidth(null); height_ = (short) image.getHeight(null); int values[] = new int[width_ * height_]; PixelGrabber grabber; if (monitor != null) { grabber = new MyGrabber(monitor, image, 0, 0, width_, height_, values, 0, width_); } else { grabber = new PixelGrabber(image, 0, 0, width_, height_, values, 0, width_); } try { if (grabber.grabPixels() != true) throw new AWTException(Strings.get("grabberError") + ": " + grabber.status()); } catch (InterruptedException e) { ; } byte r[][] = new byte[width_][height_]; byte g[][] = new byte[width_][height_]; byte b[][] = new byte[width_][height_]; int index = 0; for (int y = 0; y < height_; ++y) for (int x = 0; x < width_; ++x) { r[x][y] = (byte) ((values[index] >> 16) & 0xFF); g[x][y] = (byte) ((values[index] >> 8) & 0xFF); b[x][y] = (byte) ((values[index]) & 0xFF); ++index; } ToIndexedColor(r, g, b); } void ToIndexedColor(byte r[][], byte g[][], byte b[][]) throws AWTException { pixels_ = new byte[width_ * height_]; colors_ = new byte[256 * 3]; int colornum = 0; for (int x = 0; x < width_; ++x) { for (int y = 0; y < height_; ++y) { int search; for (search = 0; search < colornum; ++search) if (colors_[search * 3] == r[x][y] && colors_[search * 3 + 1] == g[x][y] && colors_[search * 3 + 2] == b[x][y]) break; if (search > 255) throw new AWTException(Strings.get("manyColorError")); pixels_[y * width_ + x] = (byte) search; if (search == colornum) { colors_[search * 3] = r[x][y]; colors_[search * 3 + 1] = g[x][y]; colors_[search * 3 + 2] = b[x][y]; ++colornum; } } } numColors_ = 1 << BitUtils.BitsNeeded(colornum); byte copy[] = new byte[numColors_ * 3]; System.arraycopy(colors_, 0, copy, 0, numColors_ * 3); colors_ = copy; } /** * Writes the image out to a stream in the GIF file format. This will be a * single GIF87a image, non-interlaced, with no background color. <B>This * may take some time.</B> * <P> * * @param output * The stream to output to. This should probably be a buffered * stream. * * @exception IOException * Will be thrown if a write operation fails. * */ public void write(OutputStream output) throws IOException { BitUtils.WriteString(output, "GIF87a"); ScreenDescriptor sd = new ScreenDescriptor(width_, height_, numColors_); sd.Write(output); output.write(colors_, 0, colors_.length); ImageDescriptor id = new ImageDescriptor(width_, height_, ','); id.Write(output); byte codesize = BitUtils.BitsNeeded(numColors_); if (codesize == 1) ++codesize; output.write(codesize); LZWCompressor.LZWCompress(output, codesize, pixels_); output.write(0); id = new ImageDescriptor((byte) 0, (byte) 0, ';'); id.Write(output); output.flush(); } }