/javafx-iio/src/com/sun/javafx/iio/gif/GIFImageLoader2.java
Java | 473 lines | 352 code | 63 blank | 58 comment | 91 complexity | 1a0986452da1149915dc52ccfdc5ee80 MD5 | raw file
Possible License(s): GPL-2.0, LGPL-2.0, BSD-3-Clause, LGPL-2.1
- /*
- * Copyright (c) 2012, 2013, Oracle and/or its affiliates. All rights reserved.
- * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
- *
- * This code is free software; you can redistribute it and/or modify it
- * under the terms of the GNU General Public License version 2 only, as
- * published by the Free Software Foundation. Oracle designates this
- * particular file as subject to the "Classpath" exception as provided
- * by Oracle in the LICENSE file that accompanied this code.
- *
- * 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 General Public License
- * version 2 for more details (a copy is included in the LICENSE file that
- * accompanied this code).
- *
- * You should have received a copy of the GNU General Public License version
- * 2 along with this work; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
- *
- * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
- * or visit www.oracle.com if you need additional information or have any
- * questions.
- */
- package com.sun.javafx.iio.gif;
- 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.common.PushbroomScaler;
- import com.sun.javafx.iio.common.ScalerFactory;
- 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 GIFImageLoader2 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 int DEFAULT_FPS = 25;
- InputStream stream = null;
- int screenW, screenH, bgColor;
- byte globalPalette[][]; // r,g,b,a
- byte image[];
- public GIFImageLoader2(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);
- }
- }
- // 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;
- 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;
- ImageMetadata metadata = updateMetadata(screenW, screenH, imageControlCode & 0xFFFF);
- int disposalCode = (imageControlCode >>> 26) & 7;
- byte pImage[] = new byte[w * h];
- decodeImage(pImage, w, h, isInterlaced ? computeInterlaceReIndex(h) : null);
- ImageFrame imgGIF = decodePalette(pImage, palette, trnsIndex,
- left, top, w, h, disposalCode, metadata);
- // need to remove scaler from image decoder itself
- int[] outWH = ImageTools.computeDimensions(screenW, screenH, width, height, preserveAspectRatio);
- if (screenW != outWH[0] || screenH != outWH[1]) {
- imgGIF = scaleImage(imgGIF, outWH[0], outWH[1], smooth);
- }
- return imgGIF;
- }
- // 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 {
- while (n > 0) {
- int lskip = (int) stream.skip(n);
- if (lskip <= 0) {
- throw new EOFException();
- }
- n -= lskip;
- }
- }
- public void dispose() {}
- // fill whole image with bk color
- private void fillBackground(int trnsIndex, byte img[]) {
- byte r = globalPalette[0][bgColor], g = globalPalette[1][bgColor];
- byte b = globalPalette[2][bgColor], a = (bgColor == trnsIndex) ? (byte)0 : (byte)0xFF;
- for (int i = 0, pos = 0, l = screenW * screenH; i != l; pos += 4, ++i) {
- img[pos + 0] = r;
- img[pos + 1] = g;
- img[pos + 2] = b;
- img[pos + 3] = a;
- }
- }
- // decode palletized image into RGBA
- private ImageFrame decodePalette(byte[] srcImage, byte[][] palette, int trnsIndex,
- int left, int top, int w, int h, int disposalCode, ImageMetadata metadata) {
- 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) fillBackground(trnsIndex, image);
- return new ImageFrame(ImageStorage.ImageType.RGBA, ByteBuffer.wrap(img),
- screenW, screenH, screenW * 4, null, metadata);
- }
- // copy from PNG, needs exctract refactoring later
- // scales the image
- private ImageFrame scaleImage(ImageFrame imgPNG, int rWidth, int rHeight, boolean smooth) {
- byte image[] = ((ByteBuffer) imgPNG.getImageData()).array();
- int bpp = ImageStorage.getNumBands(imgPNG.getImageType());
- PushbroomScaler scaler = ScalerFactory.createScaler(screenW, screenH, bpp,
- rWidth, rHeight, smooth);
- for (int y = 0; y != screenH; ++y) {
- scaler.putSourceScanline(image, y * screenW * bpp);
- }
- return new ImageFrame(imgPNG.getImageType(), scaler.getDestination(),
- rWidth, rHeight, rWidth * bpp, null, imgPNG.getMetadata());
- }
- // 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, 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();
- }
- }
- }
- }