/*
* (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);
}
}
}
|