/src/application/extensions/GIFLoader.java
Java | 472 lines | 355 code | 61 blank | 56 comment | 96 complexity | 507510efbff688b5621aa3d767721a6e MD5 | raw file
- /*
- * Copyright (c) 2012, 2015, Oracle and/or its affiliates. All rights reserved.
- * ORACLE PROPRIETARY/CONFIDENTIAL. Use is subject to license terms.
- *
- *
- *
- *
- *
- *
- *
- *
- *
- *
- *
- *
- *
- *
- *
- *
- *
- *
- *
- *
- */
- package application.extensions;
- import com.sun.javafx.iio.ImageFrame;
- import com.sun.javafx.iio.ImageMetadata;
- import com.sun.javafx.iio.ImageStorage;
- import com.sun.javafx.iio.common.ImageLoaderImpl;
- import com.sun.javafx.iio.common.ImageTools;
- import com.sun.javafx.iio.gif.GIFDescriptor;
- import java.io.EOFException;
- import java.io.IOException;
- import java.io.InputStream;
- import java.nio.ByteBuffer;
- import java.util.Arrays;
- /*
- * loader implementation for GIF89 file format
- */
- public class GIFLoader extends ImageLoaderImpl {
- static final byte FILE_SIG87[] = {'G', 'I', 'F', '8', '7', 'a'};
- static final byte FILE_SIG89[] = {'G', 'I', 'F', '8', '9', 'a'};
- static final byte NETSCAPE_SIG[] = {'N', 'E', 'T', 'S', 'C', 'A', 'P', 'E', '2', '.', '0'};
- static final int DEFAULT_FPS = 25;
- InputStream stream = null;
- int screenW, screenH, bgColor;
- byte globalPalette[][]; // r,g,b,a
- byte image[];
- int loopCount = 1;
- public GIFLoader(InputStream input) throws IOException {
- super(GIFDescriptor.getInstance());
- this.stream = input;
- readGlobalHeader();
- }
- // read GIF file header
- private void readGlobalHeader() throws IOException {
- byte signature[] = readBytes(new byte[6]);
- if (!Arrays.equals(FILE_SIG87, signature) && !Arrays.equals(FILE_SIG89, signature)) {
- throw new IOException("Bad GIF signature!");
- }
- screenW = readShort();
- screenH = readShort();
- int cInfo = readByte();
- bgColor = readByte();
- int aspectR = readByte();
- if ((cInfo & 0x80) != 0) {
- globalPalette = readPalete(2 << (cInfo & 7), -1);
- }
- image = new byte[screenW * screenH * 4];
- }
- // read palette data from the stream
- private byte[][] readPalete(int size, int trnsIndex) throws IOException {
- byte palette[][] = new byte[4][size];
- byte paletteData[] = readBytes(new byte[size*3]);
- for (int i = 0, idx = 0; i != size; ++i) {
- for (int k = 0; k != 3; ++k) {
- palette[k][i] = paletteData[idx++];
- }
- palette[3][i] = (i == trnsIndex) ? 0 : (byte)0xFF;
- }
- return palette;
- }
- // skip an extension
- private void consumeAnExtension() throws IOException {
- for (int blSize = readByte(); blSize != 0; blSize = readByte()) {
- skipBytes(blSize);
- }
- }
- private void readAppExtension() throws IOException {
- int size = readByte();
- byte buf[] = readBytes(new byte[size]);
- if (Arrays.equals(NETSCAPE_SIG, buf)) {
- for (int subBlockSize = readByte(); subBlockSize != 0; subBlockSize = readByte()) {
- byte subBlock[] = readBytes(new byte[subBlockSize]);
- int subBlockId = subBlock[0];
- if (subBlockSize == 3 && subBlockId == 1) { // loop count extension
- loopCount = (subBlock[1] & 0xff) | ((subBlock[2] & 0xff) << 8);
- }
- }
- } else {
- consumeAnExtension(); // read data sub-blocks
- }
- }
- // reads Image Control extension information
- // returns ((pField & 0x1F) << 24) + (trnsIndex << 16) + frameDelay;
- private int readControlCode() throws IOException {
- int size = readByte();
- int pField = readByte();
- int frameDelay = readShort();
- int trnsIndex = readByte();
- if (size != 4 || readByte() != 0) {
- throw new IOException("Bad GIF GraphicControlExtension");
- }
- return ((pField & 0x1F) << 24) + (trnsIndex << 16) + frameDelay;
- }
- // The method waits until image data in the stream
- // The method also reads and return Image Control extension information
- // returns -1 if EOF reached or the value of readControlCode
- private int waitForImageFrame() throws IOException {
- int controlData = 0;
- while (true) {
- int ch = stream.read();
- switch (ch) {
- case 0x2C:
- return controlData;
- case 0x21:
- switch (readByte()) {
- case 0xF9:
- controlData = readControlCode();
- break;
- case 0xFF:
- readAppExtension();
- break;
- default:
- consumeAnExtension();
- }
- break;
- case -1: case 0x3B: // EOF or end of GIF
- return -1;
- default:
- throw new IOException("Unexpected GIF control characher 0x"
- + String.format("%02X", ch));
- }
- }
- }
- // Decode the one frame of GIF form the input stread using internal LZWDecoder class
- private void decodeImage(byte image[], int w, int h, int interlace[]) throws IOException {
- LZWDecoder dec = new LZWDecoder();
- byte data[] = dec.getString();
- int y = 0, iPos = 0, xr = w;
- while (true) {
- int len = dec.readString();
- if (len == -1) { // end of stream
- dec.waitForTerminator();
- return;
- }
- for (int pos = 0; pos != len;) {
- int ax = xr < (len - pos) ? xr : (len - pos);
- System.arraycopy(data, pos, image, iPos, ax);
- iPos += ax;
- pos += ax;
- if ((xr -= ax) == 0) {
- if (++y == h) { // image is full
- dec.waitForTerminator();
- return;
- }
- int iY = interlace == null ? y : interlace[y];
- iPos = iY * w;
- xr = w;
- }
- }
- }
- }
- // computes row re-index for interlaced case
- private int[] computeInterlaceReIndex(int h) {
- int data[] = new int[h], pos = 0;
- for (int i = 0; i < h; i += 8) data[pos++] = i;
- for (int i = 4; i < h; i += 8) data[pos++] = i;
- for (int i = 2; i < h; i += 4) data[pos++] = i;
- for (int i = 1; i < h; i += 2) data[pos++] = i;
- return data;
- }
- // loads next image frame or null if no more
- public ImageFrame load(int imageIndex, int width, int height, boolean preserveAspectRatio, boolean smooth) throws IOException {
- int imageControlCode = waitForImageFrame();
- if (imageControlCode < 0) {
- return null;
- }
- int left = readShort(), top = readShort(), w = readShort(), h = readShort();
- // check if the image is in the virtual screen boundaries
- if (left + w > screenW || top + h > screenH) {
- throw new IOException("Wrong GIF image frame size");
- }
- int imgCtrl = readByte();
- boolean isTRNS = ((imageControlCode >>> 24) & 1) == 1;
- int trnsIndex = isTRNS ? (imageControlCode >>> 16) & 0xFF : -1;
- boolean localPalette = (imgCtrl & 0x80) != 0;
- boolean isInterlaced = (imgCtrl & 0x40) != 0;
- byte palette[][] = localPalette ? readPalete(2 << (imgCtrl & 7), trnsIndex) : globalPalette;
- int[] outWH = ImageTools.computeDimensions(screenW, screenH, width, height, preserveAspectRatio);
- width = outWH[0];
- height = outWH[1];
- ImageMetadata metadata = updateMetadata(width, height, imageControlCode & 0xFFFF);
- int disposalCode = (imageControlCode >>> 26) & 7;
- byte pImage[] = new byte[w * h];
- decodeImage(pImage, w, h, isInterlaced ? computeInterlaceReIndex(h) : null);
- ByteBuffer img = decodePalette(pImage, palette, trnsIndex,
- left, top, w, h, disposalCode);
- if (screenW != width || screenH != height) {
- img = ImageTools.scaleImage(img, screenW, screenH, 4,
- width, height, smooth);
- }
- return new ImageFrame(ImageStorage.ImageType.RGBA, img,
- width, height, width * 4, null, metadata);
- }
- // IO helpers
- private int readByte() throws IOException {
- int ch = stream.read();
- if (ch < 0) {
- throw new EOFException();
- }
- return ch;
- }
- private int readShort() throws IOException {
- int lsb = readByte(), msb = readByte();
- return lsb + (msb << 8);
- }
- private byte[] readBytes(byte data[]) throws IOException {
- return readBytes(data, 0, data.length);
- }
- private byte[] readBytes(byte data[], int offs, int size) throws IOException {
- while (size > 0) {
- int sz = stream.read(data, offs, size);
- if (sz < 0) {
- throw new EOFException();
- }
- offs += sz;
- size -= sz;
- }
- return data;
- }
- private void skipBytes(int n) throws IOException {
- ImageTools.skipFully(stream, n);
- }
- public void dispose() {}
- // GIF specification states that restore to background should fill the frame
- // with background color, but actually all modern programs fill with transparent color.
- private void restoreToBackground(byte img[], int left, int top, int w, int h) {
- for (int y = 0; y != h; ++y) {
- int iPos = ((top + y) * screenW + left) * 4;
- for (int x = 0; x != w; iPos += 4, ++x) {
- img[iPos + 3] = 0;
- }
- }
- }
- // decode palletized image into RGBA
- private ByteBuffer decodePalette(byte[] srcImage, byte[][] palette, int trnsIndex,
- int left, int top, int w, int h, int disposalCode) {
- byte img[] = (disposalCode == 3) ? image.clone() : image;
- for (int y = 0; y != h; ++y) {
- int iPos = ((top + y) * screenW + left) * 4;
- int i = y * w;
- if (trnsIndex < 0) {
- for (int x = 0; x != w; iPos += 4, ++x) {
- int index = 0xFF & srcImage[i + x];
- img[iPos + 0] = palette[0][index];
- img[iPos + 1] = palette[1][index];
- img[iPos + 2] = palette[2][index];
- img[iPos + 3] = palette[3][index];
- }
- } else {
- for (int x = 0; x != w; iPos += 4, ++x) {
- int index = 0xFF & srcImage[i + x];
- if (index != trnsIndex) {
- img[iPos + 0] = palette[0][index];
- img[iPos + 1] = palette[1][index];
- img[iPos + 2] = palette[2][index];
- img[iPos + 3] = palette[3][index];
- }
- }
- }
- }
- if (disposalCode != 3) img = img.clone();
- if (disposalCode == 2) restoreToBackground(image, left, top, w, h);
- return ByteBuffer.wrap(img);
- }
- // fill metadata
- private ImageMetadata updateMetadata(int w, int h, int delayTime) {
- ImageMetadata metaData = new ImageMetadata(null, true, null, null, null,
- delayTime != 0 ? delayTime*10 : 1000/DEFAULT_FPS, loopCount, w, h, null, null, null);
- updateImageMetadata(metaData);
- return metaData;
- }
- class LZWDecoder {
- private final int initCodeSize, clearCode, eofCode;
- private int codeSize, codeMask, tableIndex, oldCode;
- // input data buffer
- private int blockLength = 0, blockPos = 0;
- private byte block[] = new byte[255];
- private int inData = 0, inBits = 0;
- // table
- private int[] prefix = new int[4096];
- private byte[] suffix = new byte[4096];
- private byte[] initial = new byte[4096];
- private int[] length = new int[4096];
- private byte[] string = new byte[4096];
- public LZWDecoder() throws IOException {
- initCodeSize = readByte();
- clearCode = 1 << initCodeSize;
- eofCode = clearCode + 1;
- initTable();
- }
- // decode next string of data, which can be accessed by getString() method
- public final int readString() throws IOException {
- int code = getCode();
- if (code == eofCode) {
- return -1;
- } else if (code == clearCode) {
- initTable();
- code = getCode();
- if (code == eofCode) {
- return -1;
- }
- } else {
- int newSuffixIndex;
- int ti = tableIndex;
- if (code < ti) {
- newSuffixIndex = code;
- } else { // code == tableIndex
- newSuffixIndex = oldCode;
- if (code != ti) {
- throw new IOException("Bad GIF LZW: Out-of-sequence code!");
- }
- }
- int oc = oldCode;
- prefix[ti] = oc;
- suffix[ti] = initial[newSuffixIndex];
- initial[ti] = initial[oc];
- length[ti] = length[oc] + 1;
- ++tableIndex;
- if ((tableIndex == (1 << codeSize)) && (tableIndex < 4096)) {
- ++codeSize;
- codeMask = (1 << codeSize) - 1;
- }
- }
- // Reverse code
- int c = code;
- int len = length[c];
- for (int i = len - 1; i >= 0; i--) {
- string[i] = suffix[c];
- c = prefix[c];
- }
- oldCode = code;
- return len;
- }
- // data accessor, the data length returned by readString method
- public final byte[] getString() { return string; }
- // waits until data ends
- public final void waitForTerminator() throws IOException {
- consumeAnExtension();
- }
- // initialize LZW dctionary
- private void initTable() {
- int numEntries = 1 << initCodeSize;
- for (int i = 0; i < numEntries; i++) {
- prefix[i] = -1;
- suffix[i] = (byte) i;
- initial[i] = (byte) i;
- length[i] = 1;
- }
- // fill in the entire table for robustness against
- // out-of-sequence codes.
- for (int i = numEntries; i < 4096; i++) {
- prefix[i] = -1;
- length[i] = 1;
- }
- codeSize = initCodeSize + 1;
- codeMask = (1 << codeSize) - 1;
- tableIndex = numEntries + 2;
- oldCode = 0;
- }
- // reads codeSize bits from the stream
- private int getCode() throws IOException {
- while (inBits < codeSize) {
- inData |= nextByte() << inBits;
- inBits += 8;
- }
- int code = inData & codeMask;
- inBits -= codeSize;
- inData >>>= codeSize;
- return code;
- }
- // reads next in byte
- private int nextByte() throws IOException {
- if (blockPos == blockLength) {
- readData();
- }
- return (int)block[blockPos++] & 0xFF;
- }
- // reads next block if data
- private void readData() throws IOException {
- blockPos = 0;
- blockLength = readByte();
- if (blockLength > 0) {
- readBytes(block, 0, blockLength);
- } else {
- throw new EOFException();
- }
- }
- }
- }