Revision: 20094
          http://sourceforge.net/p/jmol/code/20094
Author:   hansonr
Date:     2014-11-09 22:12:09 +0000 (Sun, 09 Nov 2014)
Log Message:
-----------
Jmol.___JmolVersion="14.3.8_2014.11.09"

new feature: CAPTURE "filename0000.png"  
  -- captures set of PNG files
  -- 0000 is not required
  
new feature: CAPTURE "filename0000.gif"  
  -- captures set of GIF files
  -- 0000 IS required in order to distinguish this from animated GIF
  
bug fix: GIF writer not properly handling large numbers of colors
  -- use of CIE L*a*b gives GIMP-like color quantification

bug fix: WRITE command should remove "t" or "j" in WRITE xxx.PNGJ, WRITE 
xxx.PNGT, WRITE xxx.GIFT
  -- specifically when no PNGJ, GIFT, or PNGT designation is made.
  
bug fix: PDB reader limited to 20 connections per atom
 
code: code clean-up in GData, Graphics3D, and Export3D
code: PDB reader CONECT efficiency

Modified Paths:
--------------
    trunk/Jmol/src/javajs/img/GifEncoder.java
    trunk/Jmol/src/javajs/util/OC.java
    trunk/Jmol/src/org/jmol/awtjs2d/JSFile.java
    trunk/Jmol/src/org/jmol/io/JmolUtil.java
    trunk/Jmol/src/org/jmol/scriptext/CmdExt.java
    trunk/Jmol/src/org/jmol/viewer/FileManager.java
    trunk/Jmol/src/org/jmol/viewer/Jmol.properties
    trunk/Jmol/src/org/jmol/viewer/OutputManager.java
    trunk/Jmol/src/org/jmol/viewer/OutputManagerAwt.java

Modified: trunk/Jmol/src/javajs/img/GifEncoder.java
===================================================================
--- trunk/Jmol/src/javajs/img/GifEncoder.java   2014-11-08 17:41:04 UTC (rev 
20093)
+++ trunk/Jmol/src/javajs/img/GifEncoder.java   2014-11-09 22:12:09 UTC (rev 
20094)
@@ -22,10 +22,11 @@
  *  Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
  */
 
-// useful page: http://www.htmlhexcolor.com/0ac906 
+//  Final encoding code from 
http://acme.com/resources/classes/Acme/JPM/Encoders/GifEncoder.java
 //
 //  GifEncoder - write out an image as a GIF
 // 
+// 
 //  Transparency handling and variable bit size courtesy of Jack Palevich.
 //  
 //  Copyright (C)1996,1998 by Jef Poskanzer <j...@mail.acme.com>. All rights 
reserved.
@@ -65,27 +66,30 @@
 
 import javajs.util.CU;
 import javajs.util.Lst;
+import javajs.util.M3;
 import javajs.util.P3;
 
-import java.util.Arrays;
 import java.util.Hashtable;
 import java.util.Map;
 import java.io.IOException;
 
 /**
  * 
- * GifEncoder extensively modified for Jmol by Bob Hanson
+ * GifEncoder extensively adapted for Jmol by Bob Hanson
  * 
- * -- using median-cut with rgb
+ * Color quantization roughly follows the GIMP method
+ * "dither Floyd-Steinberg standard" but with some twists.
+ * (For example, we exclude the background color.)
  * 
- * -- adds adaptive color reduction to generate 256 colors using the median-cut
- * algorithm. Some problems still with systems having > 2000 colors.
+ * Note that although GIMP code annotation refers to "median-cut", 
+ * it is really uses MEAN-cut. That is what I use here as well.
  * 
- * -- TODO use median-cut with HSL
+ * -- commented code allows visualization of the color space using Jmol. Very
+ * enlightening!
  * 
  * -- much simplified interface with ImageEncoder
  * 
- * -- uses simple Hashtable with Integer()
+ * -- uses simple Hashtable with Integer() to catalog colors
  * 
  * -- allows progressive production of animated GIF via Jmol CAPTURE command
  * 
@@ -94,32 +98,32 @@
  * 
  * -- allows JavaScript port
  * 
- * -- Bob Hanson, 24 Sep 2013
+ * -- Bob Hanson, first try: 24 Sep 2013; final coding: 9 Nov 2014
  * 
+ * 
  * @author Bob Hanson hans...@stolaf.edu
  */
 
 public class GifEncoder extends ImageEncoder {
 
-  private int bitsPerPixel = 1;
-  private P3[] errors;
-  private P3[] pixelsLab;
+  private Map<String, Object> params;
+  private P3[] palette;
+  private int backgroundColor;
+
   private boolean interlaced;
   private boolean addHeader = true;
   private boolean addImage = true;
   private boolean addTrailer = true;
-  private int delayTime100ths = -1;
-  private boolean looping;
-  private Map<String, Object> params;
-  private int byteCount;
   private boolean isTransparent;
   private boolean floydSteinberg = true;
-  int backgroundColor;
-  Map<Integer, ColorCell> colorMap;
-  Map<Integer, ColorCell> colors256;
-  int[] red, green, blue;
-  int[] indexes;
+  private boolean capturing;
+  private boolean looping;
 
+  private int delayTime100ths = -1;
+  private int bitsPerPixel = 1;
+
+  private int byteCount;
+
   /**
    * we allow for animated GIF by being able to re-enter the code with 
different
    * parameters held in params
@@ -139,26 +143,19 @@
       isTransparent = true;
     }
 
-    //floydSteinberg = false;
-
-    logging = true;
-
     interlaced = (Boolean.TRUE == params.get("interlaced"));
-    if (interlaced 
-        || params.containsKey("captureRootExt") // file0000.gif 
-        || !params.containsKey("captureMode"))  // animated gif
+    if (params.containsKey("captureRootExt") // file0000.gif 
+        || !params.containsKey("captureMode")) // animated gif
       return;
+    interlaced = false;
+    capturing = true;
     try {
       byteCount = ((Integer) params.get("captureByteCount")).intValue();
     } catch (Exception e) {
       // ignore
     }
-    int imode = "maec".indexOf(((String) params.get("captureMode")).substring(
-        0, 1));
-
-    if (logging)
-      System.out.println("GIF capture mode " + imode);
-    switch (imode) {
+    switch ("maec"
+        .indexOf(((String) params.get("captureMode")).substring(0, 1))) {
     case 0: //"movie"
       params.put("captureMode", "add");
       addImage = false;
@@ -183,15 +180,41 @@
     }
   }
 
+  @Override
+  protected void generate() throws IOException {
+    if (addHeader)
+      writeHeader();
+    addHeader = false; // only one header
+    if (addImage) {
+      createPalette();
+      writeGraphicControlExtension();
+      if (delayTime100ths >= 0 && looping)
+        writeNetscapeLoopExtension();
+      writeImage();
+    }
+  }
+
+  @Override
+  protected void close() {
+    if (addTrailer) {
+      writeTrailer();
+    } else {
+      doClose = false;
+    }
+    if (capturing)
+      params.put("captureByteCount", Integer.valueOf(byteCount));
+  }
+
+  //////////////  256-color quantization  //////////////
+
   private class ColorItem {
 
     int rgb;
     P3 lab;
-    int count;
 
     ColorItem(int rgb) {
       this.rgb = rgb;
-      lab = toXYZ(rgb);
+      lab = toLAB(rgb);
     }
 
     @Override
@@ -200,72 +223,18 @@
     }
   }
 
-  protected class ColorVector extends Lst<ColorItem> {
-
-    private Lst<ColorCell> boxes;
-
-    void indexColors() {
-      // goal is to create an index set and and to generate rgb errors for 
each color
-      boxes = new Lst<ColorCell>();
-      // start with just two boxes -- fixed background color and all others
-      ColorCell cc = new ColorCell(0);
-      cc.addItem(new ColorItem(backgroundColor));
-      boxes.addLast(cc);
-      cc = new ColorCell(1);
-      for (int i = size(); --i >= 0;) {
-        ColorItem c = get(i);
-        if (c.rgb != backgroundColor)
-          cc.addItem(c);
-      }
-      boxes.addLast(cc);
-      int n;
-      while ((n = boxes.size()) < 256 && splitBoxes()) {
-        // loop
-      }
-      clear();
-      colorMap = new Hashtable<Integer, ColorCell>();
-      colors256 = new Hashtable<Integer, ColorCell>();
-      for (int i = 0; i < n; i++)
-        addLast(boxes.get(i).average());
-      for (int i = 0; i < n; i++)
-        boxes.get(i).setErrors();
-    }
-
-    private boolean splitBoxes() {
-      int n = boxes.size();
-      float maxVol = 0;
-      int imax = -1;
-      for (int i = n; --i >= 1;) {
-        float v = boxes.get(i).getVolume();
-        if (v > maxVol) {
-          maxVol = v;
-          imax = i;
-        }
-      }
-      if (imax < 0)
-        return false;
-      boxes.get(imax).splitBox(boxes);
-      return true;
-    }
-
-  }
-
   private class ColorCell {
     protected int index;
     // counts here are counts of color occurances for this grouped set.
     // ints here allow for 2147483647/0x100 = count of 8388607 for THIS 
average color, which should be fine.
-    private P3 xyz;
+    protected P3 lab;
     // min and max based on 0 0 0 for this rgb
-    private float maxr = Integer.MAX_VALUE, minr = -Integer.MAX_VALUE,
-        maxg = Integer.MAX_VALUE, ming = -Integer.MAX_VALUE,
-        maxb = Integer.MAX_VALUE, minb = -Integer.MAX_VALUE;
-    private float rmaxr = -Integer.MAX_VALUE, rminr = Integer.MAX_VALUE,
-        rmaxg = -Integer.MAX_VALUE, rming = Integer.MAX_VALUE,
-        rmaxb = -Integer.MAX_VALUE, rminb = Integer.MAX_VALUE;
-    private float maxre = Integer.MAX_VALUE, minre = -Integer.MAX_VALUE,
-        maxge = Integer.MAX_VALUE, minge = -Integer.MAX_VALUE,
-        maxbe = Integer.MAX_VALUE, minbe = -Integer.MAX_VALUE;
-    private ColorCell nextr, prevr, nextg, prevg, nextb, prevb;
+//    private float maxr = Integer.MAX_VALUE, minr = -Integer.MAX_VALUE,
+//        maxg = Integer.MAX_VALUE, ming = -Integer.MAX_VALUE,
+//        maxb = Integer.MAX_VALUE, minb = -Integer.MAX_VALUE;
+//    private float rmaxr = -Integer.MAX_VALUE, rminr = Integer.MAX_VALUE,
+//        rmaxg = -Integer.MAX_VALUE, rming = Integer.MAX_VALUE,
+//        rmaxb = -Integer.MAX_VALUE, rminb = Integer.MAX_VALUE;
     int rgb;
     Lst<ColorItem> lst;
     private float volume;
@@ -280,403 +249,482 @@
         return volume;
       if (lst.size() < 2)
         return -1;
-      float d;
-      rmaxr = -Integer.MAX_VALUE;
-      rminr = Integer.MAX_VALUE;
-      rmaxg = -Integer.MAX_VALUE;
-      rming = Integer.MAX_VALUE;
-      rmaxb = -Integer.MAX_VALUE;
-      rminb = Integer.MAX_VALUE;
+      //if (true)
+      //return lst.size();
+      //float d;
+      float maxx = -Integer.MAX_VALUE;
+      float minx = Integer.MAX_VALUE;
+      float maxy = -Integer.MAX_VALUE;
+      float miny = Integer.MAX_VALUE;
+      float maxz = -Integer.MAX_VALUE;
+      float minz = Integer.MAX_VALUE;
       int n = lst.size();
       for (int i = n; --i >= 0;) {
         P3 xyz = lst.get(i).lab;
-        if (xyz.x < rminr)
-          rminr = xyz.x;
-        if (xyz.y < rming)
-          rming = xyz.y;
-        if (xyz.z < rminb)
-          rminb = xyz.z;
-        if (xyz.x > rmaxr)
-          rmaxr = xyz.x;
-        if (xyz.y > rmaxg)
-          rmaxg = xyz.y;
-        if (xyz.z > rmaxb)
-          rmaxb = xyz.z;
+        if (xyz.x < minx)
+          minx = xyz.x;
+        if (xyz.y < miny)
+          miny = xyz.y;
+        if (xyz.z < minz)
+          minz = xyz.z;
+        if (xyz.x > maxx)
+          maxx = xyz.x;
+        if (xyz.y > maxy)
+          maxy = xyz.y;
+        if (xyz.z > maxz)
+          maxz = xyz.z;
       }
-      return volume = ((d = (rmaxr - rminr)/RFACTOR) * d + (d = rmaxg - rming) 
* d + (d = rmaxb
-          - rminb)
-          * d);
+      float dx = (maxx - minx);
+      float dy = (maxy - miny);
+      float dz = (maxz - minz);
+      return volume = dx * dx + dy * dy + dz * dz;
     }
 
-    void setErrors() {
-      if (nextr != null)
-        maxre = ((nextr.minr + maxr) / 2) - xyz.x;
-      if (nextg != null)
-        maxge = ((nextg.ming + maxg) / 2) - xyz.y;
-      if (nextb != null)
-        maxbe = ((nextb.minb + maxb) / 2) - xyz.z;
-      if (prevr != null)
-        minre = ((prevr.maxr + minr) / 2) - xyz.x;
-      if (prevg != null)
-        minge = ((prevg.maxg + ming) / 2) - xyz.y;
-      if (prevb != null)
-        minbe = ((prevb.maxb + minb) / 2) - xyz.z;
-    }
-
     void addItem(ColorItem c) {
       lst.addLast(c);
     }
 
-    ColorItem average() {
+    /**
+     * Set the average L*a*b value for this box
+     * 
+     * @return RGB point
+     * 
+     */
+    protected P3 setColor() {
       int count = lst.size();
-      xyz = new P3();
+      lab = new P3();
       for (int i = count; --i >= 0;) {
-        ColorItem c = lst.get(i);
-        colorMap.put(Integer.valueOf(c.rgb), this);
-        xyz.add(c.lab);
+        lab.add(lst.get(i).lab);
       }
-      xyz.scale(1f / count);
-      P3 ptrgb = toRGB(xyz);
+      lab.scale(1f / count);
+      P3 ptrgb = toRGB(lab);
       rgb = CU.colorPtToFFRGB(ptrgb);
-      red[index] = (int) ptrgb.x;
-      green[index] = (int) ptrgb.y;
-      blue[index] = (int) ptrgb.z;
-      /*
-      for (int i = size(); --i >= 0;) {
-        int rgb = get(i).rgb;
-        r = (rgb & 0xFCFCFC)>> 2;
-        System.out.println("draw id 'd"+index+"_"+i+"' width 0.5 " + 
CU.colorPtFromInt(r, null) + " color "+CU.colorPtFromInt(rgb, null)+"");
 
-      }
-      r = (rgb & 0xFCFCFC)>> 2;      
-      System.out.println("draw id 'c"+index+"' width 1.0 " + 
CU.colorPtFromInt(r, null) + " color "+CU.colorPtFromInt(rgb, null)+"");
-      
-      */
-      colors256.put(Integer.valueOf(rgb), this);
-      System.out.println(index + " " + Integer.toHexString(rgb) + " " + ptrgb 
+ " " + xyz + " " + (maxr - minr)+ " " + (maxg - ming) + " " + (maxb-minb));
-      return new ColorItem(rgb);
+      //for (int i = 0; i < count; i++) {
+      // drawPt(index, i+1, lst.get(i).rgb, false);
+      // }
+
+      //      drawPt(index, 0, rgb, true);
+      //      System.out.println("boundbox corners { " + Math.max(minr, 0) + " 
"
+      //          + Math.max(ming, 0) + " " + Math.max(minb, 0) + "}{ "
+      //          + Math.min(maxr, 100) + " " + Math.min(maxg, 100) + " "
+      //          + Math.min(maxb, 100) + "}");
+      //      System.out.println("draw d" + index + " boundbox color "
+      //          + CU.colorPtFromInt(rgb, null) + " mesh nofill");
+      //      System.out.println("//" + index + " " + volume);
+
+      //System.out.println(index + " " + Integer.toHexString(rgb) + " " + 
ptrgb + " " + xyz + " " + (maxr - minr)+ " " + (maxg - ming) + " " + 
(maxb-minb));
+      return ptrgb;
     }
 
-    private float[] ar, ag, ab;
-
     /**
      * use median_cut algorithm to split the box, creating a doubly linked 
list.
      * 
      * Paul Heckbert, MIT thesis COLOR IMAGE QUANTIZATION FOR FRAME BUFFER
      * DISPLAY https://www.cs.cmu.edu/~ph/ciq_thesis
      * 
+     * except, as in GIMP, we use mean, not median here.
+     * 
      * @param boxes
+     * @return true if split
      */
-    protected void splitBox(Lst<ColorCell> boxes) {
+    protected boolean splitBox(Lst<ColorCell> boxes) {
       int n = lst.size();
       if (n < 2)
-        return;
+        return false;
       int newIndex = boxes.size();
       ColorCell newBox = new ColorCell(newIndex);
       boxes.addLast(newBox);
-      for (int i = 0; i < 3; i++)
-        getArray(i);
-      float ranger = (ar[n - 1] - ar[0]) / RFACTOR;
-      float rangeg = ag[n - 1] - ag[0];
-      float rangeb = ab[n - 1] - ab[0];
-      int mode = (ranger >= rangeg ? (ranger >= rangeb ? 0 : 2)
-          : rangeg >= rangeb ? 1 : 2);
-      float[] a = (mode == 0 ? ar : mode == 1 ? ag : ab);
-      int median = n / 2;
-      float val = a[median];
-      int dir = (val == a[0] ? 1 : -1);
-      while (median >= 0 && median < n && a[median] == val) {
-        median += dir;
+      float[][] ranges = new float[3][3];
+      for (int ic = 0; ic < 3; ic++) {
+        float low = Float.MAX_VALUE;
+        float high = -Float.MAX_VALUE;
+        for (int i = lst.size(); --i >= 0;) {
+          P3 lab = lst.get(i).lab;
+          float v = (ic == 0 ? lab.x : ic == 1 ? lab.y : lab.z);
+          if (low > v)
+            low = v;
+          if (high < v)
+            high = v;
+        }
+        ranges[0][ic] = low;
+        ranges[1][ic] = high;
+        ranges[2][ic] = high - low;
       }
-      if (dir == -1)
-        median++;
-      val = a[median];
-      newBox.nextr = nextr;
-      newBox.nextg = nextg;
-      newBox.nextb = nextb;
-      newBox.prevr = prevr;
-      newBox.prevg = prevg;
-      newBox.prevb = prevb;
-      newBox.minr = minr;
-      newBox.ming = ming;
-      newBox.minb = minb;
-      newBox.maxr = maxr;
-      newBox.maxg = maxg;
-      newBox.maxb = maxb;
-      //System.out.println("split " + index + " " + newBox.index);
+      float[] r = ranges[2];
+      int mode = (r[0] >= r[1] ? (r[0] >= r[2] ? 0 : 2)
+          : r[1] >= r[2] ? 1 : 2);
+      // NOTE: GIMP does not use median! uses mean instead;
+
+      //      int median = n / 2;
+      //      float val = a[median];
+      //      int dir = (val == a[0] ? 1 : -1);
+      //      while (median >= 0 && median < n && a[median] == val) {
+      //        median += dir;
+      //      }
+      //      if (dir == -1)
+      //        median++;
+      //      val = a[median];
+
+      float val = ranges[0][mode] + ranges[2][mode] / 2;
+      
+//      newBox.minr = minr;
+//      newBox.ming = ming;
+//      newBox.minb = minb;
+//      newBox.maxr = maxr;
+//      newBox.maxg = maxg;
+//      newBox.maxb = maxb;
       volume = 0;
+      
       switch (mode) {
       case 0:
         for (int i = lst.size(); --i >= 0;)
           if (lst.get(i).lab.x >= val)
             newBox.addItem(lst.remove(i));
-        newBox.prevr = this;
-        nextr = newBox;
-        maxr = val - DELTA;
-        newBox.minr = val;
+//        maxr = val - 0.001f;
+//        newBox.minr = val;
         break;
       case 1:
         for (int i = lst.size(); --i >= 0;)
           if (lst.get(i).lab.y >= val)
             newBox.addItem(lst.remove(i));
-        newBox.prevg = this;
-        nextg = newBox;
-        maxg = val - DELTA;
-        newBox.ming = val;
+//        maxg = val - 0.001f;
+//        newBox.ming = val;
         break;
       case 2:
         for (int i = lst.size(); --i >= 0;)
           if (lst.get(i).lab.z >= val)
             newBox.addItem(lst.remove(i));
-        newBox.prevb = this;
-        nextb = newBox;
-        maxb = val - DELTA;
-        newBox.minb = val;
+//        maxb = val - 0.001f;
+//        newBox.minb = val;
         break;
       }
-      System.out.println(this + " -"+mode+"-> " + newBox +" " + lst.size() + 
"/" + newBox.lst.size());
+      return true;
     }
 
-    /**
-     * Get sorted array of unique component entries
-     * 
-     * @param ic
-     *        0(red) 1(green) 2(blue)
-     */
-    private void getArray(int ic) {
-      float[] a = new float[lst.size()];
-      for (int i = a.length; --i >= 0;) {
-        P3 xyz = lst.get(i).lab;
-        a[i] = (ic == 0 ? xyz.x : ic == 1 ? xyz.y : xyz.z);
-      }
-      Arrays.sort(a);
-      switch (ic) {
-      case 0:
-        ar = a;
-        break;
-      case 1:
-        ag = a;
-        break;
-      case 2:
-        ab = a;
-      }
-    }
-
-    /**
-     * 
-     * Find nearest cell; return errors in [x y z]
-     * 
-     * @param xyz
-     * @param err
-     * @return color cell
-     * 
-     */
-    ColorCell findCell(P3 xyz, P3 err) {
-      err.sub2(xyz, this.xyz);
-      //System.out.println(Integer.toHexString(rgb) + " " + this + " " + 
PT.toJSON(null, err));
-      if (err.x > maxre && nextr != null)
-        return nextr.findCell(xyz, err);
-      if (err.x < minre && prevr != null)
-        return prevr.findCell(xyz, err);
-      if (err.y > maxge && nextg != null)
-        return nextg.findCell(xyz, err);
-      if (err.y < minge && prevg != null)
-        return prevg.findCell(xyz, err);
-      if (err.z > maxbe && nextb != null)
-        return nextb.findCell(xyz, err);
-      if (err.z < minbe && prevb != null)
-        return prevb.findCell(xyz, err);
-      return this; // in this box or best we can do
-    }
-
     @Override
     public String toString() {
       return index + " " + Integer.toHexString(rgb);
     }
   }
 
-/*
-  float RFACTOR = 3.6f;
-  float DELTA = 0.001f; 
-  
-  P3 toRGB(P3 xyz) {
-    return CU.hslToRGB(xyz);
-  }
-
-  P3 toXYZ(int rgb) {
-    return CU.rgbToHSL(CU.colorPtFromInt(rgb, new P3()), false);
-  }
-*/
-  
-  float RFACTOR = 1;
-  float DELTA = 1; 
-  P3 toRGB(P3 xyz) {
-    return P3.new3(clamp(xyz.x), clamp(xyz.y), clamp(xyz.z));
-  }
-
-  P3 toXYZ(int rgb) {
-    return CU.colorPtFromInt(rgb, new P3());
-  }
-
-  @Override
-  protected void generate() throws IOException {
-    if (addHeader)
-      writeHeader();
-    addHeader = false; // only one header
-    if (addImage) {
-      createColorTable();
-      writeGraphicControlExtension();
-      if (delayTime100ths >= 0 && looping)
-        writeNetscapeLoopExtension();
-      writeImage();
-    }
-  }
-
-  @Override
-  protected void close() {
-    if (addTrailer) {
-      writeTrailer();
-    } else {
-      doClose = false;
-    }
-    params.put("captureByteCount", Integer.valueOf(byteCount));
-  }
-
   /**
-   * includes logical screen descriptor
+   * Generate a palette and quantize all colors into it. 
    * 
-   * @throws IOException
    */
-  private void writeHeader() throws IOException {
-    putString("GIF89a");
-    putWord(width);
-    putWord(height);
-    putByte(0); // no global color table -- using local instead
-    putByte(0); // no background
-    putByte(0); // no pixel aspect ratio given
-  }
-
-  /**
-   * generates a 256-color or fewer color table consisting of a set of red,
-   * green, blue arrays and a hash table pointing to a color index; adapts to
-   * situations where more than 256 colors are present.
-   * 
-   */
-  private void createColorTable() {
-    ColorVector colors = getColors();
-    int nTotal = colors.size();//colors256.size();
-    setBitsPerPixel(nTotal);
-    colors.indexColors();
-    ditherPixels();
-  }
-
-  private void setBitsPerPixel(int nTotal) {
+  private void createPalette() {
+    Lst<ColorItem> colors = new Lst<ColorItem>();
+    Map<Integer, ColorItem> ciHash = new Hashtable<Integer, ColorItem>();
+    for (int i = 0, n = pixels.length; i < n; i++) {
+      int rgb = pixels[i];
+      Integer key = Integer.valueOf(rgb);
+      ColorItem item = ciHash.get(key);
+      if (item == null) {
+        item = new ColorItem(rgb);
+        ciHash.put(key, item);
+        colors.addLast(item);
+      }
+    }
+    ciHash = null;
+    int nTotal = colors.size();
+    System.out.println("GIF total image colors: " + nTotal);
     bitsPerPixel = (nTotal <= 2 ? 1 : nTotal <= 4 ? 2 : nTotal <= 16 ? 4 : 8);
-    int mapSize = 1 << bitsPerPixel;
-    red = new int[mapSize];
-    green = new int[mapSize];
-    blue = new int[mapSize];
+    palette = new P3[1 << bitsPerPixel];
+    quantizeColors(colors);
   }
 
   /**
-   * Generate a list of all unique colors in the image.
-   * 
-   * @return the vector
+   *  Quantize colors by generating a set of boxes containing all colors.
+   *  Start with just two boxes -- fixed background color and all others.
+   *  Keep splitting boxes while there are fewer than 256 and some with
+   *  multiple colors in them. 
+   *  
+   *  It is possible that we will end up with fewer than 256 colors.
+   *  
+   * @param colors
    */
-  private ColorVector getColors() {
-    int n = pixels.length;
-    errors = new P3[n];
-    pixelsLab = new P3[n];
-    indexes = new int[n];
-    ColorVector colorVector = new ColorVector();
-    Map<Integer, ColorItem> ciHash = new Hashtable<Integer, ColorItem>();
-    int nColors = 0;
+  private void quantizeColors(Lst<ColorItem> colors) {
+    Map<Integer, ColorCell> colorMap = new Hashtable<Integer, ColorCell>();
+    Lst<ColorCell> boxes = new Lst<ColorCell>();
+    ColorCell cc = new ColorCell(0);
+    cc.addItem(new ColorItem(backgroundColor));
+    boxes.addLast(cc);
+    boxes.addLast(cc = new ColorCell(1));
+    for (int i = colors.size(); --i >= 0;) {
+      ColorItem c = colors.get(i);
+      if (c.rgb != backgroundColor)
+        cc.addItem(c);
+    }
+    colors.clear();
+    int n;
+    while ((n = boxes.size()) < 256) {
+      float maxVol = 0;
+      ColorCell maxCell = null;
+      for (int i = n; --i >= 1;) {
+        ColorCell b = boxes.get(i);
+        float v = b.getVolume();
+        if (v > maxVol) {
+          maxVol = v;
+          maxCell = b;
+        }
+      }
+      if (maxCell == null || !maxCell.splitBox(boxes))
+        break;
+    }
     for (int i = 0; i < n; i++) {
-      pixelsLab[i] = toXYZ(pixels[i]);
-      nColors += addColor(colorVector, ciHash, i);
+      ColorCell b = boxes.get(i);
+      palette[i] = b.setColor();
+      colorMap.put(Integer.valueOf(b.rgb), b);
     }
-    ciHash = null;
-    //colorVector.sort();
-    System.out.println("# total image colors = " + nColors);
-    // dont sort by frequency
-    return colorVector;
+    System.out.println("GIF final color count: " + boxes.size());
+    quantizePixels(colorMap, boxes);
   }
 
-  private int addColor(ColorVector colorVector, Map<Integer, ColorItem> ciHash,
-                       int pt) {
-    int rgb = pixels[pt];
-    Integer key = Integer.valueOf(rgb);
-    ColorItem item = ciHash.get(key);
-    if (item == null) {
-      item = new ColorItem(rgb);
-      ciHash.put(key, item);
-      colorVector.addLast(item);
-      return 1;
-    }
-    item.count++;
-    return 0;
-  }
-
   /**
    * 
-   * Idea is to find the closest known color and then spread out the error over
-   * four pixels
+   * Assign all colors to their closest approximation and
+   * change pixels[] array to index values.
    * 
+   * Floyd-Steinberg dithering, with error limiting to 75%. Finds the closest
+   * known color and then spreads out the error over four leading pixels.
+   * 
+   * @param colorMap 
+   * @param boxes 
+   * 
    */
-  private void ditherPixels() {
-    P3 xyz = new P3();
+  private void quantizePixels(Map<Integer, ColorCell> colorMap, Lst<ColorCell> 
boxes) {
+    P3[] pixelErr = new P3[pixels.length];
+    P3 err = new P3();
+    P3 lab;
+    int rgb;
     for (int i = 0, p = 0; i < height; ++i) {
       boolean notLastRow = (i != height - 1);
       for (int j = 0; j < width; ++j, p++) {
-        int rgb = getRGB(p, xyz);
-        try {
-          ColorCell app = colors256.get(Integer.valueOf(rgb));
-          if (app == null) {
-            P3 err = new P3();
-            app = colorMap.get(Integer.valueOf(pixels[p]));
-            if (floydSteinberg) {
-              app = app.findCell(xyz, err);
-              colorMap.put(Integer.valueOf(rgb), app);
-              boolean notLastCol = (j < width - 1);
+        if (pixelErr[p] == null) {
+          lab = null;
+          rgb = pixels[p];
+        } else {
+          lab = toLAB(pixels[p]);
+          err = pixelErr[p]; // it does not matter that we repurpose errors[p] 
here.
+          err.x = clamp(err.x, -75, 75);
+          err.y = clamp(err.y, -75, 75);
+          err.z = clamp(err.z, -75, 75);
+          lab.add(err);
+          rgb = CU.colorPtToFFRGB(toRGB(lab));
+        }
+        ColorCell app = colorMap.get(Integer.valueOf(rgb));
+        if (app == null) {
+          if (lab == null)
+            lab = toLAB(pixels[p]);
+          // find nearest cell
+          float maxerr = Float.MAX_VALUE;
+          // skip 0 0 0
+          for (int ib = boxes.size(); --ib >= 1;) {
+            ColorCell b = boxes.get(ib);
+            err.sub2(lab, b.lab);
+            float d = err.lengthSquared();
+            if (d < maxerr) {
+              maxerr = d;
+              app = b;
+            }
+          }
+          err.sub2(lab, app.lab);
+
+          if (floydSteinberg) {
+            // dither
+            boolean notLastCol = (j < width - 1);
+            if (notLastCol)
+              addError(err, 7, pixelErr, p + 1);
+            if (notLastRow) {
+              if (j > 0)
+                addError(err, 3, pixelErr, p + width - 1);
+              addError(err, 5, pixelErr, p + width);
               if (notLastCol)
-                addError(err, 7, p + 1);
-              if (notLastRow) {
-                if (j > 0)
-                  addError(err, 3, p + width - 1);
-                addError(err, 5, p + width);
-                if (notLastCol)
-                  addError(err, 1, p + width + 1);
-              }
+                addError(err, 1, pixelErr, p + width + 1);
             }
           }
-          indexes[p] = app.index;
-        } catch (Throwable e) {
-          System.out.println("GIF error: " + e);
         }
+        pixels[p] = app.index;
       }
     }
   }
 
-  private int getRGB(int p, P3 xyz) {
-    P3 err = errors[p];
-    xyz.setT(pixelsLab[p]);
-    if (err == null)
-      return pixels[p];
-    xyz.add(err);
-    return CU.colorPtToFFRGB(toRGB(xyz));
-  }
-
-  private void addError(P3 err, int f, int p) {
-    P3 errp = errors[p];
+  private void addError(P3 err, int f, P3[] pixelErr, int p) {
+    if (pixels[p] == backgroundColor)
+      return;
+    P3 errp = pixelErr[p];
     if (errp == null)
-      errp = errors[p] = new P3();
+      errp = pixelErr[p] = new P3();
     errp.scaleAdd2(f / 16f, err, errp);
   }
 
-  int clamp(float c) {
-    return (int) Math.floor(c < 0 ? 0 : c > 255 ? 255 : c);
+  //  protected void drawPt(int index, int i, int rgb, boolean isMain) {
+  //    P3 pt = toLAB(rgb);
+  //    System.out.println("draw id 'd" + index + "_" + i + "' width "
+  //        + (isMain ? 1.0 : 0.2) + " " + pt + " color "
+  //        + CU.colorPtFromInt(rgb, null) + " '" + index + "'");
+  //  }
+
+  ///////////////////////// CIE L*a*b / XYZ / sRGB conversion methods /////////
+
+
+  // these could be static, but that just makes for more JavaScript code
+  
+  protected P3 toLAB(int rgb) {
+    P3 lab = CU.colorPtFromInt(rgb, null);
+    rgbToXyz(lab, lab);
+    xyzToLab(lab, lab);
+    // normalize to 0-100
+    lab.y = (lab.y + 86.185f) / (98.254f + 86.185f) * 100f;
+    lab.z = (lab.z + 107.863f) / (94.482f + 107.863f) * 100f;
+    return lab;
   }
 
+  protected P3 toRGB(P3 lab) {
+    P3 xyz = P3.newP(lab);
+    // normalized to 0-100
+    xyz.y = xyz.y / 100f * (98.254f + 86.185f) - 86.185f;
+    xyz.z = xyz.z / 100f * (94.482f + 107.863f) - 107.863f;
+    labToXyz(xyz, xyz);
+    return xyzToRgb(xyz, xyz);
+  }
+
+  private static M3 xyz2rgb;
+  private static M3 rgb2xyz;
+
+  static {
+    rgb2xyz = M3.newA9(new float[] { 0.4124f, 0.3576f, 0.1805f, 0.2126f,
+        0.7152f, 0.0722f, 0.0193f, 0.1192f, 0.9505f });
+
+    xyz2rgb = M3.newA9(new float[] { 3.2406f, -1.5372f, -0.4986f, -0.9689f,
+        1.8758f, 0.0415f, 0.0557f, -0.2040f, 1.0570f });
+  }
+
+  private P3 rgbToXyz(P3 rgb, P3 xyz) {
+    // http://en.wikipedia.org/wiki/CIE_1931_color_space
+    // http://rsb.info.nih.gov/ij/plugins/download/Color_Space_Converter.java
+    if (xyz == null)
+      xyz = new P3();
+    xyz.x = srgb(rgb.x);
+    xyz.y = srgb(rgb.y);
+    xyz.z = srgb(rgb.z);
+    rgb2xyz.rotate(xyz);
+    return xyz;
+  }
+
+  private float srgb(float x) {
+    x /= 255;
+    return (float) (x <= 0.04045 ? x / 12.92 : Math.pow(((x + 0.055) / 1.055),
+        2.4)) * 100;
+  }
+
+  private P3 xyzToRgb(P3 xyz, P3 rgb) {
+    // http://en.wikipedia.org/wiki/CIE_1931_color_space
+    // http://rsb.info.nih.gov/ij/plugins/download/Color_Space_Converter.java
+    if (rgb == null)
+      rgb = new P3();
+    rgb.setT(xyz);
+    rgb.scale(0.01f);
+    xyz2rgb.rotate(rgb);
+    rgb.x = clamp(sxyz(rgb.x), 0, 255);
+    rgb.y = clamp(sxyz(rgb.y), 0, 255);
+    rgb.z = clamp(sxyz(rgb.z), 0, 255);
+    return rgb;
+  }
+
+  private float sxyz(float x) {
+    return (float) (x > 0.0031308f ? (1.055 * Math.pow(x, 1.0 / 2.4)) - 0.055
+        : x * 12.92) * 255;
+  }
+
+  private P3 xyzToLab(P3 xyz, P3 lab) {
+    // http://en.wikipedia.org/wiki/Lab_color_space
+    // http://rsb.info.nih.gov/ij/plugins/download/Color_Space_Converter.java
+    // Lab([0..100], [-86.185..98.254], [-107.863..94.482])
+    // XYZn = D65 = {95.0429, 100.0, 108.8900};
+    if (lab == null)
+      lab = new P3();
+    float x = flab(xyz.x / 95.0429f);
+    float y = flab(xyz.y / 100);
+    float z = flab(xyz.z / 108.89f);
+    lab.x = (116 * y) - 16;
+    lab.y = 500 * (x - y);
+    lab.z = 200 * (y - z);
+    return lab;
+  }
+
+  private float flab(float t) {
+    return (float) (t > 8.85645168E-3 /* (24/116)^3 */? Math.pow(t,
+        0.333333333) : 7.78703704 /* 1/3*116/24*116/24 */* t + 0.137931034 /* 
16/116 */
+    );
+  }
+
+  private P3 labToXyz(P3 lab, P3 xyz) {
+    // http://en.wikipedia.org/wiki/Lab_color_space
+    // http://rsb.info.nih.gov/ij/plugins/download/Color_Space_Converter.java
+    // XYZn = D65 = {95.0429, 100.0, 108.8900};
+    if (xyz == null)
+      xyz = new P3();
+
+    xyz.setT(lab);
+    float y = (xyz.x + 16) / 116;
+    float x = xyz.y / 500 + y;
+    float z = y - xyz.z / 200;
+    xyz.x = fxyz(x) * 95.0429f;
+    xyz.y = fxyz(y) * 100;
+    xyz.z = fxyz(z) * 108.89f;
+
+    return xyz;
+  }
+
+  private float fxyz(float t) {
+    return (float) (t > 0.206896552 /* (24/116) */? t * t * t
+        : 0.128418549 /* 3*24/116*24/116 */* (t - 0.137931034 /* 16/116 */));
+  }
+
+  private float clamp(float c, float min, float max) {
+    return Math.round(c < min ? min : c > max ? max : c);
+  }
+
+  //static {
+  //  P3 x;
+  //  x = rgbToXyz(P3.new3(0,0,0), null);
+  //  System.out.println("xyz="+x);
+  //  x = xyzToLab(x, x);
+  //  System.out.println("lab="+x);
+  //  x = labToXyz(x, x);
+  //  System.out.println(x);
+  //  x = xyzToRgb(x, x);
+  //  System.out.println(x);
+  //  
+  //  x = rgbToXyz(P3.new3(254,1,253), null);
+  //  System.out.println("xyz="+x);
+  //  x = xyzToLab(x, x);
+  //  System.out.println("lab="+x);
+  //  x = labToXyz(x, x);
+  //  System.out.println(x);
+  //  x = xyzToRgb(x, x);
+  //  System.out.println(x);
+  //  
+  //  System.out.println(toRGB(toLAB(CU.colorPtToFFRGB(P3.new3(200,100,50)))));
+  //}
+
+  ///////////////////////// GifEncoder writing methods ////////////////////////
+
+  /**
+   * includes logical screen descriptor
+   * 
+   * @throws IOException
+   */
+  private void writeHeader() throws IOException {
+    putString("GIF89a");
+    putWord(width);
+    putWord(height);
+    putByte(0); // no global color table -- using local instead
+    putByte(0); // no background
+    putByte(0); // no pixel aspect ratio given
+  }
+
   private void writeGraphicControlExtension() {
     if (isTransparent || delayTime100ths >= 0) {
       putByte(0x21); // graphic control extension
@@ -763,10 +811,13 @@
     int packedFields = 0x80 | (interlaced ? 0x40 : 0) | (bitsPerPixel - 1);
     putByte(packedFields);
     int colorMapSize = 1 << bitsPerPixel;
+    P3 p = new P3();
     for (int i = 0; i < colorMapSize; i++) {
-      putByte(red[i]);
-      putByte(green[i]);
-      putByte(blue[i]);
+      if (palette[i] != null)
+        p = palette[i];
+      putByte((int) p.x);
+      putByte((int) p.y);
+      putByte((int) p.z);
     }
     putByte(initCodeSize = (bitsPerPixel <= 1 ? 2 : bitsPerPixel));
     compress();
@@ -786,7 +837,7 @@
   private int nextPixel() {
     if (countDown-- == 0)
       return EOF;
-    int colorIndex = indexes[curpt];
+    int colorIndex = pixels[curpt];
     // Bump the current X position
     ++curx;
     if (curx == width) {
@@ -1072,4 +1123,5 @@
       bufPt = 0;
     }
   }
+
 }

Modified: trunk/Jmol/src/javajs/util/OC.java
===================================================================
--- trunk/Jmol/src/javajs/util/OC.java  2014-11-08 17:41:04 UTC (rev 20093)
+++ trunk/Jmol/src/javajs/util/OC.java  2014-11-09 22:12:09 UTC (rev 20094)
@@ -8,6 +8,7 @@
 import java.io.OutputStreamWriter;
 
 
+
 import javajs.J2SIgnoreImport;
 import javajs.api.BytePoster;
 
@@ -72,8 +73,7 @@
        os = null;
     }
     this.os = os;
-    isLocalFile = (fileName != null && !(fileName.startsWith("http://";) || 
fileName
-        .startsWith("https://";)));
+    isLocalFile = (fileName != null && !isRemote(fileName));
     if (asWriter && !isBase64 && os != null)
       bw = new BufferedWriter(new OutputStreamWriter(os));
     return this;
@@ -343,4 +343,34 @@
     return bytePoster.postByteArray(fileName, bytes);
   }
 
+  public final static String[] urlPrefixes = { "http:", "https:", "sftp:", 
"ftp:",
+  "file:" };
+  // note that SFTP is not supported
+  public final static int URL_LOCAL = 4;
+
+  public static boolean isRemote(String fileName) {
+    if (fileName == null)
+      return false;
+    int itype = urlTypeIndex(fileName);
+    return (itype >= 0 && itype != URL_LOCAL);
+  }
+
+  public static boolean isLocal(String fileName) {
+    if (fileName == null)
+      return false;
+    int itype = urlTypeIndex(fileName);
+    return (itype < 0 || itype == URL_LOCAL);
+  }
+
+  public static int urlTypeIndex(String name) {
+    if (name == null)
+      return -2; // local unsigned applet
+    for (int i = 0; i < urlPrefixes.length; ++i) {
+      if (name.startsWith(urlPrefixes[i])) {
+        return i;
+      }
+    }
+    return -1;
+  }
+
 }

Modified: trunk/Jmol/src/org/jmol/awtjs2d/JSFile.java
===================================================================
--- trunk/Jmol/src/org/jmol/awtjs2d/JSFile.java 2014-11-08 17:41:04 UTC (rev 
20093)
+++ trunk/Jmol/src/org/jmol/awtjs2d/JSFile.java 2014-11-09 22:12:09 UTC (rev 
20094)
@@ -4,9 +4,9 @@
 
 import javajs.api.GenericFileInterface;
 import javajs.util.AjaxURLConnection;
+import javajs.util.OC;
 import javajs.util.PT;
 
-import org.jmol.viewer.FileManager;
 import org.jmol.viewer.Viewer;
 
 /**
@@ -29,7 +29,7 @@
        JSFile(String name) {
        this.name = name.replace('\\','/');
        fullName = name;
-       if (!fullName.startsWith("/") && FileManager.urlTypeIndex(name) < 0)
+       if (!fullName.startsWith("/") && OC.urlTypeIndex(name) < 0)
                fullName = Viewer.jsDocumentBase + "/" + fullName;
        fullName = PT.rep(fullName, "/./", "/");
        name = name.substring(name.lastIndexOf("/") + 1);

Modified: trunk/Jmol/src/org/jmol/io/JmolUtil.java
===================================================================
--- trunk/Jmol/src/org/jmol/io/JmolUtil.java    2014-11-08 17:41:04 UTC (rev 
20093)
+++ trunk/Jmol/src/org/jmol/io/JmolUtil.java    2014-11-09 22:12:09 UTC (rev 
20094)
@@ -34,6 +34,7 @@
 import javajs.api.GenericZipTools;
 import javajs.api.GenericBinaryDocument;
 import javajs.util.LimitedLineReader;
+import javajs.util.OC;
 import javajs.util.Rdr;
 import javajs.util.Lst;
 import javajs.util.PT;
@@ -52,7 +53,6 @@
 import org.jmol.api.JmolZipUtilities;
 import org.jmol.util.Escape;
 import org.jmol.util.Logger;
-import org.jmol.viewer.FileManager;
 import org.jmol.viewer.Viewer;
 
 public class JmolUtil implements JmolZipUtilities {
@@ -695,7 +695,7 @@
           return "" + ret;
         image = (vwr.isJS ? ret : apiPlatform.createImage(ret));
       } else if (vwr.isJS) {
-      } else if (FileManager.urlTypeIndex(fullPathName) >= 0) {
+      } else if (OC.urlTypeIndex(fullPathName) >= 0) {
         try {
           image = apiPlatform.createImage(new URL((URL) null, fullPathName,
               null));

Modified: trunk/Jmol/src/org/jmol/scriptext/CmdExt.java
===================================================================
--- trunk/Jmol/src/org/jmol/scriptext/CmdExt.java       2014-11-08 17:41:04 UTC 
(rev 20093)
+++ trunk/Jmol/src/org/jmol/scriptext/CmdExt.java       2014-11-09 22:12:09 UTC 
(rev 20094)
@@ -479,16 +479,16 @@
     }
     Map<String, Object> params = vwr.captureParams;
     String type = (params == null ? "GIF" : (String) params.get("type"));
-    float endTime = 10; // ten seconds by default
+    float endTime = 0; // indefinitely by default
     int mode = 0;
     int slen = e.slen;
-    boolean isTransparent = (tokAt(e.slen - 1) == T.translucent);
-    if (isTransparent)
-      slen--;
     String fileName = "";
     boolean looping = !vwr.am.animationReplayMode.name().equals("ONCE");
     int i = 1;
     int tok = tokAt(i);
+    boolean isTransparent = (tok == T.translucent);
+    if (isTransparent)
+      tok = tokAt(++i);
     switch (tok == T.nada ? (tok = T.end) : tok) {
     case T.string:
       fileName = e.optParameterAsString(i++);
@@ -511,8 +511,14 @@
       } else {
         type = "GIF";
       }
-      boolean streaming = (fileName.indexOf("0000.") != 
fileName.lastIndexOf(".") - 4);      
+      if (isTransparent)
+        type += "T";
+      boolean streaming = (fileName.indexOf("0000.") != 
fileName.lastIndexOf(".") - 4);    
       boolean isRock = false;
+      if (tokAt(i) == T.loop) {
+        looping = true;
+        tok = tokAt(++i);
+      }
       switch (tokAt(i)) {
       case T.rock:
         isRock = true;
@@ -540,15 +546,13 @@
         boolean wf = vwr.g.waitForMoveTo;
         s = "set waitformoveto true;" + PT.rep(s, "Y", axis)
             + ";set waitformoveto " + wf;
-        s = "capture " + PT.esc(fileName) + " -1"
-            + (isTransparent ? " transparent;" : ";") + s + ";capture end;";
+        s = "capture " + (isTransparent ? "transparent " : "") + 
PT.esc(fileName) + " LOOP;"
+             + s + ";capture end;";
         e.cmdScript(0, null, s);
         return;
       case T.decimal:
       case T.integer:
         endTime = floatParameter(i++);
-        if (endTime < 0)
-          looping = true;
         break;
       }
       if (chk)
@@ -584,13 +588,11 @@
     params.put("type", type);
     Integer c = Integer.valueOf(vwr.getBackgroundArgb());
     params.put("backgroundColor", c);
-    if (isTransparent)
-      params.put("transparentColor", c);
     params.put("fileName", fileName);
     params.put("quality", Integer.valueOf(-1));
     params.put(
         "endTime",
-        Long.valueOf(endTime < 0 ? -1 : System.currentTimeMillis()
+        Long.valueOf(endTime <= 0 ? -1 : System.currentTimeMillis()
             + (long) (endTime * 1000)));
     params.put("captureMode", T.nameOf(mode).toLowerCase());
     params.put("captureLooping", looping ? Boolean.TRUE : Boolean.FALSE);
@@ -6019,7 +6021,7 @@
         } else if (PT.isOneOf(type, ";ZIP;ZIPALL;SPT;STATE;")) {
           pt++;
           break;
-        } else if (!isCoord){
+        } else if (!isCoord) {
           type = "(image)";
         }
       }
@@ -6105,6 +6107,8 @@
           || type.equals("FRAME") || type.equals("VIBRATION")) {
         type = (fileName != null && fileName.indexOf(".") >= 0 ? fileName
             .substring(fileName.lastIndexOf(".") + 1).toUpperCase() : "JPG");
+        if (PT.isOneOf(type, ";PNGJ;PNGT;GIFT;"))
+          fileName = fileName.substring(0, fileName.length() - 1);
       }
       if (type.equals("MNU")) {
         type = "MENU";
@@ -6291,7 +6295,8 @@
             }
           } else {
             if (fileName != null
-                && (bytes = data = vwr.createZip(fileName, v.size() == 1 ? 
"BINARY" : "ZIPDATA", v)) == null)
+                && (bytes = data = vwr.createZip(fileName,
+                    v.size() == 1 ? "BINARY" : "ZIPDATA", v)) == null)
               e.evalError("#CANCELED#", null);
           }
         } else if (data == "SPT") {
@@ -6378,11 +6383,6 @@
         params = new Hashtable<String, Object>();
         if (fileName != null)
           params.put("fileName", fileName);
-        if (type.equals("GIFT")) {
-          params.put("transparentColor",
-              Integer.valueOf(vwr.getBackgroundArgb()));
-          type = "GIF";
-        }
         params.put("backgroundColor", 
Integer.valueOf(vwr.getBackgroundArgb()));
         params.put("type", type);
         if (bytes instanceof String && quality == Integer.MIN_VALUE)

Modified: trunk/Jmol/src/org/jmol/viewer/FileManager.java
===================================================================
--- trunk/Jmol/src/org/jmol/viewer/FileManager.java     2014-11-08 17:41:04 UTC 
(rev 20093)
+++ trunk/Jmol/src/org/jmol/viewer/FileManager.java     2014-11-09 22:12:09 UTC 
(rev 20094)
@@ -397,7 +397,7 @@
                 .appendSB(Base64.getBase64(bytes)).toString();
           }
         }
-        int iurl = urlTypeIndex(name);
+        int iurl = OC.urlTypeIndex(name);
         boolean isURL = (iurl >= 0);
         String post = null;
         if (isURL && (iurl = name.indexOf("?POST?")) >= 0) {
@@ -930,30 +930,6 @@
     // JSmol will call that from awtjs2d.Platform.java asynchronously
   }
 
-  public final static int URL_LOCAL = 4;
-  private final static String[] urlPrefixes = { "http:", "https:", "sftp:", 
"ftp:",
-      "file:" };
-
-  public static int urlTypeIndex(String name) {
-    if (name == null)
-      return -2; // local unsigned applet
-    for (int i = 0; i < urlPrefixes.length; ++i) {
-      if (name.startsWith(urlPrefixes[i])) {
-        return i;
-      }
-    }
-    return -1;
-  }
-  
-  public static boolean isLocal(String fileName) {
-    if (fileName == null)
-      return false;
-    int itype = urlTypeIndex(fileName);
-    return (itype < 0 || itype == URL_LOCAL);
-  }
-
-
-
   /**
    * [0] and [2] may return same as [1] in the 
    * case of a local unsigned applet.
@@ -987,7 +963,7 @@
     if (appletDocumentBaseURL == null) {
       // This code is for the app or signed local applet 
       // -- no local file reading for headless
-      if (urlTypeIndex(name) >= 0 || vwr.haveAccess(ACCESS.NONE)
+      if (OC.urlTypeIndex(name) >= 0 || vwr.haveAccess(ACCESS.NONE)
           || vwr.haveAccess(ACCESS.READSPT) && !name.endsWith(".spt")
           && !name.endsWith("/")) {
         try {
@@ -1025,7 +1001,7 @@
       names[0] = pathForAllFiles + names[1];
       Logger.info("FileManager substituting " + name0 + " --> " + names[0]);
     }
-    if (isFullLoad && (file != null || urlTypeIndex(names[0]) == URL_LOCAL)) {
+    if (isFullLoad && (file != null || OC.urlTypeIndex(names[0]) == 
OC.URL_LOCAL)) {
       String path = (file == null ? PT.trim(names[0].substring(5), "/")
           : names[0]);
       int pt = path.length() - names[1].length() - 1;
@@ -1203,7 +1179,7 @@
     for (int iFile = 0; iFile < nFiles; iFile++) {
       String name0 = fileNames.get(iFile);
       String name = name0;
-      if (isLocal == isLocal(name)) {
+      if (isLocal == OC.isLocal(name)) {
         int pt = (noPath ? -1 : name.indexOf("/" + dataPath + "/"));
         if (pt >= 0) {
           name = name.substring(pt + 1);
@@ -1323,6 +1299,8 @@
 
   @Override
   public String postByteArray(String fileName, byte[] bytes) {
+    // in principle, could have sftp or ftp here
+    // but sftp is not implemented
     Object ret = getBufferedInputStreamOrErrorMessageFromName(fileName, null, 
false,
             false, bytes, false, true);
     if (ret instanceof String)

Modified: trunk/Jmol/src/org/jmol/viewer/Jmol.properties
===================================================================
--- trunk/Jmol/src/org/jmol/viewer/Jmol.properties      2014-11-08 17:41:04 UTC 
(rev 20093)
+++ trunk/Jmol/src/org/jmol/viewer/Jmol.properties      2014-11-09 22:12:09 UTC 
(rev 20094)
@@ -15,7 +15,7 @@
 TODO: design and implement sidechain mutation -- MUTATE command ?
 TODO: remove HTML5 dependency on synchronous file loading (check SCRIPT 
command for problems)
 
-Jmol.___JmolVersion="14.3.8_2014.11.08"
+Jmol.___JmolVersion="14.3.8_2014.11.09"
 
 new feature: CAPTURE "filename0000.png"  
   -- captures set of PNG files
@@ -25,13 +25,18 @@
   -- captures set of GIF files
   -- 0000 IS required in order to distinguish this from animated GIF
   
-code: code clean-up in GData, Graphics3D, and Export3D
-code: modifications to GIF writer. Not really satisfying; still using HSL
-code: PDB reader CONECT efficiency
+bug fix: GIF writer not properly handling large numbers of colors
+  -- use of CIE L*a*b gives GIMP-like color quantification
 
+bug fix: WRITE command should remove "t" or "j" in WRITE xxx.PNGJ, WRITE 
xxx.PNGT, WRITE xxx.GIFT
+  -- specifically when no PNGJ, GIFT, or PNGT designation is made.
+  
 bug fix: PDB reader limited to 20 connections per atom
  
+code: code clean-up in GData, Graphics3D, and Export3D
+code: PDB reader CONECT efficiency
 
+
 JmolVersion="14.3.8_2014.10.27"
 
 bug fix: up-arrow in console may not return command if contains unicode

Modified: trunk/Jmol/src/org/jmol/viewer/OutputManager.java
===================================================================
--- trunk/Jmol/src/org/jmol/viewer/OutputManager.java   2014-11-08 17:41:04 UTC 
(rev 20093)
+++ trunk/Jmol/src/org/jmol/viewer/OutputManager.java   2014-11-09 22:12:09 UTC 
(rev 20094)
@@ -168,7 +168,7 @@
           OC outTemp = getOutputChannel(null, null);
           getWrappedState(fileName, scripts, image, outTemp);
           stateData = outTemp.toByteArray();
-        } else if (rgbbuf == null && !asBytes) {
+        } else if (rgbbuf == null && !asBytes && 
!params.containsKey("captureMode")) {
           stateData = ((String) getWrappedState(null, scripts, image, null))
               .getBytes();
         }
@@ -176,11 +176,12 @@
           params.put("pngAppData", stateData);
           params.put("pngAppPrefix", "Jmol Type");
         }
-        if (type.equals("PNGT"))
-          params.put("transparentColor",
-              Integer.valueOf(vwr.getBackgroundArgb()));
-        type = "PNG";
       }
+      if (type.equals("PNGT") || type.equals("GIFT")) {
+        params.put("transparentColor",
+            Integer.valueOf(vwr.getBackgroundArgb()));
+        type = type.substring(0, 3);
+      }
       if (comment != null)
         params.put("comment", comment.length() == 0 ? Viewer.getJmolVersion()
             : comment);
@@ -191,7 +192,6 @@
       if (isOK) {
         if (params.containsKey("captureMsg") && 
!params.containsKey("captureSilent"))
           vwr.prompt((String) params.get("captureMsg"), "OK", null, true);
-
         if (asBytes)
           bytes = out.toByteArray();
         else if (params.containsKey("captureByteCount"))
@@ -365,7 +365,7 @@
     }
     if (fullPath != null)
       fullPath[0] = fileName;
-    String localName = (FileManager.isLocal(fileName) ? fileName : null);
+    String localName = (OC.isLocal(fileName) ? fileName : null);
     try {
       return openOutputChannel(privateKey, localName, false, false);
     } catch (IOException e) {
@@ -647,7 +647,7 @@
       return null;
     params.put("fileName", fileName);
     // JSmol/HTML5 WILL produce a localName now
-    if (FileManager.isLocal(fileName))
+    if (OC.isLocal(fileName))
       localName = fileName;
     int saveWidth = vwr.dimScreen.width;
     int saveHeight = vwr.dimScreen.height;
@@ -908,7 +908,7 @@
     Lst<String> newFileNames = new Lst<String>();
     for (int iFile = 0; iFile < nFiles; iFile++) {
       String name = fileNames.get(iFile);
-      boolean isLocal = !vwr.isJS && FileManager.isLocal(name);
+      boolean isLocal = !vwr.isJS && OC.isLocal(name);
       String newName = name;
       // also check that somehow we don't have a local file with the same name 
as
       // a fixed remote file name (because someone extracted the files and 
then used them)

Modified: trunk/Jmol/src/org/jmol/viewer/OutputManagerAwt.java
===================================================================
--- trunk/Jmol/src/org/jmol/viewer/OutputManagerAwt.java        2014-11-08 
17:41:04 UTC (rev 20093)
+++ trunk/Jmol/src/org/jmol/viewer/OutputManagerAwt.java        2014-11-09 
22:12:09 UTC (rev 20094)
@@ -85,7 +85,7 @@
   OC openOutputChannel(double privateKey, String fileName,
                                       boolean asWriter, boolean asAppend)
       throws IOException {
-    boolean isLocal = FileManager.isLocal(fileName);
+    boolean isLocal = OC.isLocal(fileName);
     if (asAppend && isLocal && fileName.indexOf("JmolLog_") < 0)
       asAppend = false;
     return (fileName != null && !vwr.haveAccess(ACCESS.ALL)

This was sent by the SourceForge.net collaborative development platform, the 
world's largest Open Source development site.


------------------------------------------------------------------------------
_______________________________________________
Jmol-commits mailing list
Jmol-commits@lists.sourceforge.net
https://lists.sourceforge.net/lists/listinfo/jmol-commits

Reply via email to