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
[email protected]
https://lists.berlios.de/mailman/listinfo/yacy-svn