org.eclipse.swt.internal.image.GIFFileFormat Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of org.eclipse.swt.win32.win32.x86 Show documentation
Show all versions of org.eclipse.swt.win32.win32.x86 Show documentation
SWT is an open source widget toolkit for Java designed to provide efficient, portable access to the user-interface facilities of the operating systems on which it is implemented.
The newest version!
/*******************************************************************************
* Copyright (c) 2000, 2012 IBM Corporation and others.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* IBM Corporation - initial API and implementation
*******************************************************************************/
package org.eclipse.swt.internal.image;
import org.eclipse.swt.*;
import org.eclipse.swt.graphics.*;
import java.io.*;
public final class GIFFileFormat extends FileFormat {
String signature;
int screenWidth, screenHeight, backgroundPixel, bitsPerPixel, defaultDepth;
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;
static final byte [] GIF89a = new byte[] { (byte)'G', (byte)'I', (byte)'F', (byte)'8', (byte)'9', (byte)'a' };
static final byte [] NETSCAPE2_0 = new byte[] { (byte)'N', (byte)'E', (byte)'T', (byte)'S', (byte)'C', (byte)'A', (byte)'P', (byte)'E', (byte)'2', (byte)'.', (byte)'0' };
/**
* 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);
}
boolean isFileFormat(LEDataInputStream stream) {
try {
byte[] signature = new byte[3];
stream.read(signature);
stream.unread(signature);
return signature[0] == 'G' && signature[1] == 'I' && signature[2] == 'F';
} catch (Exception e) {
return false;
}
}
/**
* Load the GIF image(s) stored in the input stream.
* Return an array of ImageData representing the image(s).
*/
ImageData[] loadFromByteStream() {
byte[] signature = new byte[3];
byte[] versionBytes = new byte[3];
byte[] block = new byte[7];
try {
inputStream.read(signature);
if (!(signature[0] == 'G' && signature[1] == 'I' && signature[2] == 'F'))
SWT.error(SWT.ERROR_INVALID_IMAGE);
inputStream.read(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;
defaultDepth = (bitField & 0x7) + 1;
PaletteData palette = null;
if ((bitField & 0x80) != 0) {
// Global palette.
//sorted = (bitField & 0x8) != 0;
palette = readPalette(1 << defaultDepth);
} else {
// No global palette.
//sorted = false;
backgroundPixel = -1;
defaultDepth = bitsPerPixel;
}
loader.backgroundPixel = backgroundPixel;
ImageData[] images = new ImageData[0];
int id = readID();
while (id != GIF_TRAILER_ID && id != -1) {
if (id == GIF_IMAGE_BLOCK_ID) {
ImageData image = readImageBlock(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;
} else if (id == GIF_EXTENSION_BLOCK_ID) {
/* Read the extension block. Currently, only the
* interesting parts of certain extensions are kept,
* and the rest is discarded. In future, if we want
* to keep extensions, they should be grouped with
* the image data before which they appear.
*/
readExtension();
} else {
/* The GIF is not to spec, but try to salvage it
* if we read at least one image. */
if (images.length > 0) break;
SWT.error(SWT.ERROR_INVALID_IMAGE);
}
id = readID(); // block terminator (0)
if (id == 0) id = readID(); // next block ID (unless we just read it)
}
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 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;
}
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 block data.
int blockSize = inputStream.read();
byte[] blockData = new byte[blockSize];
inputStream.read(blockData);
// 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.
boolean netscape =
blockSize > 7 &&
blockData[0] == 'N' &&
blockData[1] == 'E' &&
blockData[2] == 'T' &&
blockData[3] == 'S' &&
blockData[4] == 'C' &&
blockData[5] == 'A' &&
blockData[6] == 'P' &&
blockData[7] == 'E';
boolean authentic =
blockSize > 10 &&
blockData[8] == '2' &&
blockData[9] == '.' &&
blockData[10] == '0';
if (netscape && authentic && data[0] == 01) { //$NON-NLS-1$ //$NON-NLS-2$
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(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;
}
/* Work around: Ignore the case where a GIF specifies an
* invalid index for the transparent pixel that is larger
* than the number of entries in the palette. */
if (transparentPixel > 1 << depth) {
transparentPixel = -1;
}
// 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);
}
void unloadIntoByteStream(ImageLoader loader) {
/* Step 1: Acquire GIF parameters. */
ImageData[] data = loader.data;
int frameCount = data.length;
boolean multi = frameCount > 1;
ImageData firstImage = data[0];
int logicalScreenWidth = multi ? loader.logicalScreenWidth : firstImage.width;
int logicalScreenHeight = multi ? loader.logicalScreenHeight : firstImage.height;
int backgroundPixel = loader.backgroundPixel;
int depth = firstImage.depth;
PaletteData palette = firstImage.palette;
RGB[] colors = palette.getRGBs();
short globalTable = 1;
/* Step 2: Check for validity and global/local color map. */
if (!(depth == 1 || depth == 4 || depth == 8)) {
SWT.error(SWT.ERROR_UNSUPPORTED_DEPTH);
}
for (int i=0; i> 8) & 0xFF);
block[2] = (byte)(y & 0xFF);
block[3] = (byte)((y >> 8) & 0xFF);
block[4] = (byte)(width & 0xFF);
block[5] = (byte)((width >> 8) & 0xFF);
block[6] = (byte)(height & 0xFF);
block[7] = (byte)((height >> 8) & 0xFF);
block[8] = (byte)(globalTable == 0 ? (depth-1) | 0x80 : 0x00);
outputStream.write(block);
} catch (IOException e) {
SWT.error(SWT.ERROR_IO, e);
}
/* Step 8: Write Local Color Table for each frame if applicable. */
if (globalTable == 0) {
writePalette(data[frame].palette, depth);
}
/* Step 9: Write the actual data for each frame. */
try {
outputStream.write(depth); // Minimum LZW Code size
} catch (IOException e) {
SWT.error(SWT.ERROR_IO, e);
}
new LZWCodec().encode(outputStream, data[frame]);
}
/* Step 10: Write GIF terminator. */
try {
outputStream.write(0x3B);
} catch (IOException e) {
SWT.error(SWT.ERROR_IO, e);
}
}
/**
* 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);
byte[] gcBlock = new byte[4];
gcBlock[0] = 0;
gcBlock[1] = 0;
gcBlock[2] = 0;
gcBlock[3] = 0;
if (image.transparentPixel != -1) {
gcBlock[0] = (byte)0x01;
gcBlock[3] = (byte)image.transparentPixel;
}
if (image.disposalMethod != 0) {
gcBlock[0] |= (byte)((image.disposalMethod & 0x07) << 2);
}
if (image.delayTime != 0) {
gcBlock[1] = (byte)(image.delayTime & 0xFF);
gcBlock[2] = (byte)((image.delayTime >> 8) & 0xFF);
}
outputStream.write((byte)gcBlock.length);
outputStream.write(gcBlock);
outputStream.write(0); // Block terminator
} catch (IOException e) {
SWT.error(SWT.ERROR_IO, e);
}
}
/**
* 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);
}
}
}