Changeset 1391


Ignore:
Timestamp:
08/31/06 08:10:19 (14 years ago)
Author:
melissa
Message:

Modified GIF reader to open animated GIFs; defaults to ImageIO reader if there is only one frame.

Location:
trunk/loci/formats
Files:
1 added
3 edited

Legend:

Unmodified
Added
Removed
  • trunk/loci/formats/in/GIFReader.java

    r1264 r1391  
    2525package loci.formats.in; 
    2626 
    27 import java.io.IOException; 
     27import java.awt.Rectangle; 
     28import java.awt.image.BufferedImage; 
     29import java.util.Vector; 
     30import java.io.*; 
    2831import loci.formats.*; 
    2932 
    3033/** 
    31  * GIFReader is the file format reader for 
    32  * Graphics Interchange Format (GIF) images. 
    33  * 
    34  * @author Curtis Rueden ctrueden at wisc.edu 
     34 * GIFReader is the file format reader for Graphics Interchange Format 
     35 * (GIF) files. 
    3536 */ 
    36 public class GIFReader extends ImageIOReader { 
     37public class GIFReader extends FormatReader { 
     38 
     39  // -- Constants -- 
     40 
     41  /** Status codes. */ 
     42  private static final int STATUS_OK = 0; 
     43  private static final int STATUS_FORMAT_ERROR = 1; 
     44  private static final int STATUS_OPEN_ERROR = 2; 
     45 
     46  /** Maximum buffer size. */ 
     47  private static final int MAX_STACK_SIZE = 4096; 
     48 
     49  // -- Fields -- 
     50 
     51  /** Current file. */ 
     52  protected RandomAccessStream in; 
     53 
     54  /** Number of frames. */ 
     55  private int numFrames; 
     56 
     57  /** Alternate GIF reader, for single frame files. */ 
     58  private LegacyGIFReader legacy; 
     59 
     60  private int status; 
     61  private int width;    // full image width 
     62  private int height;   // full image height 
     63  private boolean gctFlag;      // global color table used 
     64  private int gctSize;          // size of global color table 
     65  private int loopCount;        // iterations; 0 = repeat forever 
     66 
     67  private int[] gct;    // global color table 
     68  private int[] lct;    // local color table 
     69  private int[] act;    // active color table 
     70 
     71  private int bgIndex;  // background color index 
     72  private int bgColor;  // background color 
     73  private int lastBgColor; // previous bg color 
     74  private int pixelAspect; // pixel aspect ratio 
     75 
     76  private boolean lctFlag; // local color table flag 
     77  private boolean interlace; // interlace flag 
     78  private int lctSize;       // local color table size 
     79 
     80  private int ix, iy, iw, ih; // current image rectangle 
     81  private Rectangle lastRect; // last image rectangle 
     82 
     83  private byte[] block = new byte[256]; // current data block 
     84  private int blockSize = 0; // block size 
     85 
     86  private int dispose = 0; 
     87  private int lastDispose = 0; 
     88  private boolean transparency = false; // use transparent color 
     89  private int delay = 0; // delay in ms 
     90  private int transIndex; // transparent color index 
     91 
     92  // LZW working arrays 
     93  private short[] prefix; 
     94  private byte[] suffix; 
     95  private byte[] pixelStack; 
     96  private byte[] pixels; 
     97 
     98  private Vector images; 
    3799 
    38100  // -- Constructor -- 
    39101 
    40   /** Constructs a new GIFReader. */ 
    41   public GIFReader() { super("Graphics Interchange Format", "gif"); } 
    42  
     102  /** Constructs a new GIF reader. */ 
     103  public GIFReader() { 
     104    super("Graphics Interchange Format (GIF)", "gif"); 
     105  } 
     106 
     107  // -- FormatReader API methods -- 
     108 
     109  /** Checks if the given block is a valid header for a GIF file. */ 
     110  public boolean isThisType(byte[] block) { 
     111    return false; 
     112  } 
     113 
     114  /** Determines the number of images in the given GIF file. */ 
     115  public int getImageCount(String id) throws FormatException, IOException { 
     116    if (!id.equals(currentId)) initFile(id); 
     117    return separated ? 3 * numFrames : numFrames; 
     118  } 
     119 
     120  /** Checks if the images in the file are RGB. */ 
     121  public boolean isRGB(String id) throws FormatException, IOException { 
     122    return true; 
     123  } 
     124 
     125  /** Get the size of the X dimension. */ 
     126  public int getSizeX(String id) throws FormatException, IOException { 
     127    if (!id.equals(currentId)) initFile(id); 
     128    return width; 
     129  } 
     130 
     131  /** Get the size of the Y dimension. */ 
     132  public int getSizeY(String id) throws FormatException, IOException { 
     133    if (!id.equals(currentId)) initFile(id); 
     134    return height; 
     135  } 
     136 
     137  /** Get the size of the Z dimension. */ 
     138  public int getSizeZ(String id) throws FormatException, IOException { 
     139    return 1; 
     140  } 
     141 
     142  /** Get the size of the C dimension. */ 
     143  public int getSizeC(String id) throws FormatException, IOException { 
     144    return 3; 
     145  } 
     146 
     147  /** Get the size of the T dimension. */ 
     148  public int getSizeT(String id) throws FormatException, IOException { 
     149    if (!id.equals(currentId)) initFile(id); 
     150    return numFrames; 
     151  } 
     152 
     153  /** Return true if the data is in little-endian format. */ 
     154  public boolean isLittleEndian(String id) throws FormatException, IOException { 
     155    return true; 
     156  } 
     157 
     158  /** 
     159   * Return a five-character string representing the dimension order 
     160   * within the file. 
     161   */ 
     162  public String getDimensionOrder(String id) throws FormatException, IOException 
     163  { 
     164    return "XYCTZ"; 
     165  } 
     166 
     167  /** Obtains the specified image from the given GIF file as a byte array. */ 
     168  public byte[] openBytes(String id, int no) throws FormatException, IOException 
     169  { 
     170    if (!id.equals(currentId)) initFile(id); 
     171    if (numFrames == 1) return legacy.openBytes(id, no); 
     172 
     173    if (no < 0 || no >= getImageCount(id)) { 
     174      throw new FormatException("Invalid image number: " + no); 
     175    } 
     176 
     177    byte[] bytes = (byte[]) images.get(no); 
     178    int[] dest = new int[width * height]; 
     179 
     180    byte[] last = null; 
     181    if (no > 0) last = openBytes(id, no - 1); 
     182 
     183    if (lastDispose > 0) { 
     184      if (lastDispose == 3) { // use image before last 
     185        int n = numFrames - 2; 
     186        if (n > 0) last = openBytes(id, no - 2); 
     187        else last = null; 
     188      } 
     189 
     190      if (last != null) { 
     191        int[] prev = new int[width * height]; 
     192        for (int i=0; i<prev.length; i++) { 
     193          byte[] s = new byte[3]; 
     194          s[0] = last[i]; 
     195          s[1] = last[i + prev.length]; 
     196          s[2] = last[i + 2*prev.length]; 
     197          prev[i] = DataTools.bytesToInt(s, false); 
     198        } 
     199 
     200        System.arraycopy(prev, 0, dest, 0, width * height); 
     201 
     202        if ((lastDispose == 2) && (lastBgColor != 0)) { 
     203          int rowStart = (int) lastRect.getY(); 
     204          int rowEnd = (int) (rowStart + lastRect.getHeight()); 
     205          int colStart = (int) lastRect.getX(); 
     206          int colEnd = (int) (colStart + lastRect.getWidth()); 
     207 
     208          for (int i=rowStart; i<rowEnd; i++) { 
     209            for (int j=colStart; j<colEnd; j++) { 
     210              dest[width * i + j] = lastBgColor; 
     211            } 
     212          } 
     213        } 
     214      } 
     215 
     216      int pass = 1; 
     217      int inc = 8; 
     218      int iline = 0; 
     219      for (int i=0; i<ih; i++) { 
     220        int line = i; 
     221        if (interlace) { 
     222          if (iline >= ih) { 
     223            pass++; 
     224            switch (pass) { 
     225              case 2: iline = 4; break; 
     226              case 3: iline = 2; inc = 4; break; 
     227              case 4: iline = 1; inc = 2; 
     228            } 
     229          } 
     230          line = iline; 
     231          iline += inc; 
     232        } 
     233        line += iy; 
     234        if (line < height) { 
     235          int k = line * width; 
     236          int dx = k + ix; 
     237          int dlim = dx + iw; 
     238          if ((k + width) < dlim) dlim = k + width; 
     239          int sx = i * iw; 
     240          while (dx < dlim) { 
     241            // map color and insert in destination 
     242            int ndx = ((int) bytes[sx++]) & 0xff; 
     243            dest[dx++] = act[ndx]; 
     244          } 
     245        } 
     246      } 
     247    } 
     248 
     249    // convert int array to byte array 
     250 
     251    if (separated) { 
     252      byte[] b = new byte[dest.length]; 
     253      for (int i=0; i<dest.length; i++) { 
     254        b[i] = (byte) (dest[i] & (0xff0000 << (2 * (no % 3)) ) << 8*(no % 3)); 
     255      } 
     256      return b; 
     257    } 
     258    else { 
     259      byte[] b = new byte[dest.length * 3]; 
     260      for (int i=0; i<dest.length; i++) { 
     261        b[i] = (byte) ((dest[i] & 0xff0000) << 16); 
     262        b[i + dest.length] = (byte) ((dest[i] & 0xff00) << 8); 
     263        b[i + 2*dest.length] = (byte) (dest[i] & 0xff); 
     264      } 
     265      return b; 
     266    } 
     267  } 
     268 
     269  /** Obtains the specified image from the given GIF file. */ 
     270  public BufferedImage openImage(String id, int no) 
     271    throws FormatException, IOException 
     272  { 
     273    if (!id.equals(currentId)) initFile(id); 
     274    if (numFrames == 1) return legacy.openImage(id, no); 
     275 
     276    if (no < 0 || no >= getImageCount(id)) { 
     277      throw new FormatException("Invalid image number: " + no); 
     278    } 
     279 
     280    byte[] b = openBytes(id, no); 
     281    return ImageTools.makeImage(b, width, height, separated ? 1 : 3, 
     282      false, 1, true); 
     283  } 
     284 
     285  /** Closes any open files. */ 
     286  public void close() throws FormatException, IOException { 
     287    if (in != null) in.close(); 
     288    in = null; 
     289    currentId = null; 
     290  } 
     291 
     292  /** Initializes the given GIF file. */ 
     293  protected void initFile(String id) throws FormatException, IOException { 
     294    super.initFile(id); 
     295 
     296    legacy = new LegacyGIFReader(); 
     297 
     298    status = STATUS_OK; 
     299    in = new RandomAccessStream(id); 
     300    images = new Vector(); 
     301 
     302    byte[] buf = new byte[6]; 
     303    in.read(buf); 
     304    String ident = new String(buf); 
     305 
     306    if (!ident.startsWith("GIF")) { 
     307      throw new FormatException("Not a valid GIF file."); 
     308    } 
     309 
     310    width = DataTools.read2UnsignedBytes(in, true); 
     311    height = DataTools.read2UnsignedBytes(in, true); 
     312 
     313    int packed = DataTools.readUnsignedByte(in); 
     314    gctFlag = (packed & 0x80) != 0; 
     315    gctSize = 2 << (packed & 7); 
     316    bgIndex = DataTools.readUnsignedByte(in); 
     317    pixelAspect = DataTools.readUnsignedByte(in); 
     318 
     319    if (gctFlag) { 
     320      int nbytes = 3 * gctSize; 
     321      byte[] c = new byte[nbytes]; 
     322      int n = in.read(c); 
     323 
     324      gct = new int[256]; 
     325      int i = 0; 
     326      int j = 0; 
     327      while (i < gctSize) { 
     328        int r = ((int) c[j++]) & 0xff; 
     329        int g = ((int) c[j++]) & 0xff; 
     330        int b = ((int) c[j++]) & 0xff; 
     331        gct[i++] = 0xff000000 | (r << 16) | (g << 8) | b; 
     332      } 
     333    } 
     334 
     335    bgColor = gct[bgIndex]; 
     336 
     337    boolean done = false; 
     338    while (!done) { 
     339      int code = DataTools.readUnsignedByte(in); 
     340      switch (code) { 
     341        case 0x2c: // image separator: 
     342          ix = DataTools.read2UnsignedBytes(in, true); 
     343          iy = DataTools.read2UnsignedBytes(in, true); 
     344          iw = DataTools.read2UnsignedBytes(in, true); 
     345          ih = DataTools.read2UnsignedBytes(in, true); 
     346 
     347          packed = DataTools.readUnsignedByte(in); 
     348          lctFlag = (packed & 0x80) != 0; 
     349          interlace = (packed & 0x40) != 0; 
     350          lctSize = 2 << (packed & 7); 
     351 
     352          if (lctFlag) { 
     353            int nbytes = 3 * lctSize; 
     354            byte[] c = new byte[nbytes]; 
     355            int n = 0; 
     356            try { n = in.read(c); } 
     357            catch (IOException e) { } 
     358 
     359            if (n < nbytes) { 
     360              throw new FormatException("Local color table not found"); 
     361            } 
     362 
     363            lct = new int[256]; 
     364            int i = 0; 
     365            int j = 0; 
     366            while (i < lctSize) { 
     367              int r = ((int) c[j++]) & 0xff; 
     368              int g = ((int) c[j++]) & 0xff; 
     369              int b = ((int) c[j++]) & 0xff; 
     370              lct[i++] = 0xff000000 | (r << 16) + (g << 8) | b; 
     371            } 
     372 
     373            act = lct; 
     374          } 
     375          else { 
     376            act = gct; 
     377            if (bgIndex == transIndex) bgColor = 0; 
     378          } 
     379 
     380          int save = 0; 
     381 
     382          if (transparency) { 
     383            save = act[transIndex]; 
     384            act[transIndex] = 0; 
     385          } 
     386 
     387          if (act == null) throw new FormatException("Color table not found."); 
     388 
     389          decodeImageData(); 
     390 
     391          int check = 0; 
     392          do { check = readBlock(); } 
     393          while (blockSize > 0 && check != -1); 
     394 
     395          numFrames++; 
     396 
     397          if (transparency) act[transIndex] = save; 
     398 
     399          lastDispose = dispose; 
     400          lastRect = new Rectangle(ix, iy, iw, ih); 
     401          lastBgColor = bgColor; 
     402          lct = null; 
     403 
     404          break; 
     405        case 0x21: // extension 
     406          code = DataTools.readUnsignedByte(in); 
     407          switch (code) { 
     408            case 0xf9: // graphics control extension 
     409              in.read(); 
     410              packed = DataTools.readUnsignedByte(in); 
     411              dispose = (packed & 0x1c) >> 1; 
     412              transparency = (packed & 1) != 0; 
     413              delay = DataTools.read2UnsignedBytes(in, true) * 10; 
     414              transIndex = DataTools.readUnsignedByte(in); 
     415              in.read(); 
     416              break; 
     417            case 0xff:  // application extension 
     418              if (readBlock() == -1) { 
     419                done = true; 
     420                break; 
     421              } 
     422 
     423              String app = new String(block, 0, 11); 
     424              if (app.equals("NETSCAPE2.0")) { 
     425                do { 
     426                  check = readBlock(); 
     427                  if (block[0] == 0x03) { 
     428                    int b1 = ((int) block[1]) & 0xff; 
     429                    int b2 = ((int) block[2]) & 0xff; 
     430                    loopCount = (b2 << 8) | b1; 
     431                  } 
     432                } 
     433                while (blockSize > 0 && check != -1); 
     434              } 
     435              else { 
     436                do { 
     437                  check = readBlock(); 
     438                } 
     439                while (blockSize > 0 && check != -1); 
     440              } 
     441              break; 
     442            default: 
     443              do { 
     444                check = readBlock(); 
     445              } 
     446              while (blockSize > 0 && check != -1); 
     447          } 
     448          break; 
     449        case 0x3b: // terminator 
     450          done = true; 
     451          break; 
     452      } 
     453    } 
     454  } 
     455 
     456  // -- Helper methods -- 
     457 
     458  /** Reads the next variable length block. */ 
     459  private int readBlock() throws IOException { 
     460    if (in.getFilePointer() == in.length()) return -1; 
     461    blockSize = DataTools.readUnsignedByte(in); 
     462    int n = 0; 
     463    int count; 
     464 
     465    if (blockSize > 0) { 
     466      try { 
     467        while (n < blockSize) { 
     468          count = in.read(block, n, blockSize - n); 
     469          if (count == -1) break; 
     470          n += count; 
     471        } 
     472      } 
     473      catch (IOException e) { } 
     474    } 
     475    return n; 
     476  } 
     477 
     478  /** Decodes LZW image data into a pixel array.  Adapted from ImageMagick. */ 
     479  private void decodeImageData() throws IOException { 
     480    int nullCode = -1; 
     481    int npix = iw * ih; 
     482 
     483    int available, clear, codeMask, codeSize, eoi, inCode, oldCode, bits, code, 
     484      count, i, datum, dataSize, first, top, bi, pi; 
     485 
     486    if (pixels == null || pixels.length < npix) pixels = new byte[npix]; 
     487 
     488    if (prefix == null) prefix = new short[MAX_STACK_SIZE]; 
     489    if (suffix == null) suffix = new byte[MAX_STACK_SIZE]; 
     490    if (pixelStack == null) pixelStack = new byte[MAX_STACK_SIZE + 1]; 
     491 
     492    // initialize GIF data stream decoder 
     493 
     494    dataSize = DataTools.readUnsignedByte(in); 
     495 
     496    clear = 1 << dataSize; 
     497    eoi = clear + 1; 
     498    available = clear + 2; 
     499    oldCode = nullCode; 
     500    codeSize = dataSize + 1; 
     501    codeMask = (1 << codeSize) - 1; 
     502    for (code=0; code < clear; code++) { 
     503      prefix[code] = 0; 
     504      suffix[code] = (byte) code; 
     505    } 
     506 
     507    // decode GIF pixel stream 
     508 
     509    datum = bits = count = first = top = pi = bi = 0; 
     510 
     511    for (i=0; i<npix; ) { 
     512      if (top == 0) { 
     513        if (bits < codeSize) { 
     514          if (count == 0) { 
     515            count = readBlock(); 
     516            if (count <= 0) break; 
     517            bi = 0; 
     518          } 
     519          datum += (((int) block[bi]) & 0xff) << bits; 
     520          bits += 8; 
     521          bi++; 
     522          count--; 
     523          continue; 
     524        } 
     525 
     526        // get the next code 
     527        code = datum & codeMask; 
     528        datum >>= codeSize; 
     529        bits -= codeSize; 
     530 
     531        // interpret the code 
     532 
     533        if ((code > available) || (code == eoi)) { 
     534          break; 
     535        } 
     536        if (code == clear) { 
     537          // reset the decoder 
     538          codeSize = dataSize + 1; 
     539          codeMask = (1 << codeSize) - 1; 
     540          available = clear + 2; 
     541          oldCode = nullCode; 
     542          continue; 
     543        } 
     544 
     545        if (oldCode == nullCode) { 
     546          pixelStack[top++] = suffix[code]; 
     547          oldCode = code; 
     548          first = code; 
     549          continue; 
     550        } 
     551 
     552        inCode = code; 
     553        if (code == available) { 
     554          pixelStack[top++] = (byte) first; 
     555          code = oldCode; 
     556        } 
     557 
     558        while (code > clear) { 
     559          pixelStack[top++] = suffix[code]; 
     560          code = prefix[code]; 
     561        } 
     562        first = ((int) suffix[code]) & 0xff; 
     563 
     564        if (available >= MAX_STACK_SIZE) break; 
     565        pixelStack[top++] = (byte) first; 
     566        prefix[available] = (short) oldCode; 
     567        suffix[available] = (byte) first; 
     568        available++; 
     569 
     570        if (((available & codeMask) == 0) && (available < MAX_STACK_SIZE)) { 
     571          codeSize++; 
     572          codeMask += available; 
     573        } 
     574        oldCode = inCode; 
     575      } 
     576      top--; 
     577      pixels[pi++] = pixelStack[top]; 
     578      i++; 
     579    } 
     580 
     581    for (i=pi; i<npix; i++) pixels[i] = 0; 
     582    images.add(pixels); 
     583  } 
    43584 
    44585  // -- Main method -- 
  • trunk/loci/formats/readers.txt

    r1266 r1391  
    2424loci.formats.in.PNGReader         # png [javax.imageio] 
    2525loci.formats.in.JPEGReader        # jpg [javax.imageio] 
    26 loci.formats.in.GIFReader         # gif [javax.imageio] 
    2726 
    2827# standalone readers with unique file extensions 
     28loci.formats.in.GIFReader         # gif 
    2929loci.formats.in.BMPReader         # bmp 
    3030loci.formats.in.DicomReader       # dcm, dicom 
  • trunk/loci/formats/todo.txt

    r1387 r1391  
    77 
    88API: 
    9 * Methods for switching between multiple image series: 
    10   - getSeriesCount(String id) returns number of series available 
    11   - setSeries(int no) switches which series is "active" (default is #0) 
    12   - all other methods operate on the active series 
    139* Methods for handling thumbnails: 
    1410  - openThumbImage(String id, int no) - same as openImage, but for thumbnails 
     
    1915* SDT (java/loci/apps/slim/SlimPlotter.java, data/sdt, data/specs/sdt) 
    2016* JPEG-2000 formats: .jp2, .jpc, .j2k, etc. 
    21 * animated GIF reader (steal code from ImageJ plugin) 
    2217* MNG (http://www.libpng.org/pub/mng/) -- low priority 
    2318 
Note: See TracChangeset for help on using the changeset viewer.