//******************************************************************************
// Gif89Frame.java
//******************************************************************************
//package net.jmge.gif;
package com.idega.graphics.encoder.gif;
import java.awt.Point;
import java.io.OutputStream;
import java.io.IOException;
//==============================================================================
/**
* First off, just to dispel any doubt, this class and its subclasses have
* nothing to do with GUI "frames" such as java.awt.Frame. We merely use the
* term in its very common sense of a still picture in an animation sequence.
* It's hoped that the restricted context will prevent any confusion.
* <p>
* An instance of this class is used in conjunction with a Gif89Encoder object
* to represent and encode a single static image and its associated "control"
* data. A Gif89Frame doesn't know or care whether it is encoding one of the
* many animation frames in a GIF movie, or the single bitmap in a "normal"
* GIF. (FYI, this design mirrors the encoded GIF structure.)
* <p>
* Since Gif89Frame is an abstract class we don't instantiate it directly, but
* instead create instances of its concrete subclasses, IndexGif89Frame and
* DirectGif89Frame. From the API standpoint, these subclasses differ only
* in the sort of data their instances are constructed from. Most folks will
* probably work with DirectGif89Frame, since it can be constructed from a
* java.awt.Image object, but the lower-level IndexGif89Frame class offers
* advantages in specialized circumstances. (Of course, in routine situations
* you might not explicitly instantiate any frames at all, instead letting
* Gif89Encoder's convenience methods do the honors.)
* <p>
* As far as the public API is concerned, objects in the Gif89Frame hierarchy
* interact with a Gif89Encoder only via the latter's methods for adding and
* querying frames. (As a side note, you should know that while Gif89Encoder
* objects are permanently modified by the addition of Gif89Frames, the reverse
* is NOT true. That is, even though the ultimate encoding of a Gif89Frame may
* be affected by the context its parent encoder object provides, it retains
* its original condition and can be reused in a different context.)
* <p>
* The core pixel-encoding code in this class was essentially lifted from
* Jef Poskanzer's well-known <cite>Acme GifEncoder</cite>, so please see the
* <a href="../readme.txt">readme</a> containing his notice.
*
* @version 0.90 beta (15-Jul-2000)
* @author J. M. G. Elliott (tep@jmge.net)
* @see Gif89Encoder
* @see DirectGif89Frame
* @see IndexGif89Frame
*/
public abstract class Gif89Frame {
//// Public "Disposal Mode" constants ////
/** The animated GIF renderer shall decide how to dispose of this Gif89Frame's
* display area.
* @see Gif89Frame#setDisposalMode
*/
public static final int DM_UNDEFINED = 0;
/** The animated GIF renderer shall take no display-disposal action.
* @see Gif89Frame#setDisposalMode
*/
public static final int DM_LEAVE = 1;
/** The animated GIF renderer shall replace this Gif89Frame's area with the
* background color.
* @see Gif89Frame#setDisposalMode
*/
public static final int DM_BGCOLOR = 2;
/** The animated GIF renderer shall replace this Gif89Frame's area with the
* previous frame's bitmap.
* @see Gif89Frame#setDisposalMode
*/
public static final int DM_REVERT = 3;
//// Bitmap variables set in package subclass constructors ////
int theWidth = -1;
int theHeight = -1;
byte[] ciPixels;
//// GIF graphic frame control options ////
private Point thePosition = new Point(0, 0);
private boolean isInterlaced;
private int csecsDelay;
private int disposalCode = DM_LEAVE;
//----------------------------------------------------------------------------
/** Set the position of this frame within a larger animation display space.
*
* @param p
* Coordinates of the frame's upper left corner in the display space.
* (Default: The logical display's origin [0, 0])
* @see Gif89Encoder#setLogicalDisplay
*/
public void setPosition(Point p)
{
this.thePosition = new Point(p);
}
//----------------------------------------------------------------------------
/** Set or clear the interlace flag.
*
* @param b
* true if you want interlacing. (Default: false)
*/
public void setInterlaced(boolean b)
{
this.isInterlaced = b;
}
//----------------------------------------------------------------------------
/** Set the between-frame interval.
*
* @param interval
* Centiseconds to wait before displaying the subsequent frame.
* (Default: 0)
*/
public void setDelay(int interval)
{
this.csecsDelay = interval;
}
//----------------------------------------------------------------------------
/** Setting this option determines (in a cooperative GIF-viewer) what will be
* done with this frame's display area before the subsequent frame is
* displayed. For instance, a setting of DM_BGCOLOR can be used for erasure
* when redrawing with displacement.
*
* @param code
* One of the four int constants of the Gif89Frame.DM_* series.
* (Default: DM_LEAVE)
*/
public void setDisposalMode(int code)
{
this.disposalCode = code;
}
//----------------------------------------------------------------------------
Gif89Frame() {} // package-visible default constructor
/**
*
* @uml.property name="pixelSource"
*/
//----------------------------------------------------------------------------
abstract Object getPixelSource();
//----------------------------------------------------------------------------
int getWidth() { return this.theWidth; }
//----------------------------------------------------------------------------
int getHeight() { return this.theHeight; }
//----------------------------------------------------------------------------
byte[] getPixelSink() { return this.ciPixels; }
//----------------------------------------------------------------------------
void encode(OutputStream os, boolean epluribus, int color_depth,
int transparent_index) throws IOException
{
writeGraphicControlExtension(os, epluribus, transparent_index);
writeImageDescriptor(os);
new GifPixelsEncoder(
this.theWidth, this.theHeight, this.ciPixels, this.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) // using transparency or animating ?
{
os.write('!'); // GIF Extension Introducer
os.write(0xf9); // Graphic Control Label
os.write(4); // subsequent data block size
os.write((this.disposalCode << 2) | transflag); // packed fields (1 byte)
Put.leShort(this.csecsDelay, os); // delay field (2 bytes)
os.write(itransparent); // transparent index field
os.write(0); // block terminator
}
}
//----------------------------------------------------------------------------
private void writeImageDescriptor(OutputStream os) throws IOException
{
os.write(','); // Image Separator
Put.leShort(this.thePosition.x, os);
Put.leShort(this.thePosition.y, os);
Put.leShort(this.theWidth, os);
Put.leShort(this.theHeight, os);
os.write(this.isInterlaced ? 0x40 : 0); // packed fields (1 byte)
}
}
//==============================================================================
class GifPixelsEncoder {
private static final int EOF = -1;
private int imgW;
private int imgH;
private byte[] pixAry;
private boolean wantInterlaced;
private int initCodeSize;
// raster data navigators
private int countDown;
private int xCur;
private int yCur;
private int curPass;
//----------------------------------------------------------------------------
GifPixelsEncoder(int width, int height, byte[] pixels, boolean interlaced,
int color_depth)
{
this.imgW = width;
this.imgH = height;
this.pixAry = pixels;
this.wantInterlaced = interlaced;
this.initCodeSize = Math.max(2, color_depth);
}
//----------------------------------------------------------------------------
void encode(OutputStream os) throws IOException
{
os.write(this.initCodeSize); // write "initial code size" byte
this.countDown = this.imgW * this.imgH; // reset navigation variables
this.xCur = this.yCur = this.curPass = 0;
compress(this.initCodeSize + 1, os); // compress and write the pixel data
os.write(0); // write block terminator
}
//****************************************************************************
// (J.E.) The logic of the next two methods is largely intact from
// Jef Poskanzer. Some stylistic changes were made for consistency sake,
// plus the second method accesses the pixel value from a prefiltered linear
// array. That's about it.
//****************************************************************************
//----------------------------------------------------------------------------
// Bump the 'xCur' and 'yCur' to point to the next pixel.
//----------------------------------------------------------------------------
private void bumpPosition()
{
// Bump the current X position
++this.xCur;
// If we are at the end of a scan line, set xCur back to the beginning
// If we are interlaced, bump the yCur to the appropriate spot,
// otherwise, just increment it.
if (this.xCur == this.imgW)
{
this.xCur = 0;
if (!this.wantInterlaced) {
++this.yCur;
}
else {
switch (this.curPass)
{
case 0:
this.yCur += 8;
if (this.yCur >= this.imgH)
{
++this.curPass;
this.yCur = 4;
}
break;
case 1:
this.yCur += 8;
if (this.yCur >= this.imgH)
{
++this.curPass;
this.yCur = 2;
}
break;
case 2:
this.yCur += 4;
if (this.yCur >= this.imgH)
{
++this.curPass;
this.yCur = 1;
}
break;
case 3:
this.yCur += 2;
break;
}
}
}
}
//----------------------------------------------------------------------------
// Return the next pixel from the image
//----------------------------------------------------------------------------
private int nextPixel()
{
if (this.countDown == 0) {
return EOF;
}
--this.countDown;
byte pix = this.pixAry[this.yCur * this.imgW + this.xCur];
bumpPosition();
return pix & 0xff;
}
//****************************************************************************
// (J.E.) I didn't touch Jef Poskanzer's code from this point on. (Well, OK,
// I changed the name of the sole outside method it accesses.) I figure
// if I have no idea how something works, I shouldn't play with it :)
//
// Despite its unencapsulated structure, this section is actually highly
// self-contained. The calling code merely calls compress(), and the present
// code calls nextPixel() in the caller. That's the sum total of their
// communication. I could have dumped it in a separate class with a callback
// via an interface, but it didn't seem worth messing with.
//****************************************************************************
// GIFCOMPR.C - GIF Image compression routines
//
// Lempel-Ziv compression based on 'compress'. GIF modifications by
// David Rowley (mgardi@watdcsu.waterloo.edu)
// General DEFINEs
static final int BITS = 12;
static final int HSIZE = 5003; // 80% occupancy
// GIF Image compression - modified 'compress'
//
// Based on: compress.c - File compression ala IEEE Computer, June 1984.
//
// By Authors: Spencer W. Thomas (decvax!harpo!utah-cs!utah-gr!thomas)
// Jim McKie (decvax!mcvax!jim)
// Steve Davies (decvax!vax135!petsd!peora!srd)
// Ken Turkowski (decvax!decwrl!turtlevax!ken)
// James A. Woods (decvax!ihnp4!ames!jaw)
// Joe Orost (decvax!vax135!petsd!joe)
int n_bits; // number of bits/code
int maxbits = BITS; // user settable max # bits/code
int maxcode; // maximum code, given n_bits
int maxmaxcode = 1 << BITS; // should NEVER generate this code
final int MAXCODE( int n_bits )
{
return ( 1 << n_bits ) - 1;
}
int[] htab = new int[HSIZE];
int[] codetab = new int[HSIZE];
int hsize = HSIZE; // for dynamic table sizing
int free_ent = 0; // first unused entry
// block compression parameters -- after all codes are used up,
// and compression rate changes, start over.
boolean clear_flg = false;
// Algorithm: use open addressing double hashing (no chaining) on the
// prefix code / next character combination. We do a variant of Knuth's
// algorithm D (vol. 3, sec. 6.4) along with G. Knott's relatively-prime
// secondary probe. Here, the modular division first probe is gives way
// to a faster exclusive-or manipulation. Also do block compression with
// an adaptive reset, whereby the code table is cleared when the compression
// ratio decreases, but after the table fills. The variable-length output
// codes are re-sized at this point, and a special CLEAR code is generated
// for the decompressor. Late addition: construct the table according to
// file size for noticeable speed improvement on small files. Please direct
// questions about this implementation to ames!jaw.
int g_init_bits;
/**
*
* @uml.property name="clearCode"
*/
int ClearCode;
/**
*
* @uml.property name="eOFCode"
*/
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;
// Set up the globals: g_init_bits - initial number of bits
this.g_init_bits = init_bits;
// Set up the necessary values
this.clear_flg = false;
this.n_bits = this.g_init_bits;
this.maxcode = MAXCODE( this.n_bits );
this.ClearCode = 1 << ( init_bits - 1 );
this.EOFCode = this.ClearCode + 1;
this.free_ent = this.ClearCode + 2;
char_init();
ent = nextPixel();
hshift = 0;
for ( fcode = this.hsize; fcode < 65536; fcode *= 2 ) {
++hshift;
}
hshift = 8 - hshift; // set hash code range bound
hsize_reg = this.hsize;
cl_hash( hsize_reg ); // clear hash table
output( this.ClearCode, outs );
outer_loop:
while ( (c = nextPixel()) != EOF )
{
fcode = ( c << this.maxbits ) + ent;
i = ( c << hshift ) ^ ent; // xor hashing
if ( this.htab[i] == fcode )
{
ent = this.codetab[i];
continue;
}
else if ( this.htab[i] >= 0 ) // non-empty slot
{
disp = hsize_reg - i; // secondary hash (after G. Knott)
if ( i == 0 ) {
disp = 1;
}
do
{
if ( (i -= disp) < 0 ) {
i += hsize_reg;
}
if ( this.htab[i] == fcode )
{
ent = this.codetab[i];
continue outer_loop;
}
}
while ( this.htab[i] >= 0 );
}
output( ent, outs );
ent = c;
if ( this.free_ent < this.maxmaxcode )
{
this.codetab[i] = this.free_ent++; // code -> hashtable
this.htab[i] = fcode;
}
else {
cl_block( outs );
}
}
// Put out the final code.
output( ent, outs );
output( this.EOFCode, outs );
}
// output
//
// Output the given code.
// Inputs:
// code: A n_bits-bit integer. If == -1, then EOF. This assumes
// that n_bits =< wordsize - 1.
// Outputs:
// Outputs code to the file.
// Assumptions:
// Chars are 8 bits long.
// Algorithm:
// Maintain a BITS character long buffer (so that 8 codes will
// fit in it exactly). Use the VAX insv instruction to insert each
// code in turn. When the buffer fills up empty it and start over.
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
{
this.cur_accum &= this.masks[this.cur_bits];
if ( this.cur_bits > 0 ) {
this.cur_accum |= ( code << this.cur_bits );
}
else {
this.cur_accum = code;
}
this.cur_bits += this.n_bits;
while ( this.cur_bits >= 8 )
{
char_out( (byte) ( this.cur_accum & 0xff ), outs );
this.cur_accum >>= 8;
this.cur_bits -= 8;
}
// If the next entry is going to be too big for the code size,
// then increase it, if possible.
if ( this.free_ent > this.maxcode || this.clear_flg )
{
if ( this.clear_flg )
{
this.maxcode = MAXCODE(this.n_bits = this.g_init_bits);
this.clear_flg = false;
}
else
{
++this.n_bits;
if ( this.n_bits == this.maxbits ) {
this.maxcode = this.maxmaxcode;
}
else {
this.maxcode = MAXCODE(this.n_bits);
}
}
}
if ( code == this.EOFCode )
{
// At EOF, write the rest of the buffer.
while ( this.cur_bits > 0 )
{
char_out( (byte) ( this.cur_accum & 0xff ), outs );
this.cur_accum >>= 8;
this.cur_bits -= 8;
}
flush_char( outs );
}
}
// Clear out the hash table
// table clear for block compress
void cl_block( OutputStream outs ) throws IOException
{
cl_hash( this.hsize );
this.free_ent = this.ClearCode + 2;
this.clear_flg = true;
output( this.ClearCode, outs );
}
// reset code table
void cl_hash( int hsize )
{
for ( int i = 0; i < hsize; ++i ) {
this.htab[i] = -1;
}
}
// GIF Specific routines
// Number of characters so far in this 'packet'
int a_count;
// Set up the 'byte output' routine
void char_init()
{
this.a_count = 0;
}
// Define the storage for the packet accumulator
byte[] accum = new byte[256];
// Add a character to the end of the current packet, and if it is 254
// characters, flush the packet to disk.
void char_out( byte c, OutputStream outs ) throws IOException
{
this.accum[this.a_count++] = c;
if ( this.a_count >= 254 ) {
flush_char( outs );
}
}
// Flush the packet to disk, and reset the accumulator
void flush_char( OutputStream outs ) throws IOException
{
if ( this.a_count > 0 )
{
outs.write( this.a_count );
outs.write( this.accum, 0, this.a_count );
this.a_count = 0;
}
}
}