Package gov.lanl.adore.djatoka.io.writer

Source Code of gov.lanl.adore.djatoka.io.writer.GIFWriter

/*
* Copyright (c) 2008  Los Alamos National Security, LLC.
*
* Los Alamos National Laboratory
* Research Library
* Digital Library Research & Prototyping Team
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
*
*/

package gov.lanl.adore.djatoka.io.writer;

import gov.lanl.adore.djatoka.io.FormatIOException;
import gov.lanl.adore.djatoka.io.IWriter;

import java.awt.image.BufferedImage;
import java.awt.image.ColorModel;
import java.awt.image.IndexColorModel;
import java.io.BufferedOutputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.util.Properties;

import ij.ImagePlus;
import ij.process.ImageProcessor;

/**
* GIF File Writer. Uses GifEncoder to write BufferedImage as GIF
* @author Ryan Chute
*
*/
public class GIFWriter implements IWriter {
  /**
   * Write a BufferedImage instance using implementation to the
   * provided OutputStream.
   * @param bi a BufferedImage instance to be serialized
   * @param os OutputStream to output the image to
   * @throws FormatIOException
   */
  public void write(BufferedImage bi, OutputStream os) throws FormatIOException {
    if (bi != null) {
      BufferedOutputStream bos = null;
        bos = new BufferedOutputStream(os);
      ImagePlus ip = new ImagePlus();
      ip.setImage(bi);
      GifEncoder ge = new GifEncoder();
      ge.start(bos);
      ge.addFrame(ip);
      ge.finish();
    }
  }
 
  /**
   * NOT SUPPORTED.
   */
  public void setWriterProperties(Properties props) {}

  /**
   * This plugin encodes a GIF file consisting of one or more frames.
   *
   * <pre>
   * Extensively Modified for ImagePlus
   * Extended to handle 8 bit Images with more complex Color lookup tables with transparency index
   *
   * Ryan Raz March 2002
   * raz@rraz.ca
   * Version 1.01
   ** Extensively Modified for ImagePlus
   * Extended to handle 8 bit Images with more complex Color lookup tables with transparency index
   *
   * Ryan Raz March 2002
   * ryan@rraz.ca
   * Version 1.01 Please report any bugs
   *
   * Credits for the base conversion codes
   * No copyright asserted on the source code of this class.  May be used
   * for any purpose, however, refer to the Unisys LZW patent for restrictions
   * on use of the associated LZWEncoder class.  Please forward any corrections
   * to kweiner@fmsware.com.
   * </pre>
   *
   * @author Kevin Weiner, FM Software
   * @version 1.0 December 2000
   *
   */
  class GifEncoder {
       int width;                 // image size
       int height;
       boolean transparent;  // transparent color if given
       int transIndex;            // transparent index in color table
       int repeat = 0;           // repeat forever
       protected int delay = 50;             // frame delay (hundredths)
       boolean started = false;   // ready to output frames
       OutputStream out;
       ImagePlus image;       // current frame
       byte[] pixels;             // BGR byte array from frame
       byte[] indexedPixels;      // converted frame indexed to palette
       int colorDepth;            // number of bit planes
       byte[] colorTab;           // RGB palette
       int lctSize = 7;           // local color table size (bits-1)
       int dispose = 0;          // disposal code (-1 = use default)
       boolean closeStream = false// close stream when finished
       boolean firstFrame = true;
       boolean sizeSet = false;   // if false, get size from first frame
       int sample = 2;           // default sample interval for quantizer distance should be small for small icons
       byte[] gct = null;       //Global color table
       boolean GCTextracted = false; // Set if global color table extracted from rgb image
       boolean GCTloadedExternal = false; // Set if global color table loaded directly from external image
       int    GCTred =  0;   //Transparent Color
       int  GCTgrn = 0;    // green
       int   GCTbl  =  0;   // blue
       int   GCTcindex = 0//index into color table
       boolean GCTsetTransparent = false; //If true then Color table transparency index is set
       boolean GCToverideIndex = false; //If true Transparent index is set to index with closest colors
       boolean GCToverideColor = false; //if true Color at Transparent index is set to GCTred, GCTgrn GCTbl
      
       /**
        * Adds next GIF frame.  The frame is not written immediately, but is
        * actually deferred until the next frame is received so that timing
        * data can be inserted.  Invoking <code>finish()</code> flushes all
        * frames.  If <code>setSize</code> was not invoked, the size of the
        * first image is used for all subsequent frames.
        *
        * @param im  containing frame to write.
        * @return true if successful.
        */
       public boolean addFrame(ImagePlus image) {
          if ((image == null) || !started) return false;
          boolean ok = true;
          try {
             if (firstFrame) {
                if (!sizeSet) {
                   // use first frame's size
                   setSize(image.getWidth(), image.getHeight());
                }
                writeLSD();
                if (repeat>=0) writeNetscapeExt();      // use NS app extension to indicate reps
                firstFrame = false;
             }
            int bitDepth = image.getBitDepth();
            // If  indexed byte image then format does not need changing
            int k;
            Process8bitCLT(image);
            writeGraphicCtrlExt();         // write graphic control extension
            writeImageDesc();              // image descriptor
            writePalette();                // local color table
            writePixels();                 // encode and write pixel data
          } catch (IOException e) { ok = false; }
          return ok;
      }
     
     /*
    *   Get Options because options box has been checked
       
        Some of the code being set
            setTransparent(Color.black);
            Dispose = 0;  
                setDelay(500);   //  time per frame in milliseconds
                gctused = false; // Set to true to use Global color table
            GCTextracted = false; // Set if global color table extracted from rgb image
                    GCTloadedExternal = false; // Set if global color table loaded directly from external image
                    GCTextracted = false; // Set if global color table extracted from rgb image
            GCTred =  0;   //Transparent Color
            GCTgrn = 0;    // green
            GCTbl  =  0;   // blue
            GCTcindex = 0;  //index into color table
            autotransparent = false; // Set True if transparency index coming from image 8 bit only
            GCTsetTransparent = true; //If true then Color table transparency index is set
            GCToverideIndex = false; //If true Transparent index is set to index with closest colors
            GCToverideColor = false; //if true Color at Transparent index is set to GCTred, GCTgrn GCTbl
      
    */

     
    /********************************************************
    *    Gets Color lookup Table from 8 bit ImagePlus
    */
    void Process8bitCLT(ImagePlus image){
           colorDepth = 8;
            //setTransparent(false);
            ImageProcessor ip = image.getProcessor();
            ip = ip.convertToByte(true);      
            ColorModel cm = ip.getColorModel();
            indexedPixels = (byte[])(ip.getPixels());
            IndexColorModel m = (IndexColorModel)cm;
            int mapSize = m.getMapSize();
            if (transIndex>=mapSize) {
                setTransparent(false);
                transIndex = 0;
            }
            int k;
            colorTab = new byte[mapSize*3];
            for (int i = 0; i < mapSize; i++) {
                k=i*3;
                colorTab[k] = (byte)m.getRed(i);
                colorTab[k+1] = (byte)m.getGreen(i);
                colorTab[k+2] = (byte)m.getBlue(i);
            }
            m.finalize();
     
     }    

       /**
        * Flushes any pending data and closes output file.
        * If writing to an OutputStream, the stream is not
        * closed.
        */
       public boolean finish() {
          if (!started) return false;
          boolean ok = true;
          started = false;
          try {
             out.write(0x3b)// gif trailer
             out.flush();
             if (closeStream)
                out.close();
          } catch (IOException e) { ok = false; }

          // reset for subsequent use
          GCTextracted = false; // Set if global color table extracted from rgb image
          GCTloadedExternal = false; // Set if global color table loaded directly from external image
          transIndex = 0;
          transparent = false;   
          gct = null;       //Global color table
          out = null;
          image = null;
          pixels = null;
          indexedPixels = null;
          colorTab = null;
          closeStream = false;
          firstFrame = true;

          return ok;
       }
      
       /**
        * Sets the delay time between each frame, or changes it
        * for subsequent frames (applies to last frame added).
        *
        * @param ms int delay time in milliseconds
        */
       public void setDelay(int ms) {
          delay = Math.round(ms / 10.0f);
       }


       /**
        * Sets the GIF frame disposal code for the last added frame
        * and any subsequent frames.  Default is 0 if no transparent
        * color has been set, otherwise 2.
        * @param code int disposal code.
        */
       public void setDispose(int code) {
          if (code >= 0)
             dispose = code;
       }


       /**
        * Sets frame rate in frames per second.  Equivalent to
        * <code>setDelay(1000/fps)</code>.
        *
        * @param fps float frame rate (frames per second)
        */
       public void setFrameRate(float fps) {
          if (fps != 0f) {
             delay = Math.round(100f/fps);
          }
       }


       /**
        * Sets quality of color quantization (conversion of images
        * to the maximum 256 colors allowed by the GIF specification).
        * Lower values (minimum = 1) produce better colors, but slow
        * processing significantly.  10 is the default, and produces
        * good color mapping at reasonable speeds.  Values greater
        * than 20 do not yield significant improvements in speed.
        *
        * @param quality int greater than 0.
        * @return
        */
       public void setQuality(int quality) {
          if (quality < 1) quality = 1;
          sample = quality;
       }
      
       /**
        * Sets the number of times the set of GIF frames
        * should be played.  Default is 1; 0 means play
        * indefinitely.  Must be invoked before the first
        * image is added.
        *
        * @param iter int number of iterations.
        * @return
        */
       public void setRepeat(int iter) {
          if (iter >= 0)
             repeat = iter;
       }


       /**
        * Sets the GIF frame size.  The default size is the
        * size of the first frame added if this method is
        * not invoked.
        *
        * @param w int frame width.
        * @param h int frame width.
        */
       public void setSize(int w, int h) {
          if (started && !firstFrame) return;
          width = w;
          height = h;
          if (width < 1) width = 320;
          if (height < 1) height = 240;
          sizeSet = true;
       }


       /**
        * Sets the transparent color for the last added frame
        * and any subsequent frames.
        * Since all colors are subject to modification
        * in the quantization process, the color in the final
        * palette for each frame closest to the given color
        * becomes the transparent color for that frame.
        * May be set to null to indicate no transparent color.
        *
        * @param c Color to be treated as transparent on display.
        */
       public void setTransparent(boolean c) {
          transparent = c;
       }


       /**
        * Initiates GIF file creation on the given stream.  The stream
        * is not closed automatically.
        *
        * @param os OutputStream on which GIF images are written.
        * @return false if initial write failed.
        */
       public boolean start(OutputStream os) {
          if (os == null) return false;
          boolean ok = true;
          closeStream = false;
          out = os;
          try {
             writeString("GIF89a");        // header
          } catch (IOException e) { ok = false; }
          return started = ok;
       }


       /**
        * Initiates writing of a GIF file with the specified name.
        *
        * @param file String containing output file name.
        * @return false if open or initial write failed.
        */
       public boolean start(String file) {
          boolean ok = true;
          try {
             out = new BufferedOutputStream(new FileOutputStream(file));
             ok = start(out);
             closeStream = true;
          } catch (IOException e) { ok = false; }
          return started = ok;
       }
      

    /**
        Sets Net sample size depending on image size
       
    **/
       public void OverRideQuality(int npixs){
            if(npixs>100000) sample = 10;
            else sample = npixs/10000;
            if(sample < 1) sample = 1;

        }
       
       /**
        * Writes Graphic Control Extension
        */
       protected void writeGraphicCtrlExt() throws IOException {
          out.write(0x21);         // extension introducer
          out.write(0xf9);         // GCE label
          out.write(4);            // data block size
          int transp, disp;
          if (!transparent) {
             transp = 0;
             disp = 0;             // dispose = no action
          } else {
             transp = 1;
             disp = 2;             // force clear if using transparent color 
          }
          if (dispose >= 0)
             disp = dispose & 7;   // user override
          disp <<= 2;

          // packed fields
          out.write0 |          // 1:3 reserved
                   disp |          // 4:6 disposal
                   0 |             // 7   user input - 0 = none
                   transp);        // 8   transparency flag

          writeShort(delay);       // delay x 1/100 sec
          out.write(transIndex);   // transparent color index
          out.write(0);            // block terminator
      }


       /**
        * Writes Image Descriptor
        */
       protected void writeImageDesc() throws IOException {
          out.write(0x2c);         // image separator
          writeShort(0);           // image position x,y = 0,0
          writeShort(0);
          writeShort(width);       // image size
          writeShort(height);
          // packed fields
          out.write(0x80 |         // 1 local color table  1=yes
                0 |            // 2 interlace - 0=no
                0 |            // 3 sorted - 0=no
                0 |            // 4-5 reserved
                lctSize);   // size of local color table
       }


       /**
        * Writes Logical Screen Descriptor with global color table
        */
       protected void writeLSDgct() throws IOException {
          // logical screen size
          writeShort(width);
          writeShort(height);
          // packed fields
          out.write((0x80 |       // 1   : global color table flag = 0 (nn
                   0x70 |         // 2-4 : color resolution = 7
                   0x00 |         // 5   : gct sort flag = 0
                   lctSize));        // 6-8 : gct size = 0

          out.write(0);           // background color index
          out.write(0);           // pixel aspect ratio - assume 1:1
       }
      
     /**
        * Writes Logical Screen Descriptor without global color table
        */
       protected void writeLSD() throws IOException {
          // logical screen size
          writeShort(width);
          writeShort(height);
          // packed fields
          out.write((0x00 |       // 1   : global color table flag = 0 (none)
                   0x70 |         // 2-4 : color resolution = 7
                   0x00 |         // 5   : gct sort flag = 0
                   0x00));        // 6-8 : gct size = 0

          out.write(0);           // background color index
          out.write(0);           // pixel aspect ratio - assume 1:1
       }


       /**
        * Writes Netscape application extension to define
        * repeat count.
        */
       protected void writeNetscapeExt() throws IOException {
          out.write(0x21);       // extension introducer
          out.write(0xff);       // app extension label
          out.write(11);         // block size
          writeString("NETSCAPE"+"2.0");    // app id + auth code
          out.write(3);          // sub-block size
          out.write(1);          // loop sub-block id
          writeShort(repeat);    // loop count (extra iterations, 0=repeat forever)
          out.write(0);          // block terminator
       }


       /**
        * Writes color table
        */
       protected void writePalette() throws IOException {
          out.write(colorTab, 0, colorTab.length);
          int n = (3 * 256) - colorTab.length;
          for (int i = 0; i < n; i++)
             out.write(0);
       }


       /**
        * Encodes and writes pixel data
        */
       protected void writePixels() throws IOException {
          LZWEncoder encoder = new LZWEncoder(width, height, indexedPixels, colorDepth);
          encoder.encode(out);
       }


       /**
        *    Write 16-bit value to output stream, LSB first
        */
       protected void writeShort(int value) throws IOException {
          out.write(value & 0xff);
          out.write((value >> 8) & 0xff);
       }


       /**
        * Writes string to output stream
        */
       protected void writeString(String s) throws IOException {
          for (int i = 0; i < s.length(); i++)
             out.write((byte) s.charAt(i));
       }
    }


//    ==============================================================================
//      Adapted from Jef Poskanzer's Java port by way of J. M. G. Elliott.
//      K Weiner 12/00


    class LZWEncoder {

      private static final int EOF = -1;

      private int     imgW, imgH;
      private byte[]  pixAry;
      private int     initCodeSize;
      private int     remaining;
      private int     curPixel;


      // GIFCOMPR.C       - GIF Image compression routines
      //
      // Lempel-Ziv compression based on 'compress'.  GIF modifications by
      // David Rowley (mgardi@watdcsu.waterloo.edu)

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

      int[] htab = new int[HSIZE];
      int[] codetab = new int[HSIZE];

      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
      //
      // 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;

      int masks[] = { 0x0000, 0x0001, 0x0003, 0x0007, 0x000F,
                  0x001F, 0x003F, 0x007F, 0x00FF,
                  0x01FF, 0x03FF, 0x07FF, 0x0FFF,
                  0x1FFF, 0x3FFF, 0x7FFF, 0xFFFF };

      // Number of characters so far in this 'packet'
      int a_count;

      // Define the storage for the packet accumulator
      byte[] accum = new byte[256];


      //----------------------------------------------------------------------------
      LZWEncoder(int width, int height, byte[] pixels, int color_depth) {
       imgW = width;
       imgH = height;
       pixAry = pixels;
       initCodeSize = Math.max(2, color_depth);
      }


      // 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, OutputStream outs ) throws IOException
         {
         accum[a_count++] = c;
         if ( a_count >= 254 )
            flush_char( outs );
         }


      // Clear out the hash table

      // table clear for block compress
      void cl_block( OutputStream outs ) throws IOException
         {
         cl_hash( hsize );
         free_ent = ClearCode + 2;
         clear_flg = true;

         output( ClearCode, outs );
         }


      // reset code table
      void cl_hash( int hsize )
         {
         for ( int i = 0; i < hsize; ++i )
            htab[i] = -1;
         }


      void compress( int init_bits, OutputStream outs ) throws IOException
         {
         int fcode;
         int i /* = 0 */;
         int c;
         int ent;
         int disp;
         int hsize_reg;
         int hshift;

         // Set up the globals:  g_init_bits - initial number of bits
         g_init_bits = init_bits;

         // Set up the necessary values
         clear_flg = false;
         n_bits = g_init_bits;
         maxcode = MAXCODE( n_bits );

         ClearCode = 1 << ( init_bits - 1 );
         EOFCode = ClearCode + 1;
         free_ent = ClearCode + 2;

         a_count = 0// clear packet

         ent = nextPixel();

         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, outs );

         outer_loop:
         while ( (c = nextPixel()) != EOF )
            {
            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 )
                  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, outs );
            ent = c;
            if ( free_ent < maxmaxcode )
               {
               codetab[i] = free_ent++;  // code -> hashtable
               htab[i] = fcode;
               }
            else
               cl_block( outs );
            }
         // Put out the final code.
         output( ent, outs );
         output( EOFCode, outs );
         }


      //----------------------------------------------------------------------------
      void encode(OutputStream os) throws IOException
      {
       os.write(initCodeSize);         // write "initial code size" byte

       remaining = imgW * imgH;        // reset navigation variables
       curPixel = 0;

       compress(initCodeSize + 1, os); // compress and write the pixel data

       os.write(0);                    // write block terminator
      }


      // Flush the packet to disk, and reset the accumulator
      void flush_char( OutputStream outs ) throws IOException
         {
         if ( a_count > 0 )
            {
            outs.write( a_count );
            outs.write( accum, 0, a_count );
            a_count = 0;
            }
         }


      final int MAXCODE( int n_bits )
         {
         return ( 1 << n_bits ) - 1;
         }


      //----------------------------------------------------------------------------
      // Return the next pixel from the image
      //----------------------------------------------------------------------------
      private int nextPixel()
      {
       if (remaining == 0)
         return EOF;

       --remaining;

       byte pix = pixAry[curPixel++];

       return pix & 0xff;
      }


      void output( int code, OutputStream outs ) throws IOException
         {
         cur_accum &= masks[cur_bits];

         if ( cur_bits > 0 )
            cur_accum |= ( code << cur_bits );
         else
            cur_accum = code;

         cur_bits += n_bits;

         while ( cur_bits >= 8 )
            {
            char_out( (byte) ( cur_accum & 0xff ), outs );
            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.
        if ( free_ent > maxcode || clear_flg )
            {
            if ( clear_flg )
               {
               maxcode = MAXCODE(n_bits = g_init_bits);
               clear_flg = false;
               }
            else
               {
               ++n_bits;
               if ( n_bits == maxbits )
                  maxcode = maxmaxcode;
               else
                  maxcode = MAXCODE(n_bits);
               }
            }

         if ( code == EOFCode )
            {
            // At EOF, write the rest of the buffer.
            while ( cur_bits > 0 )
               {
               char_out( (byte) ( cur_accum & 0xff ), outs );
               cur_accum >>= 8;
               cur_bits -= 8;
               }

            flush_char( outs );
            }
         }
    }
}
TOP

Related Classes of gov.lanl.adore.djatoka.io.writer.GIFWriter

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.