package ij.plugin;
import ij.*;
import ij.io.*;
import ij.gui.*;
import ij.process.*;
import ij.plugin.*;
import java.io.*;
import java.awt.*;
import java.awt.image.*;
import java.awt.Color;
import java.awt.Point;
import java.io.OutputStream;
import java.io.IOException;
import javax.imageio.ImageIO;
public class GifWriter implements PlugIn {
static int transparentIndex = Prefs.getTransparentIndex();
public void run(String path) {
ImagePlus imp = IJ.getImage();
if (path.equals("")) {
SaveDialog sd = new SaveDialog("Save as Gif", imp.getTitle(), ".gif");
if (sd.getFileName()==null) return;
path = sd.getDirectory()+sd.getFileName();
}
ImageStack stack = imp.getStack();
Overlay overlay = imp.getOverlay();
int nSlices = stack.getSize();
if (nSlices==1) { if (overlay!=null)
imp = imp.flatten();
try {
writeImage(imp, path, transparentIndex);
} catch (Exception e) {
String msg = e.getMessage();
if (msg==null || msg.equals(""))
msg = ""+e;
IJ.error("GifWriter", "An error occured writing the file.\n \n" + msg);
}
return;
}
GifEncoder ge = new GifEncoder();
double fps = imp.getCalibration().fps;
if (fps==0.0) fps = Animator.getFrameRate();
if (fps<=0.2) fps = 0.2;
if (fps>60.0) fps = 60.0;
ge.setDelay((int)((1.0/fps)*1000.0));
if (transparentIndex!=-1) {
ge.transparent = true;
ge.transIndex = transparentIndex;
}
ge.start(path);
ImagePlus tmp = new ImagePlus();
for (int i=1; i<=nSlices; i++) {
IJ.showStatus("writing: "+i+"/"+nSlices);
IJ.showProgress((double)i/nSlices);
tmp.setProcessor(stack.getProcessor(i));
if (overlay!=null) {
Overlay overlay2 = overlay.duplicate();
overlay2.crop(i, i);
if (overlay2.size()>0) {
tmp.setOverlay(overlay2);
tmp = tmp.flatten();
new ImageConverter(tmp).convertRGBtoIndexedColor(256);
}
}
try {
ge.addFrame(tmp);
} catch(Exception e) {
IJ.showMessage("Save as Gif", ""+e);
return;
}
}
ge.finish();
IJ.showStatus("");
IJ.showProgress(1.0);
}
private void writeImage(ImagePlus imp, String path, int transparentIndex) throws Exception {
if (transparentIndex>=0 && transparentIndex<=255)
writeImageWithTransparency(imp, path, transparentIndex);
else
ImageIO.write(imp.getBufferedImage(), "gif", new File(path));
}
private void writeImageWithTransparency(ImagePlus imp, String path, int transparentIndex) throws Exception {
int width = imp.getWidth();
int height = imp.getHeight();
ImageProcessor ip = imp.getProcessor();
IndexColorModel cm = (IndexColorModel)ip.getColorModel();
int size = cm.getMapSize();
byte[] reds = new byte[256];
byte[] greens = new byte[256];
byte[] blues = new byte[256];
cm.getReds(reds);
cm.getGreens(greens);
cm.getBlues(blues);
cm = new IndexColorModel(8, size, reds, greens, blues, transparentIndex);
WritableRaster wr = cm.createCompatibleWritableRaster(width, height);
DataBufferByte db = (DataBufferByte)wr.getDataBuffer();
byte[] biPixels = db.getData();
System.arraycopy(ip.getPixels(), 0, biPixels, 0, biPixels.length);
BufferedImage bi = new BufferedImage(cm, wr, false, null);
ImageIO.write(bi, "gif", new File(path));
}
}
class GifEncoder {
int width; int height;
boolean transparent; int transIndex; int repeat = 0; protected int delay = 50; boolean started = false; OutputStream out;
ImagePlus image; byte[] pixels; byte[] indexedPixels; int colorDepth; byte[] colorTab; int lctSize = 7; int dispose = 0; boolean closeStream = false; boolean firstFrame = true;
boolean sizeSet = false; int sample = 2; byte[] gct = null; boolean GCTextracted = false; boolean GCTloadedExternal = false; int GCTred = 0; int GCTgrn = 0; int GCTbl = 0; int GCTcindex = 0; boolean GCTsetTransparent = false; boolean GCToverideIndex = false; boolean GCToverideColor = false;
public boolean addFrame(ImagePlus image) {
if ((image == null) || !started) return false;
boolean ok = true;
try {
if (firstFrame) {
if (!sizeSet) setSize(image.getWidth(), image.getHeight());
writeLSD();
if (repeat>=0) writeNetscapeExt(); firstFrame = false;
}
int bitDepth = image.getBitDepth();
int k;
Process8bitCLT(image);
writeGraphicCtrlExt(); writeImageDesc(); writePalette(); writePixels(); } catch (IOException e) { ok = false; }
return ok;
}
void Process8bitCLT(ImagePlus image){
colorDepth = 8;
ImageProcessor ip = image.getProcessor();
ip = ip.convertToByte(true);
ColorModel cm = ip.getColorModel();
indexedPixels = (byte[])(ip.getPixels());
IndexColorModel m = (IndexColorModel)cm;
int mapSize = m.getMapSize();
if (transIndex>=mapSize) {
setTransparent(false);
transIndex = 0;
}
int k;
colorTab = new byte[mapSize*3];
for (int i = 0; i < mapSize; i++) {
k=i*3;
colorTab[k] = (byte)m.getRed(i);
colorTab[k+1] = (byte)m.getGreen(i);
colorTab[k+2] = (byte)m.getBlue(i);
}
m.finalize();
}
public boolean finish() {
if (!started) return false;
boolean ok = true;
started = false;
try {
out.write(0x3b); out.flush();
if (closeStream)
out.close();
} catch (IOException e) { ok = false; }
GCTextracted = false; GCTloadedExternal = false; transIndex = 0;
transparent = false;
gct = null; out = null;
image = null;
pixels = null;
indexedPixels = null;
colorTab = null;
closeStream = false;
firstFrame = true;
return ok;
}
public void setDelay(int ms) {
delay = Math.round(ms / 10.0f);
}
public void setDispose(int code) {
if (code >= 0)
dispose = code;
}
public void setFrameRate(float fps) {
if (fps != 0f) {
delay = Math.round(100f/fps);
}
}
public void setQuality(int quality) {
if (quality < 1) quality = 1;
sample = quality;
}
public void setRepeat(int iter) {
if (iter >= 0)
repeat = iter;
}
public void setSize(int w, int h) {
if (started && !firstFrame) return;
width = w;
height = h;
if (width < 1) width = 320;
if (height < 1) height = 240;
sizeSet = true;
}
public void setTransparent(boolean c) {
transparent = c;
}
public boolean start(OutputStream os) {
if (os == null) return false;
boolean ok = true;
closeStream = false;
out = os;
try {
writeString("GIF89a"); } catch (IOException e) { ok = false; }
return started = ok;
}
public boolean start(String file) {
boolean ok = true;
try {
out = new BufferedOutputStream(new FileOutputStream(file));
ok = start(out);
closeStream = true;
} catch (IOException e) { ok = false; }
return started = ok;
}
public void OverRideQuality(int npixs){
if(npixs>100000) sample = 10;
else sample = npixs/10000;
if(sample < 1) sample = 1;
}
protected void writeGraphicCtrlExt() throws IOException {
out.write(0x21); out.write(0xf9); out.write(4); int transp, disp;
if (!transparent) {
transp = 0;
disp = 0; } else {
transp = 1;
disp = 2; }
if (dispose >= 0)
disp = dispose & 7; disp <<= 2;
out.write( 0 | disp | 0 | transp);
writeShort(delay); out.write(transIndex); out.write(0); }
protected void writeImageDesc() throws IOException {
out.write(0x2c); writeShort(0); writeShort(0);
writeShort(width); writeShort(height);
out.write(0x80 | 0 | 0 | 0 | lctSize); }
protected void writeLSDgct() throws IOException {
writeShort(width);
writeShort(height);
out.write((0x80 | 0x70 | 0x00 | lctSize));
out.write(0); out.write(0); }
protected void writeLSD() throws IOException {
writeShort(width);
writeShort(height);
out.write((0x00 | 0x70 | 0x00 | 0x00));
out.write(0); out.write(0); }
protected void writeNetscapeExt() throws IOException {
out.write(0x21); out.write(0xff); out.write(11); writeString("NETSCAPE"+"2.0"); out.write(3); out.write(1); writeShort(repeat); out.write(0); }
protected void writePalette() throws IOException {
out.write(colorTab, 0, colorTab.length);
int n = (3 * 256) - colorTab.length;
for (int i = 0; i < n; i++)
out.write(0);
}
protected void writePixels() throws IOException {
LZWEncoder encoder = new LZWEncoder(width, height, indexedPixels, colorDepth);
encoder.encode(out);
}
protected void writeShort(int value) throws IOException {
out.write(value & 0xff);
out.write((value >> 8) & 0xff);
}
protected void writeString(String s) throws IOException {
for (int i = 0; i < s.length(); i++)
out.write((byte) s.charAt(i));
}
}
class LZWEncoder {
private static final int EOF = -1;
private int imgW, imgH;
private byte[] pixAry;
private int initCodeSize;
private int remaining;
private int curPixel;
static final int BITS = 12;
static final int HSIZE = 5003;
int n_bits; int maxbits = BITS; int maxcode; int maxmaxcode = 1 << BITS;
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;
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 };
int a_count;
byte[] accum = new byte[256];
LZWEncoder(int width, int height, byte[] pixels, int color_depth) {
imgW = width;
imgH = height;
pixAry = pixels;
initCodeSize = Math.max(2, color_depth);
}
void char_out( byte c, OutputStream outs ) throws IOException
{
accum[a_count++] = c;
if ( a_count >= 254 )
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;
}
void compress( int init_bits, OutputStream outs ) throws IOException
{
int fcode;
int i ;
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;
a_count = 0;
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 );
}
void encode(OutputStream os) throws IOException
{
os.write(initCodeSize);
remaining = imgW * imgH; curPixel = 0;
compress(initCodeSize + 1, os);
os.write(0); }
void flush_char( OutputStream outs ) throws IOException
{
if ( a_count > 0 )
{
outs.write( a_count );
outs.write( accum, 0, a_count );
a_count = 0;
}
}
final int MAXCODE( int n_bits )
{
return ( 1 << n_bits ) - 1;
}
private int nextPixel()
{
if (remaining == 0)
return EOF;
--remaining;
byte pix = pixAry[curPixel++];
return pix & 0xff;
}
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 );
}
}
}