/* * 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 } } }