summaryrefslogtreecommitdiffstats
path: root/awt/org/apache/harmony/awt/gl/image/GifDecoder.java
diff options
context:
space:
mode:
Diffstat (limited to 'awt/org/apache/harmony/awt/gl/image/GifDecoder.java')
-rw-r--r--awt/org/apache/harmony/awt/gl/image/GifDecoder.java692
1 files changed, 692 insertions, 0 deletions
diff --git a/awt/org/apache/harmony/awt/gl/image/GifDecoder.java b/awt/org/apache/harmony/awt/gl/image/GifDecoder.java
new file mode 100644
index 0000000..7ecb15b
--- /dev/null
+++ b/awt/org/apache/harmony/awt/gl/image/GifDecoder.java
@@ -0,0 +1,692 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+/**
+ * @author Oleg V. Khaschansky
+ * @version $Revision$
+ */
+/*
+* Created on 27.01.2005
+*/
+package org.apache.harmony.awt.gl.image;
+
+import java.awt.image.ColorModel;
+import java.awt.image.ImageConsumer;
+import java.awt.image.IndexColorModel;
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Hashtable;
+import java.util.List;
+
+public class GifDecoder extends ImageDecoder {
+ // initializes proper field IDs
+ private static native void initIDs();
+
+ static {
+ System.loadLibrary("gl"); //$NON-NLS-1$
+ initIDs();
+ }
+
+ // ImageConsumer hints: common
+ private static final int baseHints =
+ ImageConsumer.SINGLEPASS | ImageConsumer.COMPLETESCANLINES |
+ ImageConsumer.SINGLEFRAME;
+ // ImageConsumer hints: interlaced
+ private static final int interlacedHints =
+ baseHints | ImageConsumer.RANDOMPIXELORDER;
+
+ // Impossible color value - no translucent pixels allowed
+ static final int IMPOSSIBLE_VALUE = 0x0FFFFFFF;
+
+ // I/O buffer
+ private static final int BUFFER_SIZE = 1024;
+ private byte buffer[] = new byte[BUFFER_SIZE];
+
+ GifDataStream gifDataStream = new GifDataStream();
+ GifGraphicBlock currBlock;
+
+ // Pointer to native structure which store decoding state
+ // between subsequent decoding/IO-suspension cycles
+ private long hNativeDecoder; // NULL initially
+
+ // Number of bytes eaten by the native decoder
+ private int bytesConsumed;
+
+ private boolean consumersPrepared;
+ private Hashtable<String, String> properties = new Hashtable<String, String>();
+
+ // Could be set up by java code or native method when
+ // transparent pixel index changes or local color table encountered
+ private boolean forceRGB;
+
+ private byte screenBuffer[];
+ private int screenRGBBuffer[];
+
+ public GifDecoder(DecodingImageSource src, InputStream is) {
+ super(src, is);
+ }
+
+ private static native int[] toRGB(byte imageData[], byte colormap[], int transparentColor);
+
+ private static native void releaseNativeDecoder(long hDecoder);
+
+ private native int decode(
+ byte input[],
+ int bytesInBuffer,
+ long hDecoder,
+ GifDataStream dataStream,
+ GifGraphicBlock currBlock
+ );
+
+ private int[] getScreenRGBBuffer() {
+ if (screenRGBBuffer == null) {
+ if (screenBuffer != null) {
+ int transparentColor =
+ gifDataStream.logicalScreen.globalColorTable.cm.getTransparentPixel();
+ transparentColor = transparentColor > 0 ? transparentColor : IMPOSSIBLE_VALUE;
+ screenRGBBuffer =
+ toRGB(
+ screenBuffer,
+ gifDataStream.logicalScreen.globalColorTable.colors,
+ transparentColor
+ );
+ } else {
+ int size = gifDataStream.logicalScreen.logicalScreenHeight *
+ gifDataStream.logicalScreen.logicalScreenWidth;
+ screenRGBBuffer = new int[size];
+ }
+ }
+
+ return screenRGBBuffer;
+ }
+
+ private void prepareConsumers() {
+ GifLogicalScreen gls = gifDataStream.logicalScreen;
+ setDimensions(gls.logicalScreenWidth,
+ gls.logicalScreenHeight);
+ setProperties(properties);
+
+ currBlock = gifDataStream.graphicBlocks.get(0);
+ if (forceRGB) {
+ setColorModel(ColorModel.getRGBdefault());
+ } else {
+ setColorModel(gls.globalColorTable.getColorModel(currBlock.transparentColor));
+ }
+
+ // Fill screen buffer with the background or transparent color
+ if (forceRGB) {
+ int fillColor = 0xFF000000;
+ if (gls.backgroundColor != IMPOSSIBLE_VALUE) {
+ fillColor = gls.backgroundColor;
+ }
+
+ Arrays.fill(getScreenRGBBuffer(), fillColor);
+ } else {
+ int fillColor = 0;
+
+ if (gls.backgroundColor != IMPOSSIBLE_VALUE) {
+ fillColor = gls.backgroundColor;
+ } else {
+ fillColor = gls.globalColorTable.cm.getTransparentPixel();
+ }
+
+ screenBuffer = new byte[gls.logicalScreenHeight*gls.logicalScreenWidth];
+ Arrays.fill(screenBuffer, (byte) fillColor);
+ }
+
+ setHints(interlacedHints); // XXX - always random pixel order
+ }
+
+ @Override
+ public void decodeImage() throws IOException {
+ try {
+ int bytesRead = 0;
+ int needBytes, offset, bytesInBuffer = 0;
+ boolean eosReached = false;
+ GifGraphicBlock blockToDispose = null;
+
+ // Create new graphic block
+ if (currBlock == null) {
+ currBlock = new GifGraphicBlock();
+ gifDataStream.graphicBlocks.add(currBlock);
+ }
+
+ // Read from the input stream
+ for (;;) {
+ needBytes = BUFFER_SIZE - bytesInBuffer;
+ offset = bytesInBuffer;
+
+ bytesRead = inputStream.read(buffer, offset, needBytes);
+
+ if (bytesRead < 0) {
+ eosReached = true;
+ bytesRead = 0;
+ } // Don't break, maybe something left in buffer
+
+ // Keep track on how much bytes left in buffer
+ bytesInBuffer += bytesRead;
+
+ // Here we pass number of new bytes read from the input stream (bytesRead)
+ // since native decoder uses java buffer and doesn't have its own
+ // buffer. So it adds this number to the number of bytes left
+ // in buffer from the previous call.
+ int numLines = decode(
+ buffer,
+ bytesRead,
+ hNativeDecoder,
+ gifDataStream,
+ currBlock);
+
+ // Keep track on how much bytes left in buffer
+ bytesInBuffer -= bytesConsumed;
+
+ if (
+ !consumersPrepared &&
+ gifDataStream.logicalScreen.completed &&
+ gifDataStream.logicalScreen.globalColorTable.completed &&
+ (currBlock.imageData != null || // Have transparent pixel filled
+ currBlock.rgbImageData != null)
+ ) {
+ prepareConsumers();
+ consumersPrepared = true;
+ }
+
+ if (bytesConsumed < 0) {
+ break; // Error exit
+ }
+
+ if (currBlock != null) {
+ if (numLines != 0) {
+ // Dispose previous image only before showing next
+ if (blockToDispose != null) {
+ blockToDispose.dispose();
+ blockToDispose = null;
+ }
+
+ currBlock.sendNewData(this, numLines);
+ }
+
+ if (currBlock.completed && hNativeDecoder != 0) {
+ blockToDispose = currBlock; // Dispose only before showing new pixels
+ currBlock = new GifGraphicBlock();
+ gifDataStream.graphicBlocks.add(currBlock);
+ }
+ }
+
+ if (hNativeDecoder == 0) {
+ break;
+ }
+
+ if (eosReached && numLines == 0) { // Maybe image is truncated...
+ releaseNativeDecoder(hNativeDecoder);
+ break;
+ }
+ }
+ } finally {
+ closeStream();
+ }
+
+ // Here all animation goes
+ // Repeat image loopCount-1 times or infinitely if loopCount = 0
+ if (gifDataStream.loopCount != 1) {
+ if (currBlock.completed == false) {
+ gifDataStream.graphicBlocks.remove(currBlock);
+ }
+
+ int numFrames = gifDataStream.graphicBlocks.size();
+ // At first last block will be disposed
+ GifGraphicBlock gb =
+ gifDataStream.graphicBlocks.get(numFrames-1);
+
+ ImageLoader.beginAnimation();
+
+ while (gifDataStream.loopCount != 1) {
+ if (gifDataStream.loopCount != 0) {
+ gifDataStream.loopCount--;
+ }
+
+ // Show all frames
+ for (int i=0; i<numFrames; i++) {
+ gb.dispose();
+ gb = gifDataStream.graphicBlocks.get(i);
+
+ // Show one frame
+ if (forceRGB) {
+ setPixels(
+ gb.imageLeft,
+ gb.imageTop,
+ gb.imageWidth,
+ gb.imageHeight,
+ ColorModel.getRGBdefault(),
+ gb.getRgbImageData(),
+ 0,
+ gb.imageWidth
+ );
+ } else {
+ setPixels(
+ gb.imageLeft,
+ gb.imageTop,
+ gb.imageWidth,
+ gb.imageHeight,
+ null,
+ gb.imageData,
+ 0,
+ gb.imageWidth
+ );
+ }
+ }
+ }
+ ImageLoader.endAnimation();
+ }
+
+ imageComplete(ImageConsumer.STATICIMAGEDONE);
+ }
+
+ void setComment(String newComment) {
+ Object currComment = properties.get("comment"); //$NON-NLS-1$
+
+ if (currComment == null) {
+ properties.put("comment", newComment); //$NON-NLS-1$
+ } else {
+ properties.put("comment", (String) currComment + "\n" + newComment); //$NON-NLS-1$ //$NON-NLS-2$
+ }
+
+ setProperties(properties);
+ }
+
+ class GifDataStream {
+ // Indicates that reading of the whole data stream accomplished
+ boolean completed = false;
+
+ // Added to support Netscape 2.0 application
+ // extension block.
+ int loopCount = 1;
+
+ GifLogicalScreen logicalScreen = new GifLogicalScreen();
+ List<GifGraphicBlock> graphicBlocks = new ArrayList<GifGraphicBlock>(10); // Of GifGraphicBlocks
+
+ // Comments from the image
+ String comments[];
+ }
+
+ class GifLogicalScreen {
+ // Indicates that reading of this block accomplished
+ boolean completed = false;
+
+ int logicalScreenWidth;
+ int logicalScreenHeight;
+
+ int backgroundColor = IMPOSSIBLE_VALUE;
+
+ GifColorTable globalColorTable = new GifColorTable();
+ }
+
+ class GifGraphicBlock {
+ // Indicates that reading of this block accomplished
+ boolean completed = false;
+
+ final static int DISPOSAL_NONE = 0;
+ final static int DISPOSAL_NODISPOSAL = 1;
+ final static int DISPOSAL_BACKGROUND = 2;
+ final static int DISPOSAL_RESTORE = 3;
+
+ int disposalMethod;
+ int delayTime; // Multiplied by 10 already
+ int transparentColor = IMPOSSIBLE_VALUE;
+
+ int imageLeft;
+ int imageTop;
+ int imageWidth;
+ int imageHeight;
+
+ // Auxilliary variables to minimize computations
+ int imageRight;
+ int imageBottom;
+
+ boolean interlace;
+
+ // Don't need local color table - if it is specified
+ // image data are converted to RGB in the native code
+
+ byte imageData[] = null;
+ int rgbImageData[] = null;
+
+ private int currY = 0; // Current output scanline
+
+ int[] getRgbImageData() {
+ if (rgbImageData == null) {
+ rgbImageData =
+ toRGB(
+ imageData,
+ gifDataStream.logicalScreen.globalColorTable.colors,
+ transparentColor
+ );
+ if (transparentColor != IMPOSSIBLE_VALUE) {
+ transparentColor =
+ gifDataStream.logicalScreen.globalColorTable.cm.getRGB(transparentColor);
+ transparentColor &= 0x00FFFFFF;
+ }
+ }
+ return rgbImageData;
+ }
+
+ private void replaceTransparentPixels(int numLines) {
+ List<GifGraphicBlock> graphicBlocks = gifDataStream.graphicBlocks;
+ int prevBlockIndex = graphicBlocks.indexOf(this) - 1;
+
+ if (prevBlockIndex >= 0) {
+ int maxY = currY + numLines + imageTop;
+ int offset = currY * imageWidth;
+
+ // Update right and bottom coordinates
+ imageRight = imageLeft + imageWidth;
+ imageBottom = imageTop + imageHeight;
+
+ int globalWidth = gifDataStream.logicalScreen.logicalScreenWidth;
+ int pixelValue, imageOffset;
+ int rgbData[] = forceRGB ? getRgbImageData() : null;
+
+ for (int y = currY + imageTop; y < maxY; y++) {
+ imageOffset = globalWidth * y + imageLeft;
+ for (int x = imageLeft; x < imageRight; x++) {
+ pixelValue = forceRGB ?
+ rgbData[offset] :
+ imageData[offset] & 0xFF;
+ if (pixelValue == transparentColor) {
+ if (forceRGB) {
+ pixelValue = getScreenRGBBuffer() [imageOffset];
+ rgbData[offset] = pixelValue;
+ } else {
+ pixelValue = screenBuffer [imageOffset];
+ imageData[offset] = (byte) pixelValue;
+ }
+ }
+ offset++;
+ imageOffset++;
+ } // for
+ } // for
+
+ } // if (prevBlockIndex >= 0)
+ }
+
+ public void sendNewData(GifDecoder decoder, int numLines) {
+ // Get values for transparent pixels
+ // from the perevious frames
+ if (transparentColor != IMPOSSIBLE_VALUE) {
+ replaceTransparentPixels(numLines);
+ }
+
+ if (forceRGB) {
+ decoder.setPixels(
+ imageLeft,
+ imageTop + currY,
+ imageWidth,
+ numLines,
+ ColorModel.getRGBdefault(),
+ getRgbImageData(),
+ currY*imageWidth,
+ imageWidth
+ );
+ } else {
+ decoder.setPixels(
+ imageLeft,
+ imageTop + currY,
+ imageWidth,
+ numLines,
+ null,
+ imageData,
+ currY*imageWidth,
+ imageWidth
+ );
+ }
+
+ currY += numLines;
+ }
+
+ public void dispose() {
+ imageComplete(ImageConsumer.SINGLEFRAMEDONE);
+
+ // Show current frame until delayInterval will not elapse
+ if (delayTime > 0) {
+ try {
+ Thread.sleep(delayTime);
+ } catch (InterruptedException e) {
+ e.printStackTrace();
+ }
+ } else {
+ Thread.yield(); // Allow consumers to consume data
+ }
+
+ // Don't dispose if image is outside of the visible area
+ if (imageLeft > gifDataStream.logicalScreen.logicalScreenWidth ||
+ imageTop > gifDataStream.logicalScreen.logicalScreenHeight) {
+ disposalMethod = DISPOSAL_NONE;
+ }
+
+ switch(disposalMethod) {
+ case DISPOSAL_BACKGROUND: {
+ if (forceRGB) {
+ getRgbImageData(); // Ensure that transparentColor is RGB, not index
+
+ int data[] = new int[imageWidth*imageHeight];
+
+ // Compatibility: Fill with transparent color if we have one
+ if (transparentColor != IMPOSSIBLE_VALUE) {
+ Arrays.fill(
+ data,
+ transparentColor
+ );
+ } else {
+ Arrays.fill(
+ data,
+ gifDataStream.logicalScreen.backgroundColor
+ );
+ }
+
+ setPixels(
+ imageLeft,
+ imageTop,
+ imageWidth,
+ imageHeight,
+ ColorModel.getRGBdefault(),
+ data,
+ 0,
+ imageWidth
+ );
+
+ sendToScreenBuffer(data);
+ } else {
+ byte data[] = new byte[imageWidth*imageHeight];
+
+ // Compatibility: Fill with transparent color if we have one
+ if (transparentColor != IMPOSSIBLE_VALUE) {
+ Arrays.fill(
+ data,
+ (byte) transparentColor
+ );
+ } else {
+ Arrays.fill(
+ data,
+ (byte) gifDataStream.logicalScreen.backgroundColor
+ );
+ }
+
+ setPixels(
+ imageLeft,
+ imageTop,
+ imageWidth,
+ imageHeight,
+ null,
+ data,
+ 0,
+ imageWidth
+ );
+
+ sendToScreenBuffer(data);
+ }
+ break;
+ }
+ case DISPOSAL_RESTORE: {
+ screenBufferToScreen();
+ break;
+ }
+ case DISPOSAL_NONE:
+ case DISPOSAL_NODISPOSAL:
+ default: {
+ // Copy transmitted data to the screen buffer
+ Object data = forceRGB ? (Object) getRgbImageData() : imageData;
+ sendToScreenBuffer(data);
+ break;
+ }
+ }
+ }
+
+ private void sendToScreenBuffer(Object data) {
+ int dataInt[];
+ byte dataByte[];
+
+ int width = gifDataStream.logicalScreen.logicalScreenWidth;
+
+
+ if (forceRGB) {
+ dataInt = (int[]) data;
+
+ if (imageWidth == width) {
+ System.arraycopy(dataInt,
+ 0,
+ getScreenRGBBuffer(),
+ imageLeft + imageTop*width,
+ dataInt.length
+ );
+ } else { // Each scanline
+ copyScanlines(dataInt, getScreenRGBBuffer(), width);
+ }
+ } else {
+ dataByte = (byte[]) data;
+
+ if (imageWidth == width) {
+ System.arraycopy(dataByte,
+ 0,
+ screenBuffer,
+ imageLeft + imageTop*width,
+ dataByte.length
+ );
+ } else { // Each scanline
+ copyScanlines(dataByte, screenBuffer, width);
+ }
+ }
+ } // sendToScreenBuffer
+
+ private void copyScanlines(Object src, Object dst, int width) {
+ for (int i=0; i<imageHeight; i++) {
+ System.arraycopy(src,
+ i*imageWidth,
+ dst,
+ imageLeft + i*width + imageTop*width,
+ imageWidth
+ );
+ } // for
+ }
+
+ private void screenBufferToScreen() {
+ int width = gifDataStream.logicalScreen.logicalScreenWidth;
+
+ Object dst = forceRGB ?
+ (Object) new int[imageWidth*imageHeight] :
+ new byte[imageWidth*imageHeight];
+
+ Object src = forceRGB ?
+ getScreenRGBBuffer() :
+ (Object) screenBuffer;
+
+ int offset = 0;
+ Object toSend;
+
+ if (width == imageWidth) {
+ offset = imageWidth * imageTop;
+ toSend = src;
+ } else {
+ for (int i=0; i<imageHeight; i++) {
+ System.arraycopy(src,
+ imageLeft + i*width + imageTop*width,
+ dst,
+ i*imageWidth,
+ imageWidth
+ );
+ } // for
+ toSend = dst;
+ }
+
+ if (forceRGB) {
+ setPixels(
+ imageLeft,
+ imageTop,
+ imageWidth,
+ imageHeight,
+ ColorModel.getRGBdefault(),
+ (int [])toSend,
+ offset,
+ imageWidth
+ );
+ } else {
+ setPixels(
+ imageLeft,
+ imageTop,
+ imageWidth,
+ imageHeight,
+ null,
+ (byte [])toSend,
+ offset,
+ imageWidth
+ );
+ }
+ }
+ }
+
+ class GifColorTable {
+ // Indicates that reading of this block accomplished
+ boolean completed = false;
+
+ IndexColorModel cm = null;
+ int size = 0; // Actual number of colors in the color table
+ byte colors[] = new byte[256*3];
+
+ IndexColorModel getColorModel(int transparentColor) {
+ if (cm != null) {
+ if (transparentColor != cm.getTransparentPixel()) {
+ return cm = null; // Force default ARGB color model
+ }
+ return cm;
+ } else
+ if (completed && size > 0) {
+ if (transparentColor == IMPOSSIBLE_VALUE) {
+ return cm =
+ new IndexColorModel(8, size, colors, 0, false);
+ }
+
+ if (transparentColor > size) {
+ size = transparentColor + 1;
+ }
+ return cm =
+ new IndexColorModel(8, size, colors, 0, false, transparentColor);
+ }
+
+ return cm = null; // Force default ARGB color model
+ }
+ }
+}