All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.apache.myfaces.trinidadinternal.image.encode.GifEncoder Maven / Gradle / Ivy

The newest version!
/*
 *  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.
 */
package org.apache.myfaces.trinidadinternal.image.encode;

import java.awt.Image;
import java.awt.image.ImageObserver;
import java.awt.image.PixelGrabber;
import java.io.OutputStream;
import java.io.IOException;
import java.io.InterruptedIOException;
import java.util.Hashtable;

import org.apache.myfaces.trinidadinternal.image.painter.ImageLoader;
import org.apache.myfaces.trinidad.logging.TrinidadLogger;

/**
 * Generates a Gif89a graphics file given pixel data.
 * 

* @version $Name: $ ($Revision: adfrt/faces/adf-faces-impl/src/main/java/oracle/adfinternal/view/faces/image/encode/GifEncoder.java#0 $) $Date: 10-nov-2005.19:05:17 $ * @since 0.1.4 */ final class GifEncoder { /** * Generate a gif to the stream. The file consists of an identification * block, a color table, a graphics control extension which enables * transparency, and the image data. * @param image The image, in pixel form. * @param stream The output stream that the gif is written to. * @return true if the encoding was a success. */ public static void encode(Image image, OutputStream stream) throws IOException { // first retrieve the pixels ImageLoader il = new ImageLoader(image); il.start(); if(!il.waitFor()){ throw new IllegalArgumentException(_LOG.getMessage( "PROBLEM_LOADING")); } int width = image.getWidth(il); int height = image.getHeight(il); int[] pixels = new int[width*height]; // all the image's pixels PixelGrabber grabber = new PixelGrabber(image.getSource(), 0, 0, width, height, pixels, 0, width); try // get the pixels { grabber.grabPixels(); } catch (InterruptedException e) { throw new InterruptedIOException(_LOG.getMessage( "GRABBING_PIXELS")); } if ((grabber.getStatus() & ImageObserver.ABORT) != 0) { throw new IllegalArgumentException(_LOG.getMessage( "ERROR_FETCHING_IMAGE", new Object[]{pixels.length,width,height})); } // Read the pixels to determine the color table byte[] globalColorTable = new byte[_MAXIMUM_COLOR_TABLE_SIZE*3]; // A hashtable is used to keep track of colors already in the table // max. one-color entries // -= Simon Lessard =- // FIXME: JDK 1.2 was truly evil... // HashMap would be better Hashtable hsh = new Hashtable(_MAXIMUM_COLOR_TABLE_SIZE); int colorIndex = 0; // the code values of the colors int background = 0; // the code of the background color int lastColor =_NO_COLOR; // will be the most recent color (for speed) int lastColorIndex = 0; // The most recent color index boolean transparency = false; // Do we have any transparent pixels? // the color table is constructed by scanning the image and creating a // numerical value for each unique color. The hashtable keeps track of // colors that have already been seen, and the rgb pixel data is replaced // by the new numerical values of these colors. for (int i = 0; i < pixels.length; i++) { // Get the current color value int color = pixels[i]; if (lastColor == color) { // If we've have just seen this color, we can just short-circuit // right here, since we already know the color index. pixels[i] = lastColorIndex; } else if (_isTransparent(color)) { // Transparent colors don't get added to the color table yet. // Mark the pixel as transparent. pixels[i] = _TRANSPARENT_COLOR; // If the color is fully transparent, make it the background if (_isFullyTransparent(color)) background = colorIndex; transparency = true; } else { // For non-transparent pixels, we first check to see if the // color has already been added to the color table. We use an // the Integer RGB value as our hash key Integer colorKey = (color & 0x00ffffff); Integer colorIndexValue = hsh.get(colorKey); if (colorIndexValue != null) { // We've already got an index for this color. Convert the pixel // value to the color index. lastColorIndex = colorIndexValue.intValue(); } else { // We've got a new color! Make sure we've got room for it if (colorIndex >= _MAXIMUM_COLOR_TABLE_SIZE) throw new IllegalArgumentException(_LOG.getMessage( "EXCEEDED_GIF_COLOR_LIMIT")); // Store the new color in the color table int off = 3 * colorIndex; // offset for table globalColorTable[off] = _getRed(color); globalColorTable[off+1] = _getGreen(color); globalColorTable[off+2] = _getBlue(color); // Hash the color->index mapping for fast lookups hsh.put(colorKey, colorIndex); // and Update the index count lastColorIndex = colorIndex++; } // Update the last seen color lastColor = color; // Convert the pixel value to the color index pixels[i] = lastColorIndex; } } // add an alpha color // the transparent color must be a color not otherwise found in the // image. For history's sake we start at r=255, g=0, b=255, a particularly gaudy shade // of pink, and increase color vals by 1 until we find a color not // found in the color table. Usually we don't have to go beyond the first color. int transparentIndex=0; // the code of transparent if (transparency) { if (colorIndex >= _MAXIMUM_COLOR_TABLE_SIZE) { throw new IllegalArgumentException(_LOG.getMessage( "NO_SPACE_LEFT_FOR_TRANSPARENCY")); } else { // start at the pink historically used for transparency for (int i = 0x00ff00ff; i < 0x00ffffff; i++) { int col = i; Integer icol = col; if (!hsh.containsKey(icol)) { // add entry to table int off = 3*colorIndex; // offset for // table transparentIndex=(colorIndex)+2; // add id of // transparent color // +2 because of Float storage scheme - see explanation // under "The LZW algorithm" globalColorTable[off] = _getRed(col); globalColorTable[off+1] = _getGreen(col); globalColorTable[off+2] = _getBlue(col); break; } } } } int codeSize = getLog(colorIndex); // extra zeroes at end of globalColorTable if necessary int globalColorTableSize = 3*(1<>> 8); blocks[2] = (byte)height; blocks[3] = (byte)(height >>> 8); blocks[4] = (byte)((byte)128 | (byte)((codeSize-1)<<4) | (byte)(codeSize-1)); blocks[5] = (byte)background; // initial value is 6 // blocks[6] = 0; stream.write(blocks, 0, 7); // logical screen descriptor // combo graphics control extension & local image description blocks[13]=blocks[0]; // get width and height from lsd blocks[14]=blocks[1]; blocks[15]=blocks[2]; blocks[16]=blocks[3]; blocks[0]= (byte)0x21; // identification stuff blocks[1]= (transparency ? (byte)0xf9 : (byte)0xfe); blocks[2]= (byte)0x04; blocks[3]= (byte)0x01; // packed byte - transparent flag blocks[4]= (transparency ? (byte)0 : (byte)0x4a); blocks[5]= (transparency ? (byte)0 : (byte)0x44); // -2 compensates for +2 applied to all color values, needed for // Float storage scenario. See "the LZW algorithm" below blocks[6]= (transparency ? (byte)(transparentIndex-2) : (byte)0x4c); blocks[8] = (byte)0x2c; // these values are already 0 // blocks[7]=blocks[9]=blocks[10]=blocks[11]=blocks[12]=blocks[17]=0; // xpos and ypos both 0 // global color table stream.write(globalColorTable, 0, globalColorTableSize); stream.write(blocks); // graphic control extension // due to algorithmic constraints, codeSize must be one larger // for two-color algorithms. //jm if (codeSize < 2) codeSize = 2; stream.write((byte)codeSize); // initial compression size-1 = codeSize // the LZW algorithm // we can store any string of characters and next character as // a pair of codes, (i, j) since for every current // sqnc_newcol, sqnc has a valid entry in the table. The pair // structure I use is a Float. To ensure code 10 and code 100 // store differently, the inverse of j is stored instead. And // to prevent div by 0 and an inverse >= 1, all numbers are // bumped up 2. There's no concatenation here, so things are // sped up considerably. // Actually, sequences are now stored in Integers instead of Floats, // to avoid loss of precision problems encountered with the decimal // portion of the Float. // Formerly, the hashtable held (Float, Integer) pairs, where the // Float is in the form a.b where a = sqnc, and b = 1/(newcol+2). // Now, we use a Integer where the top 16 bits store the sequence and // the bottom 16 bits store the newcol. // -= Simon Lessard =- // FIXME: Another line of code, another Hashtable, // Yet again HashMap would be more efficient hsh = new Hashtable(_LARGEST_CODE); // max. compression entries int code = (1<< codeSize)+2; // where code values start int clearCode = (code++)-2; // special codes int endOfInformation = (code++)-2; int sqnc = 0; // the prefix string of colors int newcol = 0; // the new color int sqnc_newcol = 0; // concatenation of the above Integer fsqnc_newcol = null; // these variables are mostly manipulated by _writeByte Info info = new Info(); info.compressionSize = codeSize+1; // compressionSize info.theByte = (byte)0; // theByte info.bitsLeft = 8; // bitsLeft info.blockOffset = 1; // blockOffset info.byteData = new byte[_MAXIMUM_COLOR_TABLE_SIZE]; // size of byteData info.byteData[0] = (byte)_BLOCK_SIZE; // size byte double infoCompSizeExp = 1<< info.compressionSize; // clear code starts the data stream _writeByte(stream, clearCode, info); for (int i = 0; i < pixels.length; i++) { final int pixel = pixels[i]; newcol = (pixel >= 0) ? pixel+2 : transparentIndex; // sub in transparent for -1 assert (sqnc <= 0xffff); assert (newcol <= 0xffff); sqnc_newcol = (((0xffff0000) & (sqnc << 16)) | ((0x0000ffff) & newcol)); if (sqnc > 0) { fsqnc_newcol = sqnc_newcol; Integer sqnc_newcol_code = hsh.get(fsqnc_newcol); if (sqnc_newcol_code == null) { // string not in table. // write prefix and add string to table _writeByte(stream, sqnc-2, info); hsh.put(fsqnc_newcol, code++); if ((code-2) > infoCompSizeExp) { // increase code length infoCompSizeExp = 1 << ++info.compressionSize; } // check for rehash time. If it is, send the // clear_code signal, // clear hashtable of everything but initialization // stuff, reset code, and continue. if ((code-2) == _LARGEST_CODE) { _writeByte(stream, clearCode, info); //hsh = new Hashtable(_LARGEST_CODE); hsh.clear(); // +2 for float offset scheme // +2 for clear code, eoi. = +4 code = (1 << codeSize)+4; info.compressionSize=codeSize+1; infoCompSizeExp = 1<< info.compressionSize; } sqnc = newcol; // slide everything over } else { // if the float is there, find its integer equivalent sqnc = sqnc_newcol_code.intValue(); } } else sqnc = newcol; } // last bit of sqnc must be written. _writeByte(stream, sqnc-2, info); // end the stream _writeByte(stream, endOfInformation, info); // clean up last block, byte, etc. if (info.bitsLeft < 8) { info.byteData[info.blockOffset++] = info.theByte; info.theByte = 0; info.bitsLeft = 8; } // change size of last block if we didn't just complete one. // if we did, this won't change the value info.byteData[0] = (byte)(info.blockOffset-1); // add 0 byte info.byteData[info.blockOffset++] = (byte)0; stream.write(info.byteData, 0, info.blockOffset); // image data stream.write(0x3b); // terminal byte } // to avoid unnecessary object creator private GifEncoder(){ } // packs bits into bytes and bytes into blocks. Writes when // appropriate. private static void _writeByte(OutputStream stream, int data, Info info) throws IOException { int offset = info.blockOffset; byte b = info.theByte; int bitsLeft = info.bitsLeft; int size = info.compressionSize; // remaining size of data while(size >0) { // bits from data moving onto b int numBits = (bitsLeft < size) ? bitsLeft : size; // take new_bits off data. byte new_bits = (byte)(data & ((1<>>= numBits; // smash new_bits taken off of data new_bits <<= (8-bitsLeft); // move it into position b |= new_bits; // push them together bitsLeft -= numBits; // spots are now taken size -= numBits; // fewer remain // might be done with b. if (bitsLeft == 0) { info.byteData[offset++] = b; b = 0; bitsLeft = 8; // might be done with block if (offset > _BLOCK_SIZE) { stream.write(info.byteData, 0, offset); // image data //info.byteData = new byte[_MAXIMUM_COLOR_TABLE_SIZE]; info.byteData[0] = (byte)_BLOCK_SIZE; offset = 1; } } } info.blockOffset = offset; info.bitsLeft = bitsLeft; info.theByte = b; } // convenience methods for getting red, green, blue elements from an int private static byte _getRed(int c) { return (byte)((c>>16)&255); } private static byte _getGreen(int c) { return (byte)((c>>8)&255); } private static byte _getBlue(int c) { return (byte)(c&255); } // Tests whether the color is transparent private static boolean _isTransparent(int c) { // We consider a color to be transparent if the alpha // value is lower than a predetermined threshold. return (((c >> 24) & 0x000000ff) < _TRANSPARENCY_THRESHHOLD); } // Tests whether the color is 100% transparent (alpha is zero) private static boolean _isFullyTransparent(int c) { return ((c & 0xff000000) == 0); } // convenience method to avoid computing log private static int getLog(int n) { int i = 0; while (n != 0) { n >>=1; i++; } return i; } // largest code value, according to gif spec private static final int _LARGEST_CODE = 4096; private static final int _BLOCK_SIZE = 254; private static final int _MAXIMUM_COLOR_TABLE_SIZE = 256; private static final int _NO_COLOR = -2; private static final int _TRANSPARENT_COLOR = -1; private static final byte[] _HEADER = "GIF89a".getBytes(); // under this alpha value, pixels are taken to be transparent private static final int _TRANSPARENCY_THRESHHOLD = 1; // fields used in writing bytes private static class Info { public int bitsLeft; public int compressionSize; public int blockOffset; public byte theByte; public byte[] byteData; } private static final TrinidadLogger _LOG = TrinidadLogger.createTrinidadLogger( GifEncoder.class); }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy