diff options
Diffstat (limited to 'awt/org/apache/harmony/awt/gl/image/GifDecoder.java')
-rw-r--r-- | awt/org/apache/harmony/awt/gl/image/GifDecoder.java | 692 |
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 + } + } +} |