0001: /*
0002: * (C)2000 by F. Jalvingh, Mumble Internet Services
0003: * For questions and the like: fjalvingh@bigfoot.com
0004: *
0005: * Compression part (C)1996,1998 by Jef Poskanzer <jef@acme.com>. All rights reserved.
0006: *
0007: * This software is placed in the public domain. You are free to use this
0008: * software for any means while respecting the above copyright.
0009: *
0010: * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
0011: * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
0012: * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
0013: * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
0014: * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
0015: * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
0016: * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
0017: * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
0018: * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
0019: * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
0020: * SUCH DAMAGE.
0021: *
0022: * Optimizations by Jal:
0023: * ---------------------
0024: * Initial: Coded RLE code for building the 8-bit color table.
0025: * 6dec00: Changed code to remove extraneous if's and unrolled some calls.
0026: * Replaced color hashtable with local specialized variant.
0027: * 7dec00: Made specialized direct buffer access versions for BufferedImage
0028: * images..
0029: *
0030: */
0031:
0032: // very slightly adopted by Michael Christen, 12.12.2007
0033: // - removed unused variables
0034: // - replaced old java classes by new one
0035: package de.anomic.ymage;
0036:
0037: import java.awt.Canvas;
0038: import java.awt.Image;
0039: import java.awt.MediaTracker;
0040: import java.awt.image.BufferedImage;
0041: import java.awt.image.ColorModel;
0042: import java.awt.image.DataBuffer;
0043: import java.awt.image.DataBufferByte;
0044: import java.awt.image.DataBufferInt;
0045: import java.awt.image.DataBufferShort;
0046: import java.awt.image.IndexColorModel;
0047: import java.awt.image.PixelGrabber;
0048: import java.awt.image.PixelInterleavedSampleModel;
0049: import java.awt.image.Raster;
0050: import java.awt.image.SampleModel;
0051: import java.awt.image.SinglePixelPackedSampleModel;
0052: import java.io.File;
0053: import java.io.FileNotFoundException;
0054: import java.io.FileOutputStream;
0055: import java.io.IOException;
0056: import java.io.OutputStream;
0057: import java.util.ArrayList;
0058:
0059: /**
0060: * <p>This class can be used to write an animated GIF file by combining several
0061: * images. It is loosely based on the Acme GIF encoder.</p>
0062: *
0063: * <p>The characteristics of the generated Gif 89a image are:
0064: * <ul>
0065: * <li>Only a single color table is used (no local tables). This table is
0066: * created by combining the colors from all other images.</li>
0067: * </ul>
0068: * </p>
0069: *
0070: * @author F. Jalvingh
0071: */
0072: public class AnimGifEncoder {
0073: /** The default interlacing indicator */
0074: private boolean m_default_interlace = false;
0075:
0076: /** The default delay time, */
0077: private int m_default_delay = 100;
0078:
0079: /** Set when looping the set is requested. */
0080: private boolean m_loop = true;
0081:
0082: /** The outputstream to write the image to. */
0083: private OutputStream m_os;
0084:
0085: /** The (current) list of images to embed in the GIF */
0086: private ArrayList<AnIma> m_ima_ar;
0087:
0088: /** The total width and height of all combined images */
0089: private int m_w, m_h;
0090:
0091: /** The canvas is used to proprly track images. */
0092: private Canvas m_cv;
0093:
0094: /** The index for the "transparant" color. -1 if no transparant found. */
0095: private short m_transparant_ix = -1;
0096:
0097: /** The index (palette table entry #) to use for the NEXT color encountered */
0098: private short m_color_ix;
0099:
0100: /** The #of bits to use (2log m_color_ix). */
0101: private int m_color_bits;
0102:
0103: /// Temp optimization inhibition.
0104: public boolean m_no_opt;
0105:
0106: /**
0107: * This constructor creates an empty default codec.
0108: */
0109: public AnimGifEncoder(OutputStream os) {
0110: m_os = os;
0111: }
0112:
0113: /**
0114: * Creates a codec and specify interlace (not implemented yet).
0115: */
0116: public AnimGifEncoder(OutputStream os, boolean interlace) {
0117: m_os = os;
0118: m_default_interlace = interlace;
0119: }
0120:
0121: /**
0122: * <p>For animated GIF's the default is to LOOP all images in the GIF file.
0123: * This means that after displaying all images in the file the first image
0124: * is redisplayed ad infinitum.</p>
0125: * <p>To prevent the images from looping call setLoop(false) before calling
0126: * the encode() method.
0127: * </p>
0128: * <p>The current version does not allow the number of repetitions to be
0129: * specified.
0130: * </p>
0131: */
0132: public void setLoop(boolean loop) {
0133: m_loop = loop;
0134: }
0135:
0136: /**
0137: * Releases ALL cached resources.
0138: */
0139: public void flush() {
0140: //-- 1. The basic stuff
0141: m_ccolor_ar = null;
0142: m_cindex_ar = null;
0143: m_cv = null;
0144: m_ima_ar = null;
0145:
0146: //-- 2. The compressor.
0147: m_curr_pixels = null;
0148: htab = null;
0149: codetab = null;
0150: accum = null;
0151: }
0152:
0153: /*------------------------------------------------------------------*/
0154: /* CODING: Adding images to combine into the animated GIF... */
0155: /*------------------------------------------------------------------*/
0156: /**
0157: * Adds the specified image to the list of images. While adding, the
0158: * image is converted to pixels; each color is added to the color table
0159: * and the resulting 8-bit pixelset is saved. After this call the image
0160: * is released, and only the pixelset remains until the encode call is
0161: * made. Calling encode will release the pixelset.
0162: */
0163: public void add(Image ima, int delaytime, boolean interlace,
0164: int px, int py) throws IOException {
0165: AnIma ai = new AnIma();
0166: ai.m_delay = delaytime;
0167: ai.m_interlace = interlace;
0168: ai.m_x = px;
0169: ai.m_y = py;
0170:
0171: //-- Add to the list of images to embed,
0172: if (m_ima_ar == null) // First call?
0173: {
0174: m_ccolor_ar = new int[CHSIZE]; // New colors code table
0175: m_cindex_ar = new short[CHSIZE];
0176: m_ima_ar = new ArrayList<AnIma>(10); // Contains all component images,
0177: m_cv = new Canvas();
0178: }
0179: m_ima_ar.add(ai);
0180:
0181: //-- Pre-scan the image!!
0182: if (!m_no_opt)
0183: preCode(ai, ima); // Convert to 8bit and make palette
0184: else
0185: precodeImage(ai, ima);
0186: }
0187:
0188: /**
0189: * Adds the specified image to the list of images.
0190: */
0191: public void add(Image ima) throws IOException {
0192: add(ima, m_ima_ar == null ? 0 : m_default_delay,
0193: m_default_interlace, 0, 0);
0194: }
0195:
0196: /**
0197: * Adds the specified image to the list of images.
0198: */
0199: public void add(Image ima, int delay) throws IOException {
0200: add(ima, delay, m_default_interlace, 0, 0);
0201: }
0202:
0203: /*------------------------------------------------------------------*/
0204: /* CODING: I/O to the file - helpers... */
0205: /*------------------------------------------------------------------*/
0206: /**
0207: * Writes a string as a #of bytes to the output stream.
0208: */
0209: private void utStr(String str) throws IOException {
0210: byte[] buf = str.getBytes();
0211: m_os.write(buf);
0212: }
0213:
0214: private void utWord(int val) throws IOException {
0215: utByte((byte) (val & 0xff));
0216: utByte((byte) ((val >> 8) & 0xff));
0217: }
0218:
0219: private void utByte(byte b) throws IOException {
0220: m_os.write(b);
0221: }
0222:
0223: /*------------------------------------------------------------------*/
0224: /* CODING: Starting the encode process... */
0225: /*------------------------------------------------------------------*/
0226: /**
0227: * Creates the GIF file from all images added to the encoder.
0228: */
0229: public void encode() throws IOException {
0230: //-- Check validity,
0231: if (m_ima_ar == null || m_ima_ar.size() == 0)
0232: throw new IOException("No images added.");
0233:
0234: //-- Init the compressor's tables
0235: htab = new int[HSIZE];
0236: codetab = new int[HSIZE];
0237: accum = new byte[256];
0238:
0239: //-- Write the GIF header now,
0240: genHeader();
0241:
0242: /*
0243: * Traverse the data for each image. This determines the actual color
0244: * table and the complete output size.
0245: */
0246: for (int i = 0; i < m_ima_ar.size(); i++) {
0247: AnIma ai = (AnIma) m_ima_ar.get(i);
0248: genImage(ai);
0249: ai.m_rgb = null;
0250: }
0251: genTrailer();
0252: flush();
0253: }
0254:
0255: /*--------------------------------------------------------------*/
0256: /* CODING: Color table code & specialized color hashtable. */
0257: /*--------------------------------------------------------------*/
0258: /*
0259: * This is a hashtable mapping (int, byte). The first int is the actual
0260: * color as gotten from the image. The byte is the index color in the
0261: * colormap for the entry.
0262: * We need to find (byte) by indexing with (int) VERY quicky.
0263: * Furthermore we already know that the table will at max hold 256 entries.
0264: *
0265: * Since all colors >= 0 are transparant, we use (int) = 0 as the empty
0266: * case.
0267: *
0268: * This hashtable uses the same hash mechanism as the LZH compressor: a
0269: * double hash without chaining.
0270: */
0271: static private final int CHSIZE = 1023;
0272:
0273: /// The color hashtable's COLOR table (int rcolors)
0274: private int[] m_ccolor_ar;
0275:
0276: /// The color hashtable's INDEX table (byte index)
0277: private short[] m_cindex_ar;
0278:
0279: /**
0280: * This retrieves the index for a color code from the color hash. If the
0281: * color doesn't exist it is added to the hash table. This uses the double
0282: * hash mechanism described above. If this call causes >255 colors to be
0283: * stored it throws a too many colors exception.
0284: * The function returns the index code for the color.
0285: */
0286: private short findColorIndex(int color) throws IOException {
0287: //-- 1. Primary hash..
0288: int i = (color & 0x7fffffff) % CHSIZE;
0289:
0290: if (m_ccolor_ar[i] == color) // Bucket found?
0291: return m_cindex_ar[i];
0292:
0293: //-- 2. No match. If the bucket is not empty do the 2nd hash,
0294: if (m_ccolor_ar[i] != 0) // Bucket is full?
0295: {
0296: //-- This was a clash. Locate a new bucket & look for another match!
0297: int disp = CHSIZE - i;
0298: do {
0299: i -= disp;
0300: if (i < 0)
0301: i += CHSIZE;
0302: if (m_ccolor_ar[i] == color) // Found in 2nd hash?
0303: return m_cindex_ar[i]; // Then return it.
0304: } while (m_ccolor_ar[i] != 0); // Loop till empty bucket.
0305: }
0306:
0307: //-- 3. Empty bucket found: add this there as a new index.
0308: if (m_color_ix >= 256)
0309: throw new IOException(
0310: "More than 255 colors in this GIF are not allowed.");
0311: m_ccolor_ar[i] = color;
0312: m_cindex_ar[i] = (short) m_color_ix;
0313: return m_color_ix++;
0314: }
0315:
0316: /*--------------------------------------------------------------*/
0317: /* CODING: Optimized pixel grabbers... */
0318: /*--------------------------------------------------------------*/
0319: /**
0320: * Checks if the image lies in the current complete image, else it extends
0321: * the source image.
0322: */
0323: private void checkTotalSize(AnIma ai) {
0324: int t;
0325:
0326: t = ai.m_w + ai.m_x; // Get end-X of image,
0327: if (t > m_w)
0328: m_w = t; // Adjust complete GIF's size
0329: t = ai.m_h + ai.m_y; // Get total height
0330: if (t > m_h)
0331: m_h = t; // Adjust if higher,
0332: }
0333:
0334: /*--------------------------------------------------------------*/
0335: /* CODING: The precoder translates all to 8bit indexed... */
0336: /*--------------------------------------------------------------*/
0337: /**
0338: * Traverse this image, and determine it's characteristics. It adds all
0339: * used colors to the color table and determines the completed size of
0340: * the thing. The image is converted to an 8-bit pixelmap where each pixel
0341: * indexes the generated color table.
0342: * This function tries to get the fastest access to the pixel data for
0343: * several types of BufferedImage. This should enhance the encoding speed
0344: * by preventing the loop thru the entire generalized Raster and ColorModel
0345: * method....
0346: * All precode methods build a color table containing all colors used in
0347: * the image, and an 8-bit "image" containing, for each pixel, the index
0348: * into that color table. They also set the transparant color to use.
0349: */
0350: private void preCode(AnIma ai, Image ima) throws IOException {
0351: //-- Call the appropriate encoder depending on the image type.
0352: if (ima instanceof BufferedImage)
0353: precodeBuffered(ai, (BufferedImage) ima);
0354: else
0355: precodeImage(ai, ima);
0356: }
0357:
0358: /**
0359: * Tries to decode a buffered image in an optimal way. It checks to see
0360: * if it knows the BufferedImage type and calls the appropriate quick
0361: * decoder. If the image is not implemented we fall back to the generic
0362: * method.
0363: */
0364: private void precodeBuffered(AnIma ai, BufferedImage bi)
0365: throws IOException {
0366: //-- 1. Handle all shared tasks...
0367: ai.m_w = bi.getWidth();
0368: ai.m_h = bi.getHeight();
0369: if (ai.m_h == 0 || ai.m_w == 0)
0370: return;
0371: checkTotalSize(ai);
0372:
0373: //-- 2. Optimize for known types...
0374: boolean done = false;
0375: int bt = bi.getType();
0376: switch (bt) {
0377: case BufferedImage.TYPE_BYTE_INDEXED:
0378: done = precodeByteIndexed(ai, bi);
0379: break;
0380: case BufferedImage.TYPE_INT_BGR:
0381: done = precodeIntPacked(ai, bi);
0382: break;
0383: case BufferedImage.TYPE_INT_ARGB:
0384: done = precodeIntPacked(ai, bi);
0385: break;
0386: case BufferedImage.TYPE_USHORT_555_RGB:
0387: done = precodeShortPacked(ai, bi);
0388: break;
0389: case BufferedImage.TYPE_USHORT_565_RGB:
0390: done = precodeShortPacked(ai, bi);
0391: break;
0392: case BufferedImage.TYPE_INT_RGB:
0393: done = precodeIntPacked(ai, bi);
0394: break;
0395: }
0396:
0397: if (done)
0398: return;
0399:
0400: precodeImage(ai, bi);
0401: }
0402:
0403: private int getBiOffset(Raster ras, PixelInterleavedSampleModel sm,
0404: int x, int y) {
0405: return (y - ras.getSampleModelTranslateY())
0406: * sm.getScanlineStride() + x
0407: - ras.getSampleModelTranslateX();
0408: }
0409:
0410: private int getBiOffset(Raster ras,
0411: SinglePixelPackedSampleModel sm, int x, int y) {
0412: return (y - ras.getSampleModelTranslateY())
0413: * sm.getScanlineStride() + x
0414: - ras.getSampleModelTranslateX();
0415: }
0416:
0417: /*--------------------------------------------------------------*/
0418: /* CODING: BufferedImage.TYPE_BYTE_INDEXED.. */
0419: /*--------------------------------------------------------------*/
0420: /**
0421: * Encodes TYPE_BYTE_INDEXED images.
0422: */
0423: private boolean precodeByteIndexed(AnIma ai, BufferedImage bi)
0424: throws IOException {
0425: //-- Get the colormodel, the raster, the databuffer and the samplemodel
0426: ColorModel tcm = bi.getColorModel();
0427: if (!(tcm instanceof IndexColorModel))
0428: return false;
0429: IndexColorModel cm = (IndexColorModel) tcm;
0430:
0431: Raster ras = bi.getRaster();
0432: SampleModel tsm = ras.getSampleModel();
0433: if (!(tsm instanceof PixelInterleavedSampleModel))
0434: return false;
0435: PixelInterleavedSampleModel sm = (PixelInterleavedSampleModel) tsm;
0436:
0437: DataBuffer dbt = ras.getDataBuffer();
0438: if (dbt.getDataType() != DataBuffer.TYPE_BYTE)
0439: return false;
0440: if (dbt.getNumBanks() != 1)
0441: return false;
0442: DataBufferByte db = (DataBufferByte) dbt;
0443:
0444: //-- Prepare the color mapping
0445: short[] map = new short[256]; // Alternate lookup table
0446: for (int i = 0; i < 256; i++)
0447: // Set all entries to unused,
0448: map[i] = -1;
0449:
0450: /*
0451: * Prepare the run: get all constants e.a. The mechanism runs thru
0452: * all pixels by traversing each X scanline, then moving to the next
0453: * one. One fun thing: we only have to COPY all pixels, since we're
0454: * already byte-packed.
0455: */
0456: int endoff = ai.m_w * ai.m_h; // Output image size,
0457: byte[] par = new byte[endoff]; // Byte-indexed output array,
0458: int doff = 0; // Destination offset,
0459:
0460: //-- source
0461: int soff = getBiOffset(ras, sm, 0, 0);
0462: byte[] px = db.getData(0); // Get the pixelset,
0463: int esoff = getBiOffset(ras, sm, ai.m_w - 1, ai.m_h - 1); // calc end offset,
0464: int iw = sm.getScanlineStride(); // Increment width = databuf's width
0465:
0466: while (soff < esoff) { // For all scan lines,
0467:
0468: int xe = soff + ai.m_w; // End for this line
0469: while (soff < xe) { // While within this line
0470: //-- (continue) collect a run,
0471: int rs = soff; // Save run start
0472: byte rcolor = px[soff++]; // First color
0473: while (soff < xe && px[soff] == rcolor)
0474: // Run till eoln or badclor
0475: soff++;
0476:
0477: //-- Run ended. Map the input index to the GIF's index,
0478: short ii = map[rcolor + 0x80];
0479: if (ii == -1) { // Unknown map?
0480: //-- New color. Get it's translated RGB value,
0481: int rix = (int) rcolor & 0xff; // Translate to unsigned
0482: int rgb = cm.getRGB(rix); // Get RGB value for this input index,
0483: if (rgb >= 0) { // Transparant color?
0484: //-- If there is a transparant color index use it...
0485: if (m_transparant_ix < 0) {
0486: //-- First transparant color found- save it,
0487: if (rgb == 0)
0488: rgb = 1; // Zero color protection - req'd for hashtable implementation
0489: m_transparant_ix = findColorIndex(rgb);
0490: }
0491: ii = m_transparant_ix; // Use trans color to fill
0492: } else {
0493: //-- Not transparant,
0494: ii = findColorIndex(rgb); // Add RGB value to the index,
0495: }
0496: map[rcolor + 0x80] = ii;
0497: }
0498:
0499: //-- Always write this run.
0500: int dep = doff + (soff - rs); // End output pos
0501: byte idx = (byte) ii;
0502: while (doff < dep)
0503: par[doff++] = idx; // Fill output.
0504: }
0505:
0506: //-- Prepare for a new line.
0507: soff += iw - ai.m_w; // Increment what's left to next line,
0508: }
0509:
0510: ai.m_rgb = par; // Save created thing
0511: return true;
0512: }
0513:
0514: /*--------------------------------------------------------------*/
0515: /* CODING: BufferedImage.All int packed stuff.. */
0516: /*--------------------------------------------------------------*/
0517: /**
0518: * Encodes INT pixel-packed images.
0519: */
0520: private boolean precodeIntPacked(AnIma ai, BufferedImage bi)
0521: throws IOException {
0522: //-- Get the colormodel, the raster, the databuffer and the samplemodel
0523: ColorModel cm = bi.getColorModel();
0524: Raster ras = bi.getRaster();
0525: SampleModel tsm = ras.getSampleModel();
0526: if (!(tsm instanceof SinglePixelPackedSampleModel))
0527: return false;
0528: SinglePixelPackedSampleModel sm = (SinglePixelPackedSampleModel) tsm;
0529:
0530: DataBuffer dbt = ras.getDataBuffer();
0531: if (dbt.getDataType() != DataBuffer.TYPE_INT)
0532: return false;
0533: if (dbt.getNumBanks() != 1)
0534: return false;
0535: DataBufferInt db = (DataBufferInt) dbt;
0536:
0537: /*
0538: * Prepare the run: get all constants e.a. The mechanism runs thru
0539: * all pixels by traversing each X scanline, then moving to the next
0540: * one. One fun thing: we only have to COPY all pixels, since we're
0541: * already byte-packed.
0542: */
0543: int endoff = ai.m_w * ai.m_h; // Output image size,
0544: byte[] par = new byte[endoff]; // Byte-indexed output array,
0545: int doff = 0; // Destination offset,
0546: byte ii;
0547:
0548: //-- source
0549: int soff = getBiOffset(ras, sm, 0, 0);
0550: int[] px = db.getData(0); // Get the pixelset,
0551: int esoff = getBiOffset(ras, sm, ai.m_w - 1, ai.m_h - 1); // calc end offset,
0552: int iw = sm.getScanlineStride(); // Increment width = databuf's width
0553:
0554: while (soff < esoff) { // For all scan lines,
0555:
0556: int xe = soff + ai.m_w; // End for this line
0557: while (soff < xe) { // While within this line
0558: //-- (continue) collect a run,
0559: int rs = soff; // Save run start
0560: int rcolor = px[soff++]; // First color
0561: while (soff < xe && px[soff] == rcolor)
0562: // Run till eoln or badclor
0563: soff++;
0564:
0565: //-- Run ended. Map the input index to the GIF's index,
0566: int rgb = cm.getRGB(rcolor); // Get RGB value for this input index,
0567: if (rgb >= 0) { // Transparant color?
0568: //-- If there is a transparant color index use it...
0569: if (m_transparant_ix < 0) {
0570: //-- First transparant color found- save it,
0571: if (rgb == 0)
0572: rgb = 1; // Zero color protection - req'd for hashtable implementation
0573: m_transparant_ix = findColorIndex(rgb);
0574: }
0575: ii = (byte) m_transparant_ix; // Use trans color to fill
0576: } else {
0577: //-- Not transparant,
0578: ii = (byte) findColorIndex(rgb); // Add RGB value to the index,
0579: }
0580:
0581: //-- Always write this run.
0582: int dep = doff + (soff - rs); // End output pos
0583: while (doff < dep)
0584: par[doff++] = ii; // Fill output.
0585: }
0586:
0587: //-- Prepare for a new line.
0588: soff += iw - ai.m_w; // Increment what's left to next line,
0589:
0590: }
0591:
0592: ai.m_rgb = par; // Save created thing
0593: return true;
0594: }
0595:
0596: /*--------------------------------------------------------------*/
0597: /* CODING: BufferedImage- SHORT type stuff.. */
0598: /*--------------------------------------------------------------*/
0599: /**
0600: * Encodes SHORT pixel-packed images.
0601: */
0602: private boolean precodeShortPacked(AnIma ai, BufferedImage bi)
0603: throws IOException {
0604: //-- Get the colormodel, the raster, the databuffer and the samplemodel
0605: ColorModel cm = bi.getColorModel();
0606: Raster ras = bi.getRaster();
0607: SampleModel tsm = ras.getSampleModel();
0608: if (!(tsm instanceof SinglePixelPackedSampleModel))
0609: return false;
0610: SinglePixelPackedSampleModel sm = (SinglePixelPackedSampleModel) tsm;
0611:
0612: DataBuffer dbt = ras.getDataBuffer();
0613: if (dbt.getDataType() != DataBuffer.TYPE_SHORT)
0614: return false;
0615: if (dbt.getNumBanks() != 1)
0616: return false;
0617: DataBufferShort db = (DataBufferShort) dbt;
0618:
0619: /*
0620: * Prepare the run: get all constants e.a. The mechanism runs thru
0621: * all pixels by traversing each X scanline, then moving to the next
0622: * one. One fun thing: we only have to COPY all pixels, since we're
0623: * already byte-packed.
0624: */
0625: int endoff = ai.m_w * ai.m_h; // Output image size,
0626: byte[] par = new byte[endoff]; // Byte-indexed output array,
0627: int doff = 0; // Destination offset,
0628: byte ii;
0629:
0630: //-- source
0631: int soff = getBiOffset(ras, sm, 0, 0);
0632: short[] px = db.getData(0); // Get the pixelset,
0633: int esoff = getBiOffset(ras, sm, ai.m_w - 1, ai.m_h - 1); // calc end offset,
0634: int iw = sm.getScanlineStride(); // Increment width = databuf's width
0635:
0636: while (soff < esoff) // For all scan lines,
0637: {
0638: int xe = soff + ai.m_w; // End for this line
0639: while (soff < xe) // While within this line
0640: {
0641: //-- (continue) collect a run,
0642: int rs = soff; // Save run start
0643: short rcolor = px[soff++]; // First color
0644: while (soff < xe && px[soff] == rcolor)
0645: // Run till eoln or badclor
0646: soff++;
0647:
0648: //-- Run ended. Map the input index to the GIF's index,
0649: int rgb = cm.getRGB(rcolor); // Get RGB value for this input index,
0650: if (rgb >= 0) // Transparant color?
0651: {
0652: //-- If there is a transparant color index use it...
0653: if (m_transparant_ix < 0) {
0654: //-- First transparant color found- save it,
0655: if (rgb == 0)
0656: rgb = 1; // Zero color protection - req'd for hashtable implementation
0657: m_transparant_ix = findColorIndex(rgb);
0658: }
0659: ii = (byte) m_transparant_ix; // Use trans color to fill
0660: } else {
0661: //-- Not transparant,
0662: ii = (byte) findColorIndex(rgb); // Add RGB value to the index,
0663: }
0664:
0665: //-- Always write this run.
0666: int dep = doff + (soff - rs); // End output pos
0667: while (doff < dep)
0668: par[doff++] = ii; // Fill output.
0669: }
0670:
0671: //-- Prepare for a new line.
0672: soff += iw - ai.m_w; // Increment what's left to next line,
0673:
0674: }
0675:
0676: ai.m_rgb = par; // Save created thing
0677: return true;
0678: }
0679:
0680: /*--------------------------------------------------------------*/
0681: /* CODING: The generic Image stuff to translate the GIF */
0682: /*--------------------------------------------------------------*/
0683: /**
0684: * Using a generic Image, this uses a PixelGrabber to get an integer
0685: * pixel array.
0686: */
0687: private void precodeImage(AnIma ai, Image ima) throws IOException {
0688: int[] px;
0689:
0690: //-- Wait for the image to arrive,
0691: MediaTracker mt = new MediaTracker(m_cv);
0692: mt.addImage(ima, 0);
0693: try {
0694: mt.waitForAll(); // Be use all are loaded,
0695: } catch (InterruptedException x) {
0696: throw new IOException("Interrupted load of image");
0697: }
0698: mt.removeImage(ima, 0);
0699: mt = null;
0700:
0701: //-- Get the images' size & adjust the complete GIF's size,
0702: ai.m_w = ima.getWidth(m_cv);
0703: ai.m_h = ima.getHeight(m_cv);
0704: if (ai.m_h == 0 || ai.m_w == 0)
0705: return;
0706: checkTotalSize(ai);
0707:
0708: //-- Grab pixels & convert to 8-bit pixelset.
0709: PixelGrabber pg = new PixelGrabber(ima, 0, 0, ai.m_w, ai.m_h,
0710: true);
0711: try {
0712: pg.grabPixels();
0713: } catch (InterruptedException x) {
0714: throw new IOException("Interrupted load of image");
0715: }
0716: px = (int[]) pg.getPixels(); // Get the pixels,
0717:
0718: translateColorsByArray(ai, px); // Run the translator
0719: }
0720:
0721: /**
0722: * For each pixel in the source image, the color is put into the palette
0723: * for the combined GIF. The index of the color is then used in the 8-bit
0724: * pixelset for this image.
0725: */
0726: private void translateColorsByArray(AnIma a, int[] px)
0727: throws IOException {
0728: int off;
0729: byte[] par;
0730: int endoff = a.m_w * a.m_h; // Total #pixels in image
0731: int rstart, rcolor; // Run data.
0732: byte newc;
0733:
0734: //-- Collect runs of pixels of the same color; then handle them;
0735: par = new byte[endoff]; // Allocate output matrix
0736: off = 0; // Output offset,
0737: while (off < endoff) {
0738: //-- Collect the current run of pixels.
0739: rstart = off;
0740: rcolor = px[off++]; // Get 1st pixel of run,
0741: while (off < endoff && px[off] == rcolor)
0742: // Fast loop!
0743: off++;
0744:
0745: //-- Translate the color to an index, and handle transparency,
0746: if (rcolor >= 0) // Is this a TRANSPARANT color?
0747: {
0748: //-- If there is a transparant color index use it...
0749: if (m_transparant_ix < 0) {
0750: //-- First transparant color found- save it,
0751: if (rcolor == 0)
0752: rcolor = 1; // Zero color protection - req'd for hashtable implementation
0753: m_transparant_ix = findColorIndex(rcolor);
0754: }
0755: newc = (byte) m_transparant_ix; // Set color to fill run with
0756: } else {
0757: //-- Not transparant- is an index known for this color?
0758: int i = (rcolor & 0x7fffffff) % CHSIZE;
0759:
0760: if (m_ccolor_ar[i] == rcolor) // Bucket found?
0761: newc = (byte) m_cindex_ar[i];
0762: else
0763: newc = (byte) findColorIndex(rcolor); // Get color index,
0764: }
0765:
0766: //-- Always fill the run with the replaced color,
0767: while (rstart < off)
0768: par[rstart++] = newc;
0769:
0770: //-- This run has been done!!
0771: }
0772:
0773: a.m_rgb = par; // Save completed map;
0774: }
0775:
0776: /**
0777: * Generates the color map by using the color table and creating all
0778: * rgb tables. These are then written to the output. This gets called when
0779: * all images have been added and pre-traversed.
0780: */
0781: private void genColorTable() throws IOException {
0782: // Turn colors into colormap entries.
0783: int nelem = 1 << m_color_bits;
0784: byte[] reds = new byte[nelem];
0785: byte[] grns = new byte[nelem];
0786: byte[] blus = new byte[nelem];
0787:
0788: //-- Now enumerate the color table.
0789: for (int i = CHSIZE; --i >= 0;) { // Count backwards (faster)
0790: if (m_ccolor_ar[i] != 0) { // A color was found?
0791: reds[m_cindex_ar[i]] = (byte) ((m_ccolor_ar[i] >> 16) & 0xff);
0792: grns[m_cindex_ar[i]] = (byte) ((m_ccolor_ar[i] >> 8) & 0xff);
0793: blus[m_cindex_ar[i]] = (byte) (m_ccolor_ar[i] & 0xff);
0794: }
0795: }
0796:
0797: //-- Write the map to the stream,
0798: for (int i = 0; i < nelem; i++) { // Save all elements,
0799: utByte(reds[i]);
0800: utByte(grns[i]);
0801: utByte(blus[i]);
0802: }
0803: }
0804:
0805: /**
0806: * Writes the GIF file header, containing all up to the first image data
0807: * structure: color table, option fields etc.
0808: */
0809: private void genHeader() throws IOException {
0810: // Figure out how many bits to use.
0811: if (m_color_ix <= 2)
0812: m_color_bits = 1;
0813: else if (m_color_ix <= 4)
0814: m_color_bits = 2;
0815: else if (m_color_ix <= 8)
0816: m_color_bits = 3;
0817: else if (m_color_ix <= 16)
0818: m_color_bits = 4;
0819: else
0820: m_color_bits = 8;
0821:
0822: //-- Start with the headerm
0823: utStr("GIF89a"); // Gif89a Header: signature & version
0824:
0825: //-- Logical Screen Descriptor Block
0826: utWord(m_w); // Collated width & height of all images
0827: utWord(m_h);
0828: byte b = (byte) (0xF0 | (m_color_bits - 1));// There IS a color map, 8 bits per color source resolution. not sorted,
0829: utByte(b); // Packet fields,
0830: utByte((byte) 0); // Background Color Index assumed 0.
0831: utByte((byte) 0); // Pixel aspect ratio 1:1: zero always works...
0832:
0833: //-- Now write the Global Color Map.
0834: genColorTable();
0835:
0836: if (m_loop && m_ima_ar.size() > 1) {
0837: //-- Generate a Netscape loop thing,
0838: utByte((byte) 0x21);
0839: utByte((byte) 0xff);
0840: utByte((byte) 0x0b);
0841: utStr("NETSCAPE2.0");
0842: utByte((byte) 0x03);
0843: utByte((byte) 1);
0844: utWord(0); // Repeat indefinitely
0845: utByte((byte) 0);
0846: }
0847: }
0848:
0849: /**
0850: * Writes the GIF file trailer, terminating the GIF file.
0851: */
0852: private void genTrailer() throws IOException {
0853: // Write the GIF file terminator
0854: utByte((byte) ';');
0855: }
0856:
0857: /**
0858: * Writes a single image instance.
0859: */
0860: private void genImage(AnIma ai) throws IOException {
0861: //-- Write out a Graphic Control Extension for transparent colour & repeat, if necessary,
0862: if (m_transparant_ix != -1 || m_ima_ar.size() > 1) {
0863: byte transpar;
0864:
0865: utByte((byte) '!'); // 0x21 Extension Introducer
0866: utByte((byte) 0xf9); // Graphic Control Label
0867: utByte((byte) 4); // Block Size,
0868: if (m_transparant_ix >= 0) { // There IS transparancy?
0869: utByte((byte) 1); // TRANS flag SET
0870: transpar = (byte) m_transparant_ix;
0871: } else {
0872: utByte((byte) 0); // TRANS flag CLEAR
0873: transpar = 0;
0874: }
0875: utWord(ai.m_delay); // Delay time,
0876: utByte(transpar); // And save the index,
0877: utByte((byte) 0);
0878: }
0879:
0880: //-- Write the Image Descriptor
0881: utByte((byte) ',');
0882: utWord(ai.m_x); // Image left position,
0883: utWord(ai.m_y); // Image right position
0884: utWord(ai.m_w);
0885: utWord(ai.m_h); // And it's size,
0886: utByte((byte) (ai.m_interlace ? 0x40 : 0)); // Packed fields: interlaced Y/N, no local table no sort,
0887:
0888: //-- The table-based image data...
0889: int initcodesz = m_color_bits <= 1 ? 2 : m_color_bits;
0890: utByte((byte) initcodesz); // Output initial LZH code size, min. 2 bits,
0891: genCompressed(ai, initcodesz + 1); // Generate the compressed data,
0892: utByte((byte) 0); // Zero-length packet (end series)
0893: }
0894:
0895: /*------------------------------------------------------------------*/
0896: /* CODING: Stuff to compress!!! */
0897: /*------------------------------------------------------------------*/
0898: /*
0899: * Most of this compressor code has been reaped from the ACME GifEncoder
0900: * package. See there for more details.
0901: * This code will be revised for speed in the next release though.
0902: */
0903: /** Pixmap from ima currently compressed */
0904: private byte[] m_curr_pixels;
0905:
0906: /** Current pixel source index in above map */
0907: private int m_px_ix;
0908:
0909: /** End index within above index. */
0910: private int m_px_endix;
0911:
0912: private void genCompressed(AnIma a, int initcodesz)
0913: throws IOException {
0914: //-- Set all globals to retrieve pixel data quickly. $$TODO: Interlaced
0915: m_curr_pixels = a.m_rgb;
0916: m_px_ix = 0;
0917: m_px_endix = a.m_w * a.m_h; // Last index,
0918:
0919: //-- Coder variables.
0920: int i, c, ent, disp, hsize_reg, hshift, fcode;
0921:
0922: //-- Init: the bit-code writer's variables,
0923: cur_accum = 0;
0924: cur_bits = 0;
0925: free_ent = 0;
0926: clear_flg = false;
0927: maxbits = BITS; // user settable max # bits/code
0928: maxmaxcode = 1 << BITS; // should NEVER generate this code
0929: a_count = 0;
0930: g_init_bits = initcodesz; // Initial #of bits
0931:
0932: // Set up the necessary values
0933: clear_flg = false;
0934: n_bits = g_init_bits;
0935: maxcode = MAXCODE(n_bits);
0936: ClearCode = 1 << (initcodesz - 1);
0937: EOFCode = ClearCode + 1;
0938: free_ent = ClearCode + 2;
0939: char_init();
0940:
0941: hshift = 0;
0942: for (fcode = hsize; fcode < 65536; fcode *= 2)
0943: ++hshift;
0944: hshift = 8 - hshift; // set hash code range bound
0945:
0946: hsize_reg = hsize;
0947: cl_hash(hsize_reg); // clear hash table
0948: output(ClearCode);
0949:
0950: ent = m_curr_pixels[m_px_ix++]; // Get 1st pixel value,
0951: outer_loop: while (m_px_ix < m_px_endix) // While not at end
0952: {
0953: c = m_curr_pixels[m_px_ix++]; // Get next pixel value,
0954: fcode = (c << maxbits) + ent;
0955: i = (c << hshift) ^ ent; // xor hashing
0956:
0957: if (htab[i] == fcode) {
0958: ent = codetab[i];
0959: continue;
0960: } else if (htab[i] >= 0) // non-empty slot
0961: {
0962: disp = hsize_reg - i; // secondary hash (after G. Knott)
0963: if (i == 0) // ?? Should be inpossible?? JAL
0964: disp = 1;
0965: do {
0966: if ((i -= disp) < 0)
0967: i += hsize_reg;
0968:
0969: if (htab[i] == fcode) {
0970: ent = codetab[i];
0971: continue outer_loop;
0972: }
0973: } while (htab[i] >= 0);
0974: }
0975: output(ent);
0976: ent = c;
0977: if (free_ent < maxmaxcode) {
0978: codetab[i] = free_ent++; // code -> hashtable
0979: htab[i] = fcode;
0980: } else
0981: cl_block();
0982: }
0983: // Put out the final code.
0984: output(ent);
0985: outputEOF();
0986: }
0987:
0988: static final int EOF = -1;
0989:
0990: // GIFCOMPR.C - GIF Image compression routines
0991: //
0992: // Lempel-Ziv compression based on 'compress'. GIF modifications by
0993: // David Rowley (mgardi@watdcsu.waterloo.edu)
0994:
0995: // General DEFINEs
0996:
0997: static final int BITS = 12;
0998: static final int HSIZE = 5003; // 80% occupancy
0999:
1000: // GIF Image compression - modified 'compress'
1001: //
1002: // Based on: compress.c - File compression ala IEEE Computer, June 1984.
1003: //
1004: // By Authors: Spencer W. Thomas (decvax!harpo!utah-cs!utah-gr!thomas)
1005: // Jim McKie (decvax!mcvax!jim)
1006: // Steve Davies (decvax!vax135!petsd!peora!srd)
1007: // Ken Turkowski (decvax!decwrl!turtlevax!ken)
1008: // James A. Woods (decvax!ihnp4!ames!jaw)
1009: // Joe Orost (decvax!vax135!petsd!joe)
1010:
1011: int n_bits; // number of bits/code
1012: int maxbits = BITS; // user settable max # bits/code
1013: int maxcode; // maximum code, given n_bits
1014: int maxmaxcode = 1 << BITS; // should NEVER generate this code
1015:
1016: final int MAXCODE(int n_bits) {
1017: return (1 << n_bits) - 1;
1018: }
1019:
1020: int[] htab;
1021: int[] codetab;
1022:
1023: int hsize = HSIZE; // for dynamic table sizing
1024:
1025: int free_ent = 0; // first unused entry
1026:
1027: // block compression parameters -- after all codes are used up,
1028: // and compression rate changes, start over.
1029: boolean clear_flg = false;
1030:
1031: // Algorithm: use open addressing double hashing (no chaining) on the
1032: // prefix code / next character combination. We do a variant of Knuth's
1033: // algorithm D (vol. 3, sec. 6.4) along with G. Knott's relatively-prime
1034: // secondary probe. Here, the modular division first probe is gives way
1035: // to a faster exclusive-or manipulation. Also do block compression with
1036: // an adaptive reset, whereby the code table is cleared when the compression
1037: // ratio decreases, but after the table fills. The variable-length output
1038: // codes are re-sized at this point, and a special CLEAR code is generated
1039: // for the decompressor. Late addition: construct the table according to
1040: // file size for noticeable speed improvement on small files. Please direct
1041: // questions about this implementation to ames!jaw.
1042:
1043: int g_init_bits;
1044: int ClearCode;
1045: int EOFCode;
1046:
1047: // Output the given code.
1048: // Inputs:
1049: // code: A n_bits-bit integer. If == -1, then EOF. This assumes
1050: // that n_bits =< wordsize - 1.
1051: // Outputs:
1052: // Outputs code to the file.
1053: // Assumptions:
1054: // Chars are 8 bits long.
1055: // Algorithm:
1056: // Maintain a BITS character long buffer (so that 8 codes will
1057: // fit in it exactly). Use the VAX insv instruction to insert each
1058: // code in turn. When the buffer fills up empty it and start over.
1059:
1060: int cur_accum = 0;
1061: int cur_bits = 0;
1062:
1063: static int masks[] = { 0x0000, 0x0001, 0x0003, 0x0007, 0x000F,
1064: 0x001F, 0x003F, 0x007F, 0x00FF, 0x01FF, 0x03FF, 0x07FF,
1065: 0x0FFF, 0x1FFF, 0x3FFF, 0x7FFF, 0xFFFF };
1066:
1067: void output(int code) throws IOException {
1068: cur_accum |= (code << cur_bits);
1069: cur_bits += n_bits;
1070:
1071: while (cur_bits >= 8) {
1072: //-- Expanded char_out code
1073: accum[a_count++] = (byte) cur_accum;
1074: if (a_count >= 254)
1075: flush_char();
1076: //-- End of char_out expansion
1077:
1078: cur_accum >>= 8;
1079: cur_bits -= 8;
1080: }
1081:
1082: // If the next entry is going to be too big for the code size,
1083: // then increase it, if possible.
1084: // $$Rewrote if (JAL)
1085: if (clear_flg) {
1086: maxcode = MAXCODE(n_bits = g_init_bits);
1087: clear_flg = false;
1088: } else if (free_ent > maxcode) {
1089: ++n_bits;
1090:
1091: if (n_bits == maxbits)
1092: maxcode = maxmaxcode;
1093: else
1094: maxcode = MAXCODE(n_bits);
1095: }
1096: }
1097:
1098: /**
1099: * Removed from output() above to skip an extra IF in the main loop. Must
1100: * be called instead of calling output(EOFCode).
1101: */
1102: private void outputEOF() throws IOException {
1103: output(EOFCode); // Actually output the code
1104:
1105: //-- At EOF, write the rest of the buffer.
1106: while (cur_bits > 0) {
1107: //-- Expanded char_out.
1108: accum[a_count++] = (byte) cur_accum;
1109: if (a_count >= 254)
1110: flush_char();
1111: //-- End of char_out expansion
1112: cur_accum >>= 8;
1113: cur_bits -= 8;
1114: }
1115: flush_char();
1116: }
1117:
1118: // Clear out the hash table
1119: // table clear for block compress
1120: void cl_block() throws IOException {
1121: cl_hash(hsize);
1122: free_ent = ClearCode + 2;
1123: clear_flg = true;
1124:
1125: output(ClearCode);
1126: }
1127:
1128: // reset code table
1129: void cl_hash(int hsize) {
1130: for (int i = hsize; --i >= 0;)
1131: htab[i] = -1;
1132: }
1133:
1134: // GIF Specific routines
1135:
1136: // Number of characters so far in this 'packet'
1137: int a_count;
1138:
1139: // Set up the 'byte output' routine
1140: void char_init() {
1141: a_count = 0;
1142: }
1143:
1144: // Define the storage for the packet accumulator
1145: byte[] accum;
1146:
1147: // Add a character to the end of the current packet, and if it is 254
1148: // characters, flush the packet to disk.
1149: void char_out(byte c) throws IOException {
1150: accum[a_count++] = c;
1151: if (a_count >= 254)
1152: flush_char();
1153: }
1154:
1155: // Flush the packet to disk, and reset the accumulator
1156: void flush_char() throws IOException {
1157: if (a_count > 0) {
1158: m_os.write(a_count);
1159: m_os.write(accum, 0, a_count);
1160: a_count = 0;
1161: }
1162: }
1163:
1164: // test method for ymage classes
1165: public static void main(String[] args) {
1166: System.setProperty("java.awt.headless", "true");
1167:
1168: ymageMatrix m = new ymageMatrix(200, 300, ymageMatrix.MODE_SUB,
1169: "FFFFFF");
1170: ymageMatrix.demoPaint(m);
1171: File file = new File("/Users/admin/Desktop/testimage.gif");
1172:
1173: OutputStream os;
1174: try {
1175: os = new FileOutputStream(file);
1176: AnimGifEncoder age = new AnimGifEncoder(os);
1177: age.add(m.getImage());
1178: age.add(m.getImage());
1179: age.encode();
1180: os.close();
1181: } catch (FileNotFoundException e) {
1182: e.printStackTrace();
1183: } catch (IOException e) {
1184: e.printStackTrace();
1185: }
1186: }
1187: }
1188:
1189: class GifColorEntry {
1190: /** The actual RGB color for this entry */
1191: public int m_color;
1192:
1193: /** The colortable [palette] entry number for this color */
1194: public int m_index;
1195:
1196: public GifColorEntry(int col, int ix) {
1197: m_color = col;
1198: m_index = ix;
1199: }
1200: };
1201:
1202: class AnIma {
1203: /** This-image's interlace flag */
1204: public boolean m_interlace;
1205:
1206: /** This-image's delay factor */
1207: public int m_delay;
1208:
1209: /** This-image's source and destination within the completed image */
1210: public int m_x, m_y;
1211:
1212: /** This image's width and height */
1213: public int m_w, m_h;
1214:
1215: /** This-image's 8-bit pixelset. It indexes the m_color_ar table. */
1216: public byte[] m_rgb;
1217: };
|