package org.eclipse.swt.internal.image; | |
/* | |
* (c) Copyright IBM Corp. 2000, 2001. | |
* All Rights Reserved | |
*/ | |
import org.eclipse.swt.*; | |
import org.eclipse.swt.graphics.*; | |
import java.io.*; | |
final class GIFFileFormat extends FileFormat { | |
String signature, version; | |
boolean sorted; | |
int screenWidth, screenHeight, backgroundPixel, aspect, bitsPerPixel; | |
boolean userInput = false; | |
int disposalMethod = 0; | |
int delayTime = 0; | |
int transparentPixel = -1; | |
int repeatCount = 1; | |
static final int GIF_APPLICATION_EXTENSION_BLOCK_ID = 0xFF; | |
static final int GIF_GRAPHICS_CONTROL_BLOCK_ID = 0xF9; | |
static final int GIF_PLAIN_TEXT_BLOCK_ID = 0x01; | |
static final int GIF_COMMENT_BLOCK_ID = 0xFE; | |
static final int GIF_EXTENSION_BLOCK_ID = 0x21; | |
static final int GIF_IMAGE_BLOCK_ID = 0x2C; | |
static final int GIF_TRAILER_ID = 0x3B; | |
/** | |
* Return whether or not the specified input stream | |
* represents a GIF file. | |
*/ | |
public static boolean isGIFFile(LEDataInputStream stream) { | |
try { | |
byte[] signature = new byte[3]; | |
stream.read(signature); | |
stream.unread(signature); | |
return new String(signature).equals("GIF"); | |
} catch (Exception e) { | |
return false; | |
} | |
} | |
/** | |
* Answer a palette containing numGrays | |
* shades of gray, ranging from black to white. | |
*/ | |
static PaletteData grayRamp(int numGrays) { | |
int n = numGrays - 1; | |
RGB[] colors = new RGB[numGrays]; | |
for (int i = 0; i < numGrays; i++) { | |
int intensity = (byte)((i * 3) * 256 / n); | |
colors[i] = new RGB(intensity, intensity, intensity); | |
} | |
return new PaletteData(colors); | |
} | |
/** | |
* Load the GIF image(s) stored in the input stream. | |
* Return an array of ImageData representing the image(s). | |
*/ | |
ImageData[] loadFromByteStream() { | |
byte[] signatureBytes = new byte[3]; | |
byte[] versionBytes = new byte[3]; | |
byte[] block = new byte[7]; | |
try { | |
inputStream.read(signatureBytes); | |
signature = new String(signatureBytes); | |
if (!signature.equals("GIF")) | |
SWT.error(SWT.ERROR_INVALID_IMAGE); | |
inputStream.read(versionBytes); | |
version = new String(versionBytes); | |
inputStream.read(block); | |
} catch (IOException e) { | |
SWT.error(SWT.ERROR_IO, e); | |
} | |
screenWidth = (block[0] & 0xFF) | ((block[1] & 0xFF) << 8); | |
loader.logicalScreenWidth = screenWidth; | |
screenHeight = (block[2] & 0xFF) | ((block[3] & 0xFF) << 8); | |
loader.logicalScreenHeight = screenHeight; | |
byte bitField = block[4]; | |
backgroundPixel = block[5] & 0xFF; | |
aspect = block[6] & 0xFF; | |
bitsPerPixel = ((bitField >> 4) & 0x07) + 1; | |
int depth = (bitField & 0x7) + 1; | |
PaletteData palette = null; | |
if ((bitField & 0x80) != 0) { | |
// Global palette. | |
sorted = (bitField & 0x8) != 0; | |
palette = readPalette(1 << depth); | |
} else { | |
// No global palette. | |
sorted = false; | |
backgroundPixel = -1; | |
depth = bitsPerPixel; | |
} | |
loader.backgroundPixel = backgroundPixel; | |
getExtensions(); | |
int id = readID(); | |
ImageData[] images = new ImageData[0]; | |
while (id == GIF_IMAGE_BLOCK_ID) { | |
ImageData image = readImageBlock(depth, palette); | |
if (loader.hasListeners()) { | |
loader.notifyListeners(new ImageLoaderEvent(loader, image, 3, true)); | |
} | |
ImageData[] oldImages = images; | |
images = new ImageData[oldImages.length + 1]; | |
System.arraycopy(oldImages, 0, images, 0, oldImages.length); | |
images[images.length - 1] = image; | |
try { | |
/* Read the 0-byte terminator at the end of the image. */ | |
id = inputStream.read(); | |
if (id > 0) { | |
/* We read the terminator earlier. */ | |
inputStream.unread(new byte[] {(byte)id}); | |
} | |
} catch (IOException e) { | |
SWT.error(SWT.ERROR_IO, e); | |
} | |
getExtensions(); | |
id = readID(); | |
} | |
return images; | |
} | |
/** | |
* Read and return the next block or extension identifier from the file. | |
*/ | |
int readID() { | |
try { | |
return inputStream.read(); | |
} catch (IOException e) { | |
SWT.error(SWT.ERROR_IO, e); | |
} | |
return -1; | |
} | |
/** | |
* Read extensions until an image descriptor appears. | |
* In the future, if we care about the extensions, they | |
* should be properly grouped with the image data before | |
* which they appeared. Right now, the interesting parts | |
* of some extensions are kept, but the rest is discarded. | |
* Throw an error if an error occurs. | |
*/ | |
void getExtensions() { | |
int id = readID(); | |
while (id != GIF_IMAGE_BLOCK_ID && id != GIF_TRAILER_ID && id > 0) { | |
if (id == GIF_EXTENSION_BLOCK_ID) { | |
readExtension(); | |
} else { | |
SWT.error(SWT.ERROR_INVALID_IMAGE); | |
} | |
id = readID(); | |
} | |
if (id == GIF_IMAGE_BLOCK_ID || id == GIF_TRAILER_ID) { | |
try { | |
inputStream.unread(new byte[] {(byte)id}); | |
} catch (IOException e) { | |
SWT.error(SWT.ERROR_IO, e); | |
} | |
} | |
} | |
/** | |
* Read a control extension. | |
* Return the extension block data. | |
*/ | |
byte[] readExtension() { | |
int extensionID = readID(); | |
if (extensionID == GIF_COMMENT_BLOCK_ID) | |
return readCommentExtension(); | |
if (extensionID == GIF_PLAIN_TEXT_BLOCK_ID) | |
return readPlainTextExtension(); | |
if (extensionID == GIF_GRAPHICS_CONTROL_BLOCK_ID) | |
return readGraphicsControlExtension(); | |
if (extensionID == GIF_APPLICATION_EXTENSION_BLOCK_ID) | |
return readApplicationExtension(); | |
// Otherwise, we don't recognize the block. If the | |
// field size is correct, we can just skip over | |
// the block contents. | |
try { | |
int extSize = inputStream.read(); | |
if (extSize < 0) { | |
SWT.error(SWT.ERROR_INVALID_IMAGE); | |
} | |
byte[] ext = new byte[extSize]; | |
inputStream.read(ext, 0, extSize); | |
return ext; | |
} catch (IOException e) { | |
SWT.error(SWT.ERROR_IO, e); | |
return null; | |
} | |
} | |
/** | |
* We have just read the Comment extension identifier | |
* from the input stream. Read in the rest of the comment | |
* and return it. GIF comment blocks are variable size. | |
*/ | |
byte[] readCommentExtension() { | |
try { | |
byte[] comment = new byte[0]; | |
byte[] block = new byte[255]; | |
int size = inputStream.read(); | |
while ((size > 0) && (inputStream.read(block, 0, size) != -1)) { | |
byte[] oldComment = comment; | |
comment = new byte[oldComment.length + size]; | |
System.arraycopy(oldComment, 0, comment, 0, oldComment.length); | |
System.arraycopy(block, 0, comment, oldComment.length, size); | |
size = inputStream.read(); | |
} | |
return comment; | |
} catch (Exception e) { | |
SWT.error(SWT.ERROR_IO, e); | |
return null; | |
} | |
} | |
/** | |
* We have just read the PlainText extension identifier | |
* from the input stream. Read in the plain text info and text, | |
* and return the text. GIF plain text blocks are variable size. | |
*/ | |
byte[] readPlainTextExtension() { | |
try { | |
// Read size of block = 0x0C. | |
inputStream.read(); | |
// Read the text information (x, y, width, height, colors). | |
byte[] info = new byte[12]; | |
inputStream.read(info); | |
// Read the text. | |
byte[] text = new byte[0]; | |
byte[] block = new byte[255]; | |
int size = inputStream.read(); | |
while ((size > 0) && (inputStream.read(block, 0, size) != -1)) { | |
byte[] oldText = text; | |
text = new byte[oldText.length + size]; | |
System.arraycopy(oldText, 0, text, 0, oldText.length); | |
System.arraycopy(block, 0, text, oldText.length, size); | |
size = inputStream.read(); | |
} | |
return text; | |
} catch (Exception e) { | |
SWT.error(SWT.ERROR_IO, e); | |
return null; | |
} | |
} | |
/** | |
* We have just read the GraphicsControl extension identifier | |
* from the input stream. Read in the control information, store | |
* it, and return it. | |
*/ | |
byte[] readGraphicsControlExtension() { | |
try { | |
// Read size of block = 0x04. | |
inputStream.read(); | |
// Read the control block. | |
byte[] controlBlock = new byte[4]; | |
inputStream.read(controlBlock); | |
byte bitField = controlBlock[0]; | |
// Store the user input field. | |
userInput = (bitField & 0x02) != 0; | |
// Store the disposal method. | |
disposalMethod = (bitField >> 2) & 0x07; | |
// Store the delay time. | |
delayTime = (controlBlock[1] & 0xFF) | ((controlBlock[2] & 0xFF) << 8); | |
// Store the transparent color. | |
if ((bitField & 0x01) != 0) | |
transparentPixel = controlBlock[3] & 0xFF; | |
else | |
transparentPixel = -1; | |
// Read block terminator. | |
inputStream.read(); | |
return controlBlock; | |
} catch (Exception e) { | |
SWT.error(SWT.ERROR_IO, e); | |
return null; | |
} | |
} | |
/** | |
* We have just read the Application extension identifier | |
* from the input stream. Read in the rest of the extension, | |
* look for and store 'number of repeats', and return the data. | |
*/ | |
byte[] readApplicationExtension() { | |
try { | |
// Read size of block = 0x0B. | |
inputStream.read(); | |
// Read application identifier. | |
byte[] applicationBytes = new byte[8]; | |
inputStream.read(applicationBytes); | |
String application = new String(applicationBytes); | |
// Read authentication code. | |
byte[] authenticationBytes = new byte[3]; | |
inputStream.read(authenticationBytes); | |
String authentication = new String(authenticationBytes); | |
// Read application data. | |
byte[] data = new byte[0]; | |
byte[] block = new byte[255]; | |
int size = inputStream.read(); | |
while ((size > 0) && (inputStream.read(block, 0, size) != -1)) { | |
byte[] oldData = data; | |
data = new byte[oldData.length + size]; | |
System.arraycopy(oldData, 0, data, 0, oldData.length); | |
System.arraycopy(block, 0, data, oldData.length, size); | |
size = inputStream.read(); | |
} | |
// Look for the NETSCAPE 'repeat count' field for an animated GIF. | |
if (application.equals("NETSCAPE") && authentication.equals("2.0") && data[0] == 01) { | |
repeatCount = (data[1] & 0xFF) | ((data[2] & 0xFF) << 8); | |
loader.repeatCount = repeatCount; | |
} | |
return data; | |
} catch (Exception e) { | |
SWT.error(SWT.ERROR_IO, e); | |
return null; | |
} | |
} | |
/** | |
* Return a DeviceIndependentImage representing the | |
* image block at the current position in the input stream. | |
* Throw an error if an error occurs. | |
*/ | |
ImageData readImageBlock(int defaultDepth, PaletteData defaultPalette) { | |
int depth; | |
PaletteData palette; | |
byte[] block = new byte[9]; | |
try { | |
inputStream.read(block); | |
} catch (IOException e) { | |
SWT.error(SWT.ERROR_IO, e); | |
} | |
int left = (block[0] & 0xFF) | ((block[1] & 0xFF) << 8); | |
int top = (block[2] & 0xFF) | ((block[3] & 0xFF) << 8); | |
int width = (block[4] & 0xFF) | ((block[5] & 0xFF) << 8); | |
int height = (block[6] & 0xFF) | ((block[7] & 0xFF) << 8); | |
byte bitField = block[8]; | |
boolean interlaced = (bitField & 0x40) != 0; | |
boolean sorted = (bitField & 0x20) != 0; | |
if ((bitField & 0x80) != 0) { | |
// Local palette. | |
depth = (bitField & 0x7) + 1; | |
palette = readPalette(1 << depth); | |
} else { | |
// No local palette. | |
depth = defaultDepth; | |
palette = defaultPalette; | |
} | |
// Promote depth to next highest supported value. | |
if (!(depth == 1 || depth == 4 || depth == 8)) { | |
if (depth < 4) | |
depth = 4; | |
else | |
depth = 8; | |
} | |
if (palette == null) { | |
palette = grayRamp(1 << depth); | |
} | |
int initialCodeSize = -1; | |
try { | |
initialCodeSize = inputStream.read(); | |
} catch (IOException e) { | |
SWT.error(SWT.ERROR_IO, e); | |
} | |
if (initialCodeSize < 0) { | |
SWT.error(SWT.ERROR_INVALID_IMAGE); | |
} | |
ImageData image = ImageData.internal_new( | |
width, | |
height, | |
depth, | |
palette, | |
4, | |
null, | |
0, | |
null, | |
null, | |
-1, | |
transparentPixel, | |
SWT.IMAGE_GIF, | |
left, | |
top, | |
disposalMethod, | |
delayTime); | |
LZWCodec codec = new LZWCodec(); | |
codec.decode(inputStream, loader, image, interlaced, initialCodeSize); | |
return image; | |
} | |
/** | |
* Read a palette from the input stream. | |
*/ | |
PaletteData readPalette(int numColors) { | |
byte[] bytes = new byte[numColors * 3]; | |
try { | |
if (inputStream.read(bytes) != bytes.length) | |
SWT.error(SWT.ERROR_INVALID_IMAGE); | |
} catch (IOException e) { | |
SWT.error(SWT.ERROR_IO, e); | |
} | |
RGB[] colors = new RGB[numColors]; | |
for (int i = 0; i < numColors; i++) | |
colors[i] = new RGB(bytes[i*3] & 0xFF, | |
bytes[i*3+1] & 0xFF, bytes[i*3+2] & 0xFF); | |
return new PaletteData(colors); | |
} | |
/** | |
* Write the specified device independent image | |
* to the output stream. | |
*/ | |
void unloadIntoByteStream(ImageData image) { | |
if (!((image.depth == 1) || (image.depth == 4) || (image.depth == 8))) { | |
SWT.error(SWT.ERROR_UNSUPPORTED_DEPTH); | |
} | |
byte bitField = (byte)((0x80 & 0xF8 & 0xF7 & 0x8F) + (image.depth - 1) + ((image.depth - 1) * 16)); | |
try { | |
outputStream.write(new byte[] { (byte)'G', (byte)'I', (byte)'F' }); | |
outputStream.write(new byte[] { (byte)'8', (byte)'9', (byte)'a' }); | |
outputStream.writeShort((short)image.width); | |
outputStream.writeShort((short)image.height); | |
outputStream.writeByte(bitField); | |
outputStream.writeByte((byte)0); | |
outputStream.writeByte((byte)0); | |
} catch (IOException e) { | |
SWT.error(SWT.ERROR_IO, e); | |
} | |
writePalette(image.palette, image.depth); | |
if (image.transparentPixel != -1 || image.disposalMethod != 0 || image.delayTime != 0) { | |
writeGraphicsControlBlock(image); | |
} | |
writeImageBlock(image); | |
} | |
/** | |
* Write out a GraphicsControlBlock to describe | |
* the specified device independent image. | |
*/ | |
void writeGraphicsControlBlock(ImageData image) { | |
try { | |
outputStream.write(GIF_EXTENSION_BLOCK_ID); | |
outputStream.write(GIF_GRAPHICS_CONTROL_BLOCK_ID); | |
outputStream.write(0x04); // size of block | |
byte[] gcBlock = new byte[4]; | |
gcBlock[0] = (byte)0xFD; | |
gcBlock[1] = 0; | |
gcBlock[2] = 0; | |
gcBlock[3] = 0; | |
if (image.transparentPixel == -1) { | |
gcBlock[0] = (byte)(gcBlock[0] & 0xFE); | |
} else { | |
gcBlock[0] = (byte)(gcBlock[0] | 0x01); | |
gcBlock[3] = (byte)image.transparentPixel; | |
} | |
if (image.disposalMethod != 0) { | |
gcBlock[0] = (byte)(gcBlock[0] | ((image.disposalMethod & 0x07) << 2)); | |
} | |
if (image.delayTime != 0) { | |
gcBlock[1] = (byte)(image.delayTime & 0xFF); | |
gcBlock[2] = (byte)((image.delayTime >> 8) & 0xFF); | |
} | |
outputStream.write(gcBlock); | |
outputStream.write(0); // block terminator | |
} catch (IOException e) { | |
SWT.error(SWT.ERROR_IO, e); | |
} | |
} | |
/** | |
* Write the specified device independent image | |
* to the current position in the output stream. | |
*/ | |
void writeImageBlock(ImageData image) { | |
try { | |
outputStream.write(GIF_IMAGE_BLOCK_ID); | |
byte[] block = new byte[9]; | |
block[0] = (byte)(image.x & 0xFF); | |
block[1] = (byte)((image.x >> 8) & 0xFF); | |
block[2] = (byte)(image.y & 0xFF); | |
block[3] = (byte)((image.y >> 8) & 0xFF); | |
block[4] = (byte)(image.width & 0xFF); | |
block[5] = (byte)((image.width >> 8) & 0xFF); | |
block[6] = (byte)(image.height & 0xFF); | |
block[7] = (byte)((image.height >> 8) & 0xFF); | |
block[8] = 0; // no interlace, no sort, no local palette | |
outputStream.write(block); | |
outputStream.write(image.depth); | |
} catch (IOException e) { | |
SWT.error(SWT.ERROR_IO, e); | |
} | |
new LZWCodec().encode(outputStream, image); | |
} | |
/** | |
* Write the specified palette to the output stream. | |
*/ | |
void writePalette(PaletteData palette, int depth) { | |
byte[] bytes = new byte[(1 << depth) * 3]; | |
int offset = 0; | |
for (int i = 0; i < palette.colors.length; i++) { | |
RGB color = palette.colors[i]; | |
bytes[offset] = (byte)color.red; | |
bytes[offset + 1] = (byte)color.green; | |
bytes[offset + 2] = (byte)color.blue; | |
offset += 3;; | |
} | |
try { | |
outputStream.write(bytes); | |
} catch (IOException e) { | |
SWT.error(SWT.ERROR_IO, e); | |
} | |
} | |
} |