/******************************************************************************* * Copyright (c) 2001-2005 Sasa Markovic and Ciaran Treanor. * Copyright (c) 2011 The OpenNMS Group, Inc. * * This library 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 library 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 library; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA *******************************************************************************/ /////////////////////////////////////////////////////////////////// // GifEncoder from J.M.G. Elliott // http://jmge.net/java/gifenc/ /////////////////////////////////////////////////////////////////// package org.jrobin.graph; import java.awt.*; import java.awt.image.PixelGrabber; import java.io.IOException; import java.io.OutputStream; import java.util.Vector; class GifEncoder { private Dimension dispDim = new Dimension(0, 0); private GifColorTable colorTable; private int bgIndex = 0; private int loopCount = 1; private String theComments; private Vector<Gif89Frame> vFrames = new Vector<Gif89Frame>(); GifEncoder() { colorTable = new GifColorTable(); } GifEncoder(Image static_image) throws IOException { this(); addFrame(static_image); } GifEncoder(Color[] colors) { colorTable = new GifColorTable(colors); } GifEncoder(Color[] colors, int width, int height, byte ci_pixels[]) throws IOException { this(colors); addFrame(width, height, ci_pixels); } int getFrameCount() { return vFrames.size(); } Gif89Frame getFrameAt(int index) { return isOk(index) ? vFrames.elementAt(index) : null; } void addFrame(Gif89Frame gf) throws IOException { accommodateFrame(gf); vFrames.addElement(gf); } void addFrame(Image image) throws IOException { addFrame(new DirectGif89Frame(image)); } void addFrame(int width, int height, byte ci_pixels[]) throws IOException { addFrame(new IndexGif89Frame(width, height, ci_pixels)); } void insertFrame(int index, Gif89Frame gf) throws IOException { accommodateFrame(gf); vFrames.insertElementAt(gf, index); } void setTransparentIndex(int index) { colorTable.setTransparent(index); } void setLogicalDisplay(Dimension dim, int background) { dispDim = new Dimension(dim); bgIndex = background; } void setLoopCount(int count) { loopCount = count; } void setComments(String comments) { theComments = comments; } void setUniformDelay(int interval) { for (int i = 0; i < vFrames.size(); ++i) { vFrames.elementAt(i).setDelay(interval); } } void encode(OutputStream out) throws IOException { int nframes = getFrameCount(); boolean is_sequence = nframes > 1; colorTable.closePixelProcessing(); Put.ascii("GIF89a", out); writeLogicalScreenDescriptor(out); colorTable.encode(out); if (is_sequence && loopCount != 1) { writeNetscapeExtension(out); } if (theComments != null && theComments.length() > 0) { writeCommentExtension(out); } for (int i = 0; i < nframes; ++i) { vFrames.elementAt(i).encode( out, is_sequence, colorTable.getDepth(), colorTable.getTransparent() ); } out.write((int) ';'); out.flush(); } private void accommodateFrame(Gif89Frame gf) throws IOException { dispDim.width = Math.max(dispDim.width, gf.getWidth()); dispDim.height = Math.max(dispDim.height, gf.getHeight()); colorTable.processPixels(gf); } private void writeLogicalScreenDescriptor(OutputStream os) throws IOException { Put.leShort(dispDim.width, os); Put.leShort(dispDim.height, os); os.write(0xf0 | colorTable.getDepth() - 1); os.write(bgIndex); os.write(0); } private void writeNetscapeExtension(OutputStream os) throws IOException { os.write((int) '!'); os.write(0xff); os.write(11); Put.ascii("NETSCAPE2.0", os); os.write(3); os.write(1); Put.leShort(loopCount > 1 ? loopCount - 1 : 0, os); os.write(0); } private void writeCommentExtension(OutputStream os) throws IOException { os.write((int) '!'); os.write(0xfe); int remainder = theComments.length() % 255; int nsubblocks_full = theComments.length() / 255; int nsubblocks = nsubblocks_full + (remainder > 0 ? 1 : 0); int ibyte = 0; for (int isb = 0; isb < nsubblocks; ++isb) { int size = isb < nsubblocks_full ? 255 : remainder; os.write(size); Put.ascii(theComments.substring(ibyte, ibyte + size), os); ibyte += size; } os.write(0); } private boolean isOk(int frame_index) { return frame_index >= 0 && frame_index < vFrames.size(); } } class DirectGif89Frame extends Gif89Frame { private int[] argbPixels; DirectGif89Frame(Image img) throws IOException { PixelGrabber pg = new PixelGrabber(img, 0, 0, -1, -1, true); String errmsg = null; try { if (!pg.grabPixels()) { errmsg = "can't grab pixels from image"; } } catch (InterruptedException e) { errmsg = "interrupted grabbing pixels from image"; } if (errmsg != null) { throw new IOException(errmsg + " (" + getClass().getName() + ")"); } theWidth = pg.getWidth(); theHeight = pg.getHeight(); argbPixels = (int[]) pg.getPixels(); ciPixels = new byte[argbPixels.length]; } DirectGif89Frame(int width, int height, int argb_pixels[]) { theWidth = width; theHeight = height; argbPixels = new int[theWidth * theHeight]; System.arraycopy(argb_pixels, 0, argbPixels, 0, argbPixels.length); ciPixels = new byte[argbPixels.length]; } Object getPixelSource() { return argbPixels; } } class GifColorTable { private int[] theColors = new int[256]; private int colorDepth; private int transparentIndex = -1; private int ciCount = 0; private ReverseColorMap ciLookup; GifColorTable() { ciLookup = new ReverseColorMap(); } GifColorTable(Color[] colors) { int n2copy = Math.min(theColors.length, colors.length); for (int i = 0; i < n2copy; ++i) { theColors[i] = colors[i].getRGB(); } } int getDepth() { return colorDepth; } int getTransparent() { return transparentIndex; } void setTransparent(int color_index) { transparentIndex = color_index; } void processPixels(Gif89Frame gf) throws IOException { if (gf instanceof DirectGif89Frame) { filterPixels((DirectGif89Frame) gf); } else { trackPixelUsage((IndexGif89Frame) gf); } } void closePixelProcessing() { colorDepth = computeColorDepth(ciCount); } void encode(OutputStream os) throws IOException { int palette_size = 1 << colorDepth; for (int i = 0; i < palette_size; ++i) { os.write(theColors[i] >> 16 & 0xff); os.write(theColors[i] >> 8 & 0xff); os.write(theColors[i] & 0xff); } } private void filterPixels(DirectGif89Frame dgf) throws IOException { if (ciLookup == null) { throw new IOException("RGB frames require palette autodetection"); } int[] argb_pixels = (int[]) dgf.getPixelSource(); byte[] ci_pixels = dgf.getPixelSink(); int npixels = argb_pixels.length; for (int i = 0; i < npixels; ++i) { int argb = argb_pixels[i]; if ((argb >>> 24) < 0x80) { if (transparentIndex == -1) { transparentIndex = ciCount; } else if (argb != theColors[transparentIndex]) { ci_pixels[i] = (byte) transparentIndex; continue; } } int color_index = ciLookup.getPaletteIndex(argb & 0xffffff); if (color_index == -1) { if (ciCount == 256) { throw new IOException("can't encode as GIF (> 256 colors)"); } theColors[ciCount] = argb; ciLookup.put(argb & 0xffffff, ciCount); ci_pixels[i] = (byte) ciCount; ++ciCount; } else { ci_pixels[i] = (byte) color_index; } } } private void trackPixelUsage(IndexGif89Frame igf) { byte[] ci_pixels = (byte[]) igf.getPixelSource(); int npixels = ci_pixels.length; for (int i = 0; i < npixels; ++i) { if (ci_pixels[i] >= ciCount) { ciCount = ci_pixels[i] + 1; } } } private int computeColorDepth(int colorcount) { if (colorcount <= 2) { return 1; } if (colorcount <= 4) { return 2; } if (colorcount <= 16) { return 4; } return 8; } } class ReverseColorMap { private static class ColorRecord { int rgb; int ipalette; ColorRecord(int rgb, int ipalette) { this.rgb = rgb; this.ipalette = ipalette; } } private static final int HCAPACITY = 2053; private ColorRecord[] hTable = new ColorRecord[HCAPACITY]; int getPaletteIndex(int rgb) { ColorRecord rec; for (int itable = rgb % hTable.length; (rec = hTable[itable]) != null && rec.rgb != rgb; itable = ++itable % hTable.length ) { ; } if (rec != null) { return rec.ipalette; } return -1; } void put(int rgb, int ipalette) { int itable; for (itable = rgb % hTable.length; hTable[itable] != null; itable = ++itable % hTable.length ) { ; } hTable[itable] = new ColorRecord(rgb, ipalette); } } abstract class Gif89Frame { static final int DM_UNDEFINED = 0; static final int DM_LEAVE = 1; static final int DM_BGCOLOR = 2; static final int DM_REVERT = 3; int theWidth = -1; int theHeight = -1; byte[] ciPixels; private Point thePosition = new Point(0, 0); private boolean isInterlaced; private int csecsDelay; private int disposalCode = DM_LEAVE; void setPosition(Point p) { thePosition = new Point(p); } void setInterlaced(boolean b) { isInterlaced = b; } void setDelay(int interval) { csecsDelay = interval; } void setDisposalMode(int code) { disposalCode = code; } Gif89Frame() { } abstract Object getPixelSource(); int getWidth() { return theWidth; } int getHeight() { return theHeight; } byte[] getPixelSink() { return ciPixels; } void encode(OutputStream os, boolean epluribus, int color_depth, int transparent_index) throws IOException { writeGraphicControlExtension(os, epluribus, transparent_index); writeImageDescriptor(os); new GifPixelsEncoder( theWidth, theHeight, ciPixels, isInterlaced, color_depth ).encode(os); } private void writeGraphicControlExtension(OutputStream os, boolean epluribus, int itransparent) throws IOException { int transflag = itransparent == -1 ? 0 : 1; if (transflag == 1 || epluribus) { os.write((int) '!'); os.write(0xf9); os.write(4); os.write((disposalCode << 2) | transflag); Put.leShort(csecsDelay, os); os.write(itransparent); os.write(0); } } private void writeImageDescriptor(OutputStream os) throws IOException { os.write((int) ','); Put.leShort(thePosition.x, os); Put.leShort(thePosition.y, os); Put.leShort(theWidth, os); Put.leShort(theHeight, os); os.write(isInterlaced ? 0x40 : 0); } } class GifPixelsEncoder { private static final int EOF = -1; private int imgW, imgH; private byte[] pixAry; private boolean wantInterlaced; private int initCodeSize; private int countDown; private int xCur, yCur; private int curPass; GifPixelsEncoder(int width, int height, byte[] pixels, boolean interlaced, int color_depth) { imgW = width; imgH = height; pixAry = pixels; wantInterlaced = interlaced; initCodeSize = Math.max(2, color_depth); } void encode(OutputStream os) throws IOException { os.write(initCodeSize); countDown = imgW * imgH; xCur = yCur = curPass = 0; compress(initCodeSize + 1, os); os.write(0); } private void bumpPosition() { ++xCur; if (xCur == imgW) { xCur = 0; if (!wantInterlaced) { ++yCur; } else { switch (curPass) { case 0: yCur += 8; if (yCur >= imgH) { ++curPass; yCur = 4; } break; case 1: yCur += 8; if (yCur >= imgH) { ++curPass; yCur = 2; } break; case 2: yCur += 4; if (yCur >= imgH) { ++curPass; yCur = 1; } break; case 3: yCur += 2; break; } } } } private int nextPixel() { if (countDown == 0) { return EOF; } --countDown; byte pix = pixAry[yCur * imgW + xCur]; bumpPosition(); return pix & 0xff; } static final int BITS = 12; static final int HSIZE = 5003; int n_bits; int maxbits = BITS; int maxcode; int maxmaxcode = 1 << BITS; final int MAXCODE(int n_bits) { return (1 << n_bits) - 1; } int[] htab = new int[HSIZE]; int[] codetab = new int[HSIZE]; int hsize = HSIZE; int free_ent = 0; boolean clear_flg = false; int g_init_bits; int ClearCode; int EOFCode; void compress(int init_bits, OutputStream outs) throws IOException { int fcode; int i /* = 0 */; int c; int ent; int disp; int hsize_reg; int hshift; g_init_bits = init_bits; clear_flg = false; n_bits = g_init_bits; maxcode = MAXCODE(n_bits); ClearCode = 1 << (init_bits - 1); EOFCode = ClearCode + 1; free_ent = ClearCode + 2; char_init(); ent = nextPixel(); hshift = 0; for (fcode = hsize; fcode < 65536; fcode *= 2) { ++hshift; } hshift = 8 - hshift; hsize_reg = hsize; cl_hash(hsize_reg); output(ClearCode, outs); outer_loop: while ((c = nextPixel()) != EOF) { fcode = (c << maxbits) + ent; i = (c << hshift) ^ ent; if (htab[i] == fcode) { ent = codetab[i]; continue; } else if (htab[i] >= 0) { disp = hsize_reg - i; if (i == 0) { disp = 1; } do { if ((i -= disp) < 0) { i += hsize_reg; } if (htab[i] == fcode) { ent = codetab[i]; continue outer_loop; } } while (htab[i] >= 0); } output(ent, outs); ent = c; if (free_ent < maxmaxcode) { codetab[i] = free_ent++; htab[i] = fcode; } else { cl_block(outs); } } output(ent, outs); output(EOFCode, outs); } int cur_accum = 0; int cur_bits = 0; int masks[] = {0x0000, 0x0001, 0x0003, 0x0007, 0x000F, 0x001F, 0x003F, 0x007F, 0x00FF, 0x01FF, 0x03FF, 0x07FF, 0x0FFF, 0x1FFF, 0x3FFF, 0x7FFF, 0xFFFF}; void output(int code, OutputStream outs) throws IOException { cur_accum &= masks[cur_bits]; if (cur_bits > 0) { cur_accum |= (code << cur_bits); } else { cur_accum = code; } cur_bits += n_bits; while (cur_bits >= 8) { char_out((byte) (cur_accum & 0xff), outs); cur_accum >>= 8; cur_bits -= 8; } if (free_ent > maxcode || clear_flg) { if (clear_flg) { maxcode = MAXCODE(n_bits = g_init_bits); clear_flg = false; } else { ++n_bits; if (n_bits == maxbits) { maxcode = maxmaxcode; } else { maxcode = MAXCODE(n_bits); } } } if (code == EOFCode) { while (cur_bits > 0) { char_out((byte) (cur_accum & 0xff), outs); cur_accum >>= 8; cur_bits -= 8; } flush_char(outs); } } void cl_block(OutputStream outs) throws IOException { cl_hash(hsize); free_ent = ClearCode + 2; clear_flg = true; output(ClearCode, outs); } void cl_hash(int hsize) { for (int i = 0; i < hsize; ++i) { htab[i] = -1; } } int a_count; void char_init() { a_count = 0; } byte[] accum = new byte[256]; void char_out(byte c, OutputStream outs) throws IOException { accum[a_count++] = c; if (a_count >= 254) { flush_char(outs); } } void flush_char(OutputStream outs) throws IOException { if (a_count > 0) { outs.write(a_count); outs.write(accum, 0, a_count); a_count = 0; } } } class IndexGif89Frame extends Gif89Frame { IndexGif89Frame(int width, int height, byte ci_pixels[]) { theWidth = width; theHeight = height; ciPixels = new byte[theWidth * theHeight]; System.arraycopy(ci_pixels, 0, ciPixels, 0, ciPixels.length); } Object getPixelSource() { return ciPixels; } } final class Put { static void ascii(String s, OutputStream os) throws IOException { byte[] bytes = new byte[s.length()]; for (int i = 0; i < bytes.length; ++i) { bytes[i] = (byte) s.charAt(i); } os.write(bytes); } static void leShort(int i16, OutputStream os) throws IOException { os.write(i16 & 0xff); os.write(i16 >> 8 & 0xff); } }