Author: orbiter Date: 2008-02-16 11:40:00 +0100 (Sat, 16 Feb 2008) New Revision: 4485
Added: trunk/htroot/yacy/user/images/basket_raw.png trunk/htroot/yacy/user/images/basket_selected.png trunk/source/de/anomic/ymage/AnimGifEncoder.java Log: added basket icons and experimental gif animation class Added: trunk/htroot/yacy/user/images/basket_raw.png =================================================================== (Binary files differ) Property changes on: trunk/htroot/yacy/user/images/basket_raw.png ___________________________________________________________________ Name: svn:mime-type + application/octet-stream Added: trunk/htroot/yacy/user/images/basket_selected.png =================================================================== (Binary files differ) Property changes on: trunk/htroot/yacy/user/images/basket_selected.png ___________________________________________________________________ Name: svn:mime-type + application/octet-stream Added: trunk/source/de/anomic/ymage/AnimGifEncoder.java =================================================================== --- trunk/source/de/anomic/ymage/AnimGifEncoder.java 2008-02-16 10:36:10 UTC (rev 4484) +++ trunk/source/de/anomic/ymage/AnimGifEncoder.java 2008-02-16 10:40:00 UTC (rev 4485) @@ -0,0 +1,1187 @@ +/* + * (C)2000 by F. Jalvingh, Mumble Internet Services + * For questions and the like: [EMAIL PROTECTED] + * + * Compression part (C)1996,1998 by Jef Poskanzer <[EMAIL PROTECTED]>. All rights reserved. + * + * This software is placed in the public domain. You are free to use this + * software for any means while respecting the above copyright. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * Optimizations by Jal: + * --------------------- + * Initial: Coded RLE code for building the 8-bit color table. + * 6dec00: Changed code to remove extraneous if's and unrolled some calls. + * Replaced color hashtable with local specialized variant. + * 7dec00: Made specialized direct buffer access versions for BufferedImage + * images.. + * + */ + +// very slightly adopted by Michael Christen, 12.12.2007 +// - removed unused variables +// - replaced old java classes by new one + +package de.anomic.ymage; + +import java.awt.Canvas; +import java.awt.Image; +import java.awt.MediaTracker; +import java.awt.image.BufferedImage; +import java.awt.image.ColorModel; +import java.awt.image.DataBuffer; +import java.awt.image.DataBufferByte; +import java.awt.image.DataBufferInt; +import java.awt.image.DataBufferShort; +import java.awt.image.IndexColorModel; +import java.awt.image.PixelGrabber; +import java.awt.image.PixelInterleavedSampleModel; +import java.awt.image.Raster; +import java.awt.image.SampleModel; +import java.awt.image.SinglePixelPackedSampleModel; +import java.io.File; +import java.io.FileNotFoundException; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.OutputStream; +import java.util.ArrayList; + +/** + * <p>This class can be used to write an animated GIF file by combining several + * images. It is loosely based on the Acme GIF encoder.</p> + * + * <p>The characteristics of the generated Gif 89a image are: + * <ul> + * <li>Only a single color table is used (no local tables). This table is + * created by combining the colors from all other images.</li> + * </ul> + * </p> + * + * @author F. Jalvingh + */ +public class AnimGifEncoder { + /** The default interlacing indicator */ + private boolean m_default_interlace = false; + + /** The default delay time, */ + private int m_default_delay = 100; + + /** Set when looping the set is requested. */ + private boolean m_loop = true; + + /** The outputstream to write the image to. */ + private OutputStream m_os; + + /** The (current) list of images to embed in the GIF */ + private ArrayList<AnIma> m_ima_ar; + + /** The total width and height of all combined images */ + private int m_w, m_h; + + /** The canvas is used to proprly track images. */ + private Canvas m_cv; + + /** The index for the "transparant" color. -1 if no transparant found. */ + private short m_transparant_ix = -1; + + /** The index (palette table entry #) to use for the NEXT color encountered */ + private short m_color_ix; + + /** The #of bits to use (2log m_color_ix). */ + private int m_color_bits; + + /// Temp optimization inhibition. + public boolean m_no_opt; + + /** + * This constructor creates an empty default codec. + */ + public AnimGifEncoder(OutputStream os) { + m_os = os; + } + + /** + * Creates a codec and specify interlace (not implemented yet). + */ + public AnimGifEncoder(OutputStream os, boolean interlace) { + m_os = os; + m_default_interlace = interlace; + } + + /** + * <p>For animated GIF's the default is to LOOP all images in the GIF file. + * This means that after displaying all images in the file the first image + * is redisplayed ad infinitum.</p> + * <p>To prevent the images from looping call setLoop(false) before calling + * the encode() method. + * </p> + * <p>The current version does not allow the number of repetitions to be + * specified. + * </p> + */ + public void setLoop(boolean loop) { + m_loop = loop; + } + + /** + * Releases ALL cached resources. + */ + public void flush() { + //-- 1. The basic stuff + m_ccolor_ar = null; + m_cindex_ar = null; + m_cv = null; + m_ima_ar = null; + + //-- 2. The compressor. + m_curr_pixels = null; + htab = null; + codetab = null; + accum = null; + } + + /*------------------------------------------------------------------*/ + /* CODING: Adding images to combine into the animated GIF... */ + /*------------------------------------------------------------------*/ + /** + * Adds the specified image to the list of images. While adding, the + * image is converted to pixels; each color is added to the color table + * and the resulting 8-bit pixelset is saved. After this call the image + * is released, and only the pixelset remains until the encode call is + * made. Calling encode will release the pixelset. + */ + public void add(Image ima, int delaytime, boolean interlace, int px, int py) throws IOException { + AnIma ai = new AnIma(); + ai.m_delay = delaytime; + ai.m_interlace = interlace; + ai.m_x = px; + ai.m_y = py; + + //-- Add to the list of images to embed, + if(m_ima_ar == null) // First call? + { + m_ccolor_ar = new int[CHSIZE]; // New colors code table + m_cindex_ar = new short[CHSIZE]; + m_ima_ar = new ArrayList<AnIma>(10); // Contains all component images, + m_cv = new Canvas(); + } + m_ima_ar.add(ai); + + //-- Pre-scan the image!! + if(! m_no_opt) + preCode(ai, ima); // Convert to 8bit and make palette + else + precodeImage(ai, ima); + } + + /** + * Adds the specified image to the list of images. + */ + public void add(Image ima) throws IOException { + add(ima, m_ima_ar == null ? 0 : m_default_delay, m_default_interlace, 0, 0); + } + + /** + * Adds the specified image to the list of images. + */ + public void add(Image ima, int delay) throws IOException { + add(ima, delay, m_default_interlace, 0, 0); + } + + /*------------------------------------------------------------------*/ + /* CODING: I/O to the file - helpers... */ + /*------------------------------------------------------------------*/ + /** + * Writes a string as a #of bytes to the output stream. + */ + private void utStr(String str) throws IOException { + byte[] buf = str.getBytes(); + m_os.write( buf ); + } + + private void utWord(int val) throws IOException { + utByte( (byte) ( val & 0xff)); + utByte( (byte) (( val >> 8 ) & 0xff )); + } + + private void utByte(byte b) throws IOException { + m_os.write( b ); + } + + /*------------------------------------------------------------------*/ + /* CODING: Starting the encode process... */ + /*------------------------------------------------------------------*/ + /** + * Creates the GIF file from all images added to the encoder. + */ + public void encode() throws IOException { + //-- Check validity, + if(m_ima_ar == null || m_ima_ar.size() == 0) + throw new IOException("No images added."); + + //-- Init the compressor's tables + htab = new int[HSIZE]; + codetab = new int[HSIZE]; + accum = new byte[256]; + + //-- Write the GIF header now, + genHeader(); + + /* + * Traverse the data for each image. This determines the actual color + * table and the complete output size. + */ + for (int i = 0; i < m_ima_ar.size(); i++) { + AnIma ai = (AnIma) m_ima_ar.get(i); + genImage(ai); + ai.m_rgb = null; + } + genTrailer(); + flush(); + } + + /*--------------------------------------------------------------*/ + /* CODING: Color table code & specialized color hashtable. */ + /*--------------------------------------------------------------*/ + /* + * This is a hashtable mapping (int, byte). The first int is the actual + * color as gotten from the image. The byte is the index color in the + * colormap for the entry. + * We need to find (byte) by indexing with (int) VERY quicky. + * Furthermore we already know that the table will at max hold 256 entries. + * + * Since all colors >= 0 are transparant, we use (int) = 0 as the empty + * case. + * + * This hashtable uses the same hash mechanism as the LZH compressor: a + * double hash without chaining. + */ + static private final int CHSIZE = 1023; + + /// The color hashtable's COLOR table (int rcolors) + private int[] m_ccolor_ar; + + /// The color hashtable's INDEX table (byte index) + private short[] m_cindex_ar; + + /** + * This retrieves the index for a color code from the color hash. If the + * color doesn't exist it is added to the hash table. This uses the double + * hash mechanism described above. If this call causes >255 colors to be + * stored it throws a too many colors exception. + * The function returns the index code for the color. + */ + private short findColorIndex(int color) throws IOException { + //-- 1. Primary hash.. + int i = (color & 0x7fffffff) % CHSIZE; + + if(m_ccolor_ar[i] == color) // Bucket found? + return m_cindex_ar[i]; + + //-- 2. No match. If the bucket is not empty do the 2nd hash, + if(m_ccolor_ar[i] != 0) // Bucket is full? + { + //-- This was a clash. Locate a new bucket & look for another match! + int disp = CHSIZE - i; + do + { + i -= disp; + if(i < 0) i += CHSIZE; + if(m_ccolor_ar[i] == color) // Found in 2nd hash? + return m_cindex_ar[i]; // Then return it. + } while(m_ccolor_ar[i] != 0); // Loop till empty bucket. + } + + //-- 3. Empty bucket found: add this there as a new index. + if(m_color_ix >= 256) + throw new IOException("More than 255 colors in this GIF are not allowed."); + m_ccolor_ar[i] = color; + m_cindex_ar[i] = (short) m_color_ix; + return m_color_ix++; + } + + /*--------------------------------------------------------------*/ + /* CODING: Optimized pixel grabbers... */ + /*--------------------------------------------------------------*/ + /** + * Checks if the image lies in the current complete image, else it extends + * the source image. + */ + private void checkTotalSize(AnIma ai) { + int t; + + t = ai.m_w + ai.m_x; // Get end-X of image, + if(t > m_w) m_w = t; // Adjust complete GIF's size + t = ai.m_h + ai.m_y; // Get total height + if(t > m_h) m_h = t; // Adjust if higher, + } + + /*--------------------------------------------------------------*/ + /* CODING: The precoder translates all to 8bit indexed... */ + /*--------------------------------------------------------------*/ + /** + * Traverse this image, and determine it's characteristics. It adds all + * used colors to the color table and determines the completed size of + * the thing. The image is converted to an 8-bit pixelmap where each pixel + * indexes the generated color table. + * This function tries to get the fastest access to the pixel data for + * several types of BufferedImage. This should enhance the encoding speed + * by preventing the loop thru the entire generalized Raster and ColorModel + * method.... + * All precode methods build a color table containing all colors used in + * the image, and an 8-bit "image" containing, for each pixel, the index + * into that color table. They also set the transparant color to use. + */ + private void preCode(AnIma ai, Image ima) throws IOException { + //-- Call the appropriate encoder depending on the image type. + if(ima instanceof BufferedImage) + precodeBuffered(ai, (BufferedImage) ima); + else + precodeImage(ai, ima); + } + + /** + * Tries to decode a buffered image in an optimal way. It checks to see + * if it knows the BufferedImage type and calls the appropriate quick + * decoder. If the image is not implemented we fall back to the generic + * method. + */ + private void precodeBuffered(AnIma ai, BufferedImage bi) throws IOException { + //-- 1. Handle all shared tasks... + ai.m_w = bi.getWidth(); + ai.m_h = bi.getHeight(); + if(ai.m_h == 0 || ai.m_w == 0) return; + checkTotalSize(ai); + + //-- 2. Optimize for known types... + boolean done= false; + int bt = bi.getType(); + switch(bt) + { + case BufferedImage.TYPE_BYTE_INDEXED: done = precodeByteIndexed(ai, bi); break; + case BufferedImage.TYPE_INT_BGR: done = precodeIntPacked(ai, bi); break; + case BufferedImage.TYPE_INT_ARGB: done = precodeIntPacked(ai, bi); break; + case BufferedImage.TYPE_USHORT_555_RGB: done = precodeShortPacked(ai, bi); break; + case BufferedImage.TYPE_USHORT_565_RGB: done = precodeShortPacked(ai, bi); break; + case BufferedImage.TYPE_INT_RGB: done = precodeIntPacked(ai, bi); break; + } + + if(done) return; + + precodeImage(ai, bi); + } + + private int getBiOffset(Raster ras, PixelInterleavedSampleModel sm, int x, int y) { + return (y-ras.getSampleModelTranslateY()) * sm.getScanlineStride() + x-ras.getSampleModelTranslateX(); + } + + private int getBiOffset(Raster ras, SinglePixelPackedSampleModel sm, int x, int y) { + return (y-ras.getSampleModelTranslateY()) * sm.getScanlineStride() + x-ras.getSampleModelTranslateX(); + } + + /*--------------------------------------------------------------*/ + /* CODING: BufferedImage.TYPE_BYTE_INDEXED.. */ + /*--------------------------------------------------------------*/ + /** + * Encodes TYPE_BYTE_INDEXED images. + */ + private boolean precodeByteIndexed(AnIma ai, BufferedImage bi) throws IOException { + //-- Get the colormodel, the raster, the databuffer and the samplemodel + ColorModel tcm = bi.getColorModel(); + if(! (tcm instanceof IndexColorModel)) return false; + IndexColorModel cm = (IndexColorModel) tcm; + + Raster ras = bi.getRaster(); + SampleModel tsm = ras.getSampleModel(); + if(! (tsm instanceof PixelInterleavedSampleModel)) return false; + PixelInterleavedSampleModel sm = (PixelInterleavedSampleModel) tsm; + + DataBuffer dbt = ras.getDataBuffer(); + if(dbt.getDataType() != DataBuffer.TYPE_BYTE) return false; + if(dbt.getNumBanks() != 1) return false; + DataBufferByte db = (DataBufferByte) dbt; + + //-- Prepare the color mapping + short[] map = new short[256]; // Alternate lookup table + for(int i = 0; i < 256; i++) // Set all entries to unused, + map[i] = -1; + + /* + * Prepare the run: get all constants e.a. The mechanism runs thru + * all pixels by traversing each X scanline, then moving to the next + * one. One fun thing: we only have to COPY all pixels, since we're + * already byte-packed. + */ + int endoff = ai.m_w * ai.m_h; // Output image size, + byte[] par = new byte[endoff]; // Byte-indexed output array, + int doff = 0; // Destination offset, + + //-- source + int soff = getBiOffset(ras, sm, 0, 0); + byte[] px = db.getData(0); // Get the pixelset, + int esoff = getBiOffset(ras, sm, ai.m_w-1, ai.m_h-1); // calc end offset, + int iw = sm.getScanlineStride(); // Increment width = databuf's width + + while(soff < esoff) { // For all scan lines, + + int xe = soff + ai.m_w; // End for this line + while(soff < xe) { // While within this line + //-- (continue) collect a run, + int rs = soff; // Save run start + byte rcolor = px[soff++]; // First color + while(soff < xe && px[soff] == rcolor) // Run till eoln or badclor + soff++; + + //-- Run ended. Map the input index to the GIF's index, + short ii = map[rcolor + 0x80]; + if (ii == -1){ // Unknown map? + //-- New color. Get it's translated RGB value, + int rix = (int)rcolor & 0xff; // Translate to unsigned + int rgb = cm.getRGB(rix); // Get RGB value for this input index, + if(rgb >= 0) { // Transparant color? + //-- If there is a transparant color index use it... + if (m_transparant_ix < 0) { + //-- First transparant color found- save it, + if(rgb == 0) rgb = 1; // Zero color protection - req'd for hashtable implementation + m_transparant_ix = findColorIndex(rgb); + } + ii = m_transparant_ix; // Use trans color to fill + } else { + //-- Not transparant, + ii = findColorIndex(rgb); // Add RGB value to the index, + } + map[rcolor + 0x80] = ii; + } + + //-- Always write this run. + int dep = doff + (soff - rs); // End output pos + byte idx = (byte) ii; + while(doff < dep) + par[doff++] = idx; // Fill output. + } + + //-- Prepare for a new line. + soff += iw - ai.m_w; // Increment what's left to next line, + } + + ai.m_rgb = par; // Save created thing + return true; + } + + /*--------------------------------------------------------------*/ + /* CODING: BufferedImage.All int packed stuff.. */ + /*--------------------------------------------------------------*/ + /** + * Encodes INT pixel-packed images. + */ + private boolean precodeIntPacked(AnIma ai, BufferedImage bi) throws IOException { + //-- Get the colormodel, the raster, the databuffer and the samplemodel + ColorModel cm = bi.getColorModel(); + Raster ras = bi.getRaster(); + SampleModel tsm = ras.getSampleModel(); + if(! (tsm instanceof SinglePixelPackedSampleModel)) return false; + SinglePixelPackedSampleModel sm = (SinglePixelPackedSampleModel) tsm; + + DataBuffer dbt = ras.getDataBuffer(); + if(dbt.getDataType() != DataBuffer.TYPE_INT) return false; + if(dbt.getNumBanks() != 1) return false; + DataBufferInt db = (DataBufferInt) dbt; + + /* + * Prepare the run: get all constants e.a. The mechanism runs thru + * all pixels by traversing each X scanline, then moving to the next + * one. One fun thing: we only have to COPY all pixels, since we're + * already byte-packed. + */ + int endoff = ai.m_w * ai.m_h; // Output image size, + byte[] par = new byte[endoff]; // Byte-indexed output array, + int doff = 0; // Destination offset, + byte ii; + + //-- source + int soff = getBiOffset(ras, sm, 0, 0); + int[] px = db.getData(0); // Get the pixelset, + int esoff = getBiOffset(ras, sm, ai.m_w-1, ai.m_h-1); // calc end offset, + int iw = sm.getScanlineStride(); // Increment width = databuf's width + + while(soff < esoff) { // For all scan lines, + + int xe = soff + ai.m_w; // End for this line + while (soff < xe) { // While within this line + //-- (continue) collect a run, + int rs = soff; // Save run start + int rcolor = px[soff++]; // First color + while(soff < xe && px[soff] == rcolor) // Run till eoln or badclor + soff++; + + //-- Run ended. Map the input index to the GIF's index, + int rgb = cm.getRGB(rcolor); // Get RGB value for this input index, + if(rgb >= 0) { // Transparant color? + //-- If there is a transparant color index use it... + if(m_transparant_ix < 0) { + //-- First transparant color found- save it, + if(rgb == 0) rgb = 1; // Zero color protection - req'd for hashtable implementation + m_transparant_ix = findColorIndex(rgb); + } + ii = (byte)m_transparant_ix; // Use trans color to fill + } else { + //-- Not transparant, + ii = (byte)findColorIndex(rgb); // Add RGB value to the index, + } + + //-- Always write this run. + int dep = doff + (soff - rs); // End output pos + while(doff < dep) + par[doff++] = ii; // Fill output. + } + + //-- Prepare for a new line. + soff += iw - ai.m_w; // Increment what's left to next line, + + } + + ai.m_rgb = par; // Save created thing + return true; + } + + /*--------------------------------------------------------------*/ + /* CODING: BufferedImage- SHORT type stuff.. */ + /*--------------------------------------------------------------*/ + /** + * Encodes SHORT pixel-packed images. + */ + private boolean precodeShortPacked(AnIma ai, BufferedImage bi) throws IOException { + //-- Get the colormodel, the raster, the databuffer and the samplemodel + ColorModel cm = bi.getColorModel(); + Raster ras = bi.getRaster(); + SampleModel tsm = ras.getSampleModel(); + if(! (tsm instanceof SinglePixelPackedSampleModel)) return false; + SinglePixelPackedSampleModel sm = (SinglePixelPackedSampleModel) tsm; + + DataBuffer dbt = ras.getDataBuffer(); + if(dbt.getDataType() != DataBuffer.TYPE_SHORT) return false; + if(dbt.getNumBanks() != 1) return false; + DataBufferShort db = (DataBufferShort) dbt; + + /* + * Prepare the run: get all constants e.a. The mechanism runs thru + * all pixels by traversing each X scanline, then moving to the next + * one. One fun thing: we only have to COPY all pixels, since we're + * already byte-packed. + */ + int endoff = ai.m_w * ai.m_h; // Output image size, + byte[] par = new byte[endoff]; // Byte-indexed output array, + int doff = 0; // Destination offset, + byte ii; + + //-- source + int soff = getBiOffset(ras, sm, 0, 0); + short[] px = db.getData(0); // Get the pixelset, + int esoff = getBiOffset(ras, sm, ai.m_w-1, ai.m_h-1); // calc end offset, + int iw = sm.getScanlineStride(); // Increment width = databuf's width + + while(soff < esoff) // For all scan lines, + { + int xe = soff + ai.m_w; // End for this line + while(soff < xe) // While within this line + { + //-- (continue) collect a run, + int rs = soff; // Save run start + short rcolor = px[soff++]; // First color + while(soff < xe && px[soff] == rcolor) // Run till eoln or badclor + soff++; + + //-- Run ended. Map the input index to the GIF's index, + int rgb = cm.getRGB(rcolor); // Get RGB value for this input index, + if(rgb >= 0) // Transparant color? + { + //-- If there is a transparant color index use it... + if(m_transparant_ix < 0) + { + //-- First transparant color found- save it, + if(rgb == 0) rgb = 1; // Zero color protection - req'd for hashtable implementation + m_transparant_ix = findColorIndex(rgb); + } + ii = (byte)m_transparant_ix; // Use trans color to fill + } + else + { + //-- Not transparant, + ii = (byte)findColorIndex(rgb); // Add RGB value to the index, + } + + //-- Always write this run. + int dep = doff + (soff - rs); // End output pos + while(doff < dep) + par[doff++] = ii; // Fill output. + } + + //-- Prepare for a new line. + soff += iw - ai.m_w; // Increment what's left to next line, + + } + + ai.m_rgb = par; // Save created thing + return true; + } + + + /*--------------------------------------------------------------*/ + /* CODING: The generic Image stuff to translate the GIF */ + /*--------------------------------------------------------------*/ + /** + * Using a generic Image, this uses a PixelGrabber to get an integer + * pixel array. + */ + private void precodeImage(AnIma ai, Image ima) throws IOException { + int[] px; + + //-- Wait for the image to arrive, + MediaTracker mt = new MediaTracker(m_cv); + mt.addImage(ima, 0); + try + { + mt.waitForAll(); // Be use all are loaded, + } + catch(InterruptedException x) + { + throw new IOException("Interrupted load of image"); + } + mt.removeImage(ima, 0); + mt = null; + + //-- Get the images' size & adjust the complete GIF's size, + ai.m_w = ima.getWidth(m_cv); + ai.m_h = ima.getHeight(m_cv); + if(ai.m_h == 0 || ai.m_w == 0) return; + checkTotalSize(ai); + + //-- Grab pixels & convert to 8-bit pixelset. + PixelGrabber pg = new PixelGrabber(ima, 0, 0, ai.m_w, ai.m_h, true); + try { + pg.grabPixels(); + } catch(InterruptedException x) { + throw new IOException("Interrupted load of image"); + } + px = (int[]) pg.getPixels(); // Get the pixels, + + translateColorsByArray(ai, px); // Run the translator + } + + + /** + * For each pixel in the source image, the color is put into the palette + * for the combined GIF. The index of the color is then used in the 8-bit + * pixelset for this image. + */ + private void translateColorsByArray(AnIma a, int[] px) throws IOException { + int off; + byte[] par; + int endoff = a.m_w * a.m_h; // Total #pixels in image + int rstart, rcolor; // Run data. + byte newc; + + //-- Collect runs of pixels of the same color; then handle them; + par = new byte[endoff]; // Allocate output matrix + off = 0; // Output offset, + while(off < endoff) { + //-- Collect the current run of pixels. + rstart = off; + rcolor = px[off++]; // Get 1st pixel of run, + while(off < endoff && px[off] == rcolor) // Fast loop! + off++; + + //-- Translate the color to an index, and handle transparency, + if(rcolor >= 0) // Is this a TRANSPARANT color? + { + //-- If there is a transparant color index use it... + if(m_transparant_ix < 0) + { + //-- First transparant color found- save it, + if(rcolor == 0) rcolor = 1; // Zero color protection - req'd for hashtable implementation + m_transparant_ix = findColorIndex(rcolor); + } + newc = (byte)m_transparant_ix; // Set color to fill run with + } + else + { + //-- Not transparant- is an index known for this color? + int i = (rcolor & 0x7fffffff) % CHSIZE; + + if(m_ccolor_ar[i] == rcolor) // Bucket found? + newc = (byte)m_cindex_ar[i]; + else + newc = (byte)findColorIndex(rcolor); // Get color index, + } + + //-- Always fill the run with the replaced color, + while(rstart < off) + par[rstart++] = newc; + + //-- This run has been done!! + } + + a.m_rgb = par; // Save completed map; + } + + /** + * Generates the color map by using the color table and creating all + * rgb tables. These are then written to the output. This gets called when + * all images have been added and pre-traversed. + */ + private void genColorTable() throws IOException { + // Turn colors into colormap entries. + int nelem = 1 << m_color_bits; + byte[] reds = new byte[nelem]; + byte[] grns = new byte[nelem]; + byte[] blus = new byte[nelem]; + + //-- Now enumerate the color table. + for (int i = CHSIZE; --i >= 0;) { // Count backwards (faster) + if(m_ccolor_ar[i] != 0) { // A color was found? + reds[ m_cindex_ar[i] ] = (byte) ( (m_ccolor_ar[i] >> 16) & 0xff); + grns[ m_cindex_ar[i] ] = (byte) ( (m_ccolor_ar[i] >> 8) & 0xff); + blus[ m_cindex_ar[i] ] = (byte) ( m_ccolor_ar[i] & 0xff ); + } + } + + //-- Write the map to the stream, + for (int i = 0; i < nelem; i++) { // Save all elements, + utByte(reds[i]); + utByte(grns[i]); + utByte(blus[i]); + } + } + + /** + * Writes the GIF file header, containing all up to the first image data + * structure: color table, option fields etc. + */ + private void genHeader() throws IOException { + // Figure out how many bits to use. + if(m_color_ix <= 2) + m_color_bits = 1; + else if(m_color_ix <= 4) + m_color_bits = 2; + else if(m_color_ix <= 8) + m_color_bits = 3; + else if(m_color_ix <= 16) + m_color_bits = 4; + else + m_color_bits = 8; + + //-- Start with the headerm + utStr("GIF89a" ); // Gif89a Header: signature & version + + //-- Logical Screen Descriptor Block + utWord(m_w); // Collated width & height of all images + utWord(m_h); + byte b = (byte)(0xF0 | (m_color_bits-1));// There IS a color map, 8 bits per color source resolution. not sorted, + utByte(b); // Packet fields, + utByte((byte)0); // Background Color Index assumed 0. + utByte((byte)0); // Pixel aspect ratio 1:1: zero always works... + + //-- Now write the Global Color Map. + genColorTable(); + + if (m_loop && m_ima_ar.size() > 1) { + //-- Generate a Netscape loop thing, + utByte((byte) 0x21); + utByte((byte) 0xff); + utByte((byte) 0x0b); + utStr("NETSCAPE2.0"); + utByte((byte) 0x03); + utByte((byte) 1); + utWord(0); // Repeat indefinitely + utByte((byte)0); + } + } + + /** + * Writes the GIF file trailer, terminating the GIF file. + */ + private void genTrailer() throws IOException { + // Write the GIF file terminator + utByte((byte) ';'); + } + + /** + * Writes a single image instance. + */ + private void genImage(AnIma ai) throws IOException { + //-- Write out a Graphic Control Extension for transparent colour & repeat, if necessary, + if(m_transparant_ix != -1 || m_ima_ar.size() > 1) { + byte transpar; + + utByte( (byte) '!'); // 0x21 Extension Introducer + utByte( (byte) 0xf9); // Graphic Control Label + utByte( (byte) 4); // Block Size, + if(m_transparant_ix >= 0) { // There IS transparancy? + utByte((byte) 1); // TRANS flag SET + transpar = (byte) m_transparant_ix; + } else { + utByte((byte) 0); // TRANS flag CLEAR + transpar = 0; + } + utWord( ai.m_delay ); // Delay time, + utByte(transpar); // And save the index, + utByte( (byte) 0); + } + + //-- Write the Image Descriptor + utByte((byte)','); + utWord(ai.m_x); // Image left position, + utWord(ai.m_y); // Image right position + utWord(ai.m_w); + utWord(ai.m_h); // And it's size, + utByte((byte) (ai.m_interlace ? 0x40 : 0)); // Packed fields: interlaced Y/N, no local table no sort, + + //-- The table-based image data... + int initcodesz = m_color_bits <= 1 ? 2 : m_color_bits; + utByte((byte) initcodesz); // Output initial LZH code size, min. 2 bits, + genCompressed(ai, initcodesz+1); // Generate the compressed data, + utByte((byte) 0); // Zero-length packet (end series) + } + + /*------------------------------------------------------------------*/ + /* CODING: Stuff to compress!!! */ + /*------------------------------------------------------------------*/ + /* + * Most of this compressor code has been reaped from the ACME GifEncoder + * package. See there for more details. + * This code will be revised for speed in the next release though. + */ + /** Pixmap from ima currently compressed */ + private byte[] m_curr_pixels; + + /** Current pixel source index in above map */ + private int m_px_ix; + + /** End index within above index. */ + private int m_px_endix; + + private void genCompressed(AnIma a, int initcodesz) throws IOException { + //-- Set all globals to retrieve pixel data quickly. $$TODO: Interlaced + m_curr_pixels = a.m_rgb; + m_px_ix = 0; + m_px_endix = a.m_w * a.m_h; // Last index, + + //-- Coder variables. + int i, c, ent, disp, hsize_reg, hshift, fcode; + + //-- Init: the bit-code writer's variables, + cur_accum = 0; + cur_bits = 0; + free_ent = 0; + clear_flg = false; + maxbits = BITS; // user settable max # bits/code + maxmaxcode = 1 << BITS; // should NEVER generate this code + a_count = 0; + g_init_bits = initcodesz; // Initial #of bits + + // Set up the necessary values + clear_flg = false; + n_bits = g_init_bits; + maxcode = MAXCODE( n_bits ); + ClearCode = 1 << ( initcodesz - 1 ); + EOFCode = ClearCode + 1; + free_ent = ClearCode + 2; + char_init(); + + hshift = 0; + for ( fcode = hsize; fcode < 65536; fcode *= 2 ) + ++hshift; + hshift = 8 - hshift; // set hash code range bound + + hsize_reg = hsize; + cl_hash( hsize_reg ); // clear hash table + output(ClearCode); + + ent = m_curr_pixels[m_px_ix++]; // Get 1st pixel value, + outer_loop: while(m_px_ix < m_px_endix) // While not at end + { + c = m_curr_pixels[m_px_ix++]; // Get next pixel value, + fcode = ( c << maxbits ) + ent; + i = ( c << hshift ) ^ ent; // xor hashing + + if(htab[i] == fcode) + { + ent = codetab[i]; + continue; + } + else if ( htab[i] >= 0 ) // non-empty slot + { + disp = hsize_reg - i; // secondary hash (after G. Knott) + if ( i == 0 ) // ?? Should be inpossible?? JAL + disp = 1; + do + { + if( (i -= disp) < 0 ) + i += hsize_reg; + + if ( htab[i] == fcode ) + { + ent = codetab[i]; + continue outer_loop; + } + } + while ( htab[i] >= 0 ); + } + output(ent); + ent = c; + if ( free_ent < maxmaxcode ) + { + codetab[i] = free_ent++; // code -> hashtable + htab[i] = fcode; + } + else + cl_block(); + } + // Put out the final code. + output(ent); + outputEOF(); + } + + static final int EOF = -1; + + // GIFCOMPR.C - GIF Image compression routines + // + // Lempel-Ziv compression based on 'compress'. GIF modifications by + // David Rowley ([EMAIL PROTECTED]) + + // General DEFINEs + + static final int BITS = 12; + static final int HSIZE = 5003; // 80% occupancy + + // GIF Image compression - modified 'compress' + // + // Based on: compress.c - File compression ala IEEE Computer, June 1984. + // + // By Authors: Spencer W. Thomas (decvax!harpo!utah-cs!utah-gr!thomas) + // Jim McKie (decvax!mcvax!jim) + // Steve Davies (decvax!vax135!petsd!peora!srd) + // Ken Turkowski (decvax!decwrl!turtlevax!ken) + // James A. Woods (decvax!ihnp4!ames!jaw) + // Joe Orost (decvax!vax135!petsd!joe) + + int n_bits; // number of bits/code + int maxbits = BITS; // user settable max # bits/code + int maxcode; // maximum code, given n_bits + int maxmaxcode = 1 << BITS; // should NEVER generate this code + + final int MAXCODE( int n_bits ) { + return ( 1 << n_bits ) - 1; + } + + int[] htab; + int[] codetab; + + int hsize = HSIZE; // for dynamic table sizing + + int free_ent = 0; // first unused entry + + // block compression parameters -- after all codes are used up, + // and compression rate changes, start over. + boolean clear_flg = false; + + // Algorithm: use open addressing double hashing (no chaining) on the + // prefix code / next character combination. We do a variant of Knuth's + // algorithm D (vol. 3, sec. 6.4) along with G. Knott's relatively-prime + // secondary probe. Here, the modular division first probe is gives way + // to a faster exclusive-or manipulation. Also do block compression with + // an adaptive reset, whereby the code table is cleared when the compression + // ratio decreases, but after the table fills. The variable-length output + // codes are re-sized at this point, and a special CLEAR code is generated + // for the decompressor. Late addition: construct the table according to + // file size for noticeable speed improvement on small files. Please direct + // questions about this implementation to ames!jaw. + + int g_init_bits; + int ClearCode; + int EOFCode; + + // Output the given code. + // Inputs: + // code: A n_bits-bit integer. If == -1, then EOF. This assumes + // that n_bits =< wordsize - 1. + // Outputs: + // Outputs code to the file. + // Assumptions: + // Chars are 8 bits long. + // Algorithm: + // Maintain a BITS character long buffer (so that 8 codes will + // fit in it exactly). Use the VAX insv instruction to insert each + // code in turn. When the buffer fills up empty it and start over. + + int cur_accum = 0; + int cur_bits = 0; + + static int masks[] = { + 0x0000, 0x0001, 0x0003, 0x0007, 0x000F, + 0x001F, 0x003F, 0x007F, 0x00FF, + 0x01FF, 0x03FF, 0x07FF, 0x0FFF, + 0x1FFF, 0x3FFF, 0x7FFF, 0xFFFF }; + + void output(int code) throws IOException { + cur_accum |= ( code << cur_bits ); + cur_bits += n_bits; + + while( cur_bits >= 8 ) { + //-- Expanded char_out code + accum[a_count++] = (byte) cur_accum; + if ( a_count >= 254 ) + flush_char(); + //-- End of char_out expansion + + cur_accum >>= 8; + cur_bits -= 8; + } + + // If the next entry is going to be too big for the code size, + // then increase it, if possible. + // $$Rewrote if (JAL) + if(clear_flg) { + maxcode = MAXCODE(n_bits = g_init_bits); + clear_flg = false; + } else if(free_ent > maxcode) { + ++n_bits; + + if (n_bits == maxbits) + maxcode = maxmaxcode; + else + maxcode = MAXCODE(n_bits); + } + } + + /** + * Removed from output() above to skip an extra IF in the main loop. Must + * be called instead of calling output(EOFCode). + */ + private void outputEOF() throws IOException { + output(EOFCode); // Actually output the code + + //-- At EOF, write the rest of the buffer. + while( cur_bits > 0) + { + //-- Expanded char_out. + accum[a_count++] = (byte) cur_accum; + if ( a_count >= 254 ) + flush_char(); + //-- End of char_out expansion + cur_accum >>= 8; + cur_bits -= 8; + } + flush_char(); + } + + // Clear out the hash table + // table clear for block compress + void cl_block() throws IOException { + cl_hash( hsize ); + free_ent = ClearCode + 2; + clear_flg = true; + + output(ClearCode); + } + + // reset code table + void cl_hash( int hsize ) { + for(int i = hsize; --i >= 0;) + htab[i] = -1; + } + + // GIF Specific routines + + // Number of characters so far in this 'packet' + int a_count; + + // Set up the 'byte output' routine + void char_init() { + a_count = 0; + } + + // Define the storage for the packet accumulator + byte[] accum; + + // Add a character to the end of the current packet, and if it is 254 + // characters, flush the packet to disk. + void char_out(byte c) throws IOException { + accum[a_count++] = c; + if ( a_count >= 254 ) + flush_char(); + } + + // Flush the packet to disk, and reset the accumulator + void flush_char() throws IOException { + if( a_count > 0) { + m_os.write( a_count ); + m_os.write( accum, 0, a_count ); + a_count = 0; + } + } + + // test method for ymage classes + public static void main(String[] args) { + System.setProperty("java.awt.headless", "true"); + + ymageMatrix m = new ymageMatrix(200, 300, ymageMatrix.MODE_SUB, "FFFFFF"); + ymageMatrix.demoPaint(m); + File file = new File("/Users/admin/Desktop/testimage.gif"); + + OutputStream os; + try { + os = new FileOutputStream(file); + AnimGifEncoder age = new AnimGifEncoder(os); + age.add(m.getImage()); + age.add(m.getImage()); + age.encode(); + os.close(); + } catch (FileNotFoundException e) { + e.printStackTrace(); + } catch (IOException e) { + e.printStackTrace(); + } + } +} + +class GifColorEntry { + /** The actual RGB color for this entry */ + public int m_color; + + /** The colortable [palette] entry number for this color */ + public int m_index; + + public GifColorEntry(int col, int ix) { + m_color = col; + m_index = ix; + } +}; + +class AnIma { + /** This-image's interlace flag */ + public boolean m_interlace; + + /** This-image's delay factor */ + public int m_delay; + + /** This-image's source and destination within the completed image */ + public int m_x, m_y; + + /** This image's width and height */ + public int m_w, m_h; + + /** This-image's 8-bit pixelset. It indexes the m_color_ar table. */ + public byte[] m_rgb; +}; _______________________________________________ YaCy-svn mailing list YaCy-svn@lists.berlios.de https://lists.berlios.de/mailman/listinfo/yacy-svn