Revision: 20097
          http://sourceforge.net/p/jmol/code/20097
Author:   hansonr
Date:     2014-11-10 02:35:52 +0000 (Mon, 10 Nov 2014)
Log Message:
-----------
Jmol.___JmolVersion="14.2.7_2014.11.09"

bug fix: GIF writer not properly handling large numbers of colors
  -- use of CIE L*a*b for color quantification
  -- nearly identical to GIMP
  -- uses MEAN_cut (not MEDIAN_cut) 
  -- uses Floyd-Steinberg dithering
  -- will not discolor background (as GIMP will do)

Modified Paths:
--------------
    branches/v14_2/Jmol/src/javajs/img/GifEncoder.java
    branches/v14_2/Jmol/src/javajs/img/ImageEncoder.java
    branches/v14_2/Jmol/src/javajs/util/CU.java
    branches/v14_2/Jmol/src/org/jmol/modelset/BondCollection.java
    branches/v14_2/Jmol/src/org/jmol/modelset/ModelSet.java
    branches/v14_2/Jmol/src/org/jmol/script/ScriptExpr.java
    branches/v14_2/Jmol/src/org/jmol/script/ScriptMathProcessor.java
    branches/v14_2/Jmol/src/org/jmol/scriptext/CmdExt.java
    branches/v14_2/Jmol/src/org/jmol/scriptext/MathExt.java
    branches/v14_2/Jmol/src/org/jmol/viewer/Jmol.properties
    branches/v14_2/Jmol/src/org/jmol/viewer/OutputManager.java

Modified: branches/v14_2/Jmol/src/javajs/img/GifEncoder.java
===================================================================
--- branches/v14_2/Jmol/src/javajs/img/GifEncoder.java  2014-11-10 02:28:42 UTC 
(rev 20096)
+++ branches/v14_2/Jmol/src/javajs/img/GifEncoder.java  2014-11-10 02:35:52 UTC 
(rev 20097)
@@ -22,8 +22,11 @@
  *  Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
  */
 
+//  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.
@@ -59,135 +62,100 @@
 // <P>
 // @see ToGif
 
-
 package javajs.img;
 
+import javajs.util.CU;
 import javajs.util.Lst;
-import java.util.Collections;
-import java.util.Comparator;
+import javajs.util.M3;
+import javajs.util.P3;
+
 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
  * 
+ * Color quantization roughly follows the GIMP method
+ * "dither Floyd-Steinberg standard" but with some twists.
+ * (For example, we exclude the background color.)
+ * 
+ * Note that although GIMP code annotation refers to "median-cut", 
+ * it is really using MEAN-cut. That is what I use here as well.
+ * 
+ * -- 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
  * 
- * -- adds adaptive color reduction to generate 256 colors
- *      Reduction algorithm simply removes lower bits of red, green, and blue
- *      one at a time until the number of sets is <= 256. Then it creates a
- *      color for each set that is a weighted average of all the colors for 
that set.
- *      Seems to work reasonably well. Mapped isosurfaces look pretty crude.
- * 
  * -- allows progressive production of animated GIF via Jmol CAPTURE command
  * 
  * -- uses general purpose javajs.util.OutputChannel for byte-handling options
- *    such as posting to a server, writing to disk, and retrieving bytes.
- *    
+ * such as posting to a server, writing to disk, and retrieving bytes.
+ * 
  * -- 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 Map<Integer, AdaptiveColorCollection> colorMap;
-  protected int[] red, green, blue;
+  private Map<String, Object> params;
+  private P3[] palette;
+  private int backgroundColor;
 
-  private class ColorItem {
-
-    AdaptiveColorCollection acc;
-    int rgb;
-    int count;
-
-    ColorItem(int rgb, int count) {
-      this.rgb = rgb;
-      this.count = count;
-    }
-  }
-
-  protected class ColorVector extends Lst<ColorItem> {
-
-    void sort() {
-      CountComparator comparator = new CountComparator();
-      Collections.sort(this, comparator);
-    }
-
-    protected class CountComparator implements Comparator<ColorItem> {
-      @Override
-      public int compare(ColorItem a, ColorItem b) {
-        return (a == null ? 1 : b == null ? -1 : a.count < b.count ? -1
-            : a.count > b.count ? 1 : 0);
-      }
-    }
-  }
-
-  private class AdaptiveColorCollection {
-    //int rgb;
-    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 int r;
-    private int g;
-    private int b;
-    private int count;
-
-    AdaptiveColorCollection(int rgb, int index) {
-      //this.rgb = rgb;
-      this.index = index;
-      if (rgb >= 0)
-        transparentIndex = index;
-    }
-    void addRgb(int rgb, int count) {
-      this.count += count;
-      b += (rgb & 0xFF) * count;
-      g += ((rgb >> 8) & 0xFF) * count;
-      r += ((rgb >> 16) & 0xFF) * count;
-    }
-
-    void setRgb() {
-      red[index] = (r / count) & 0xff;
-      green[index] =(g / count) & 0xff;
-      blue[index] = (b / count) & 0xff;
-    }
-  }
-
   private boolean interlaced;
   private boolean addHeader = true;
   private boolean addImage = true;
   private boolean addTrailer = true;
+  private boolean isTransparent;
+  private boolean floydSteinberg = true;
+  private boolean capturing;
+  private boolean looping;
+
   private int delayTime100ths = -1;
-  private boolean looping;
-  private Map<String, Object> params;
+  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
+   * we allow for animated GIF by being able to re-enter the code with 
different
+   * parameters held in params
    * 
    * 
    */
   @Override
   protected void setParams(Map<String, Object> params) {
     this.params = params;
+    Integer ic = (Integer) params.get("transparentColor");
+    if (ic == null) {
+      ic = (Integer) params.get("backgroundColor");
+      if (ic != null)
+        backgroundColor = ic.intValue();
+    } else {
+      backgroundColor = ic.intValue();
+      isTransparent = true;
+    }
+
     interlaced = (Boolean.TRUE == params.get("interlaced"));
-    if (interlaced || !params.containsKey("captureMode"))
+    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;
@@ -197,7 +165,7 @@
       addHeader = false;
       addTrailer = false;
       int fps = Math.abs(((Integer) params.get("captureFps")).intValue());
-      delayTime100ths =  (fps == 0 ? 0 : 100 / fps);
+      delayTime100ths = (fps == 0 ? 0 : 100 / fps);
       looping = (Boolean.FALSE != params.get("captureLooping"));
       break;
     case 2: // end
@@ -212,21 +180,13 @@
     }
   }
 
-
-  // Adapted from ppmtogif, which is based on GIFENCOD by David
-  // Rowley <mga...@watdscu.waterloo.edu>.  Lempel-Zim compression
-  // based on "compress".
-
-  private int bitsPerPixel = 1;
-  protected int transparentIndex = -1;
-
   @Override
   protected void generate() throws IOException {
     if (addHeader)
       writeHeader();
     addHeader = false; // only one header
     if (addImage) {
-      createColorTable();
+      createPalette();
       writeGraphicControlExtension();
       if (delayTime100ths >= 0 && looping)
         writeNetscapeLoopExtension();
@@ -241,214 +201,593 @@
     } else {
       doClose = false;
     }
-    params.put("captureByteCount", Integer.valueOf(byteCount));
+    if (capturing)
+      params.put("captureByteCount", Integer.valueOf(byteCount));
   }
 
-  /**
-   * 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
+  //////////////  256-color quantization  //////////////
+
+  private class ColorItem {
+
+    int rgb;
+    P3 lab;
+
+    ColorItem(int rgb) {
+      this.rgb = rgb;
+      lab = toLAB(rgb);
+    }
+
+    @Override
+    public String toString() {
+      return Integer.toHexString(rgb) + " " + lab;
+    }
   }
 
+  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.
+    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;
+    int rgb;
+    Lst<ColorItem> lst;
+    private float volume;
+
+    ColorCell(int index) {
+      this.index = index;
+      lst = new Lst<ColorItem>();
+    }
+
+    public float getVolume() {
+      if (volume != 0)
+        return volume;
+      if (lst.size() < 2)
+        return -1;
+      //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 < 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;
+      }
+      float dx = (maxx - minx);
+      float dy = (maxy - miny);
+      float dz = (maxz - minz);
+      return volume = dx * dx + dy * dy + dz * dz;
+    }
+
+    void addItem(ColorItem c) {
+      lst.addLast(c);
+    }
+
+    /**
+     * Set the average L*a*b value for this box
+     * 
+     * @return RGB point
+     * 
+     */
+    protected P3 setColor() {
+      int count = lst.size();
+      lab = new P3();
+      for (int i = count; --i >= 0;) {
+        lab.add(lst.get(i).lab);
+      }
+      lab.scale(1f / count);
+      P3 ptrgb = toRGB(lab);
+      rgb = CU.colorPtToFFRGB(ptrgb);
+
+      //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;
+    }
+
+    /**
+     * 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 boolean splitBox(Lst<ColorCell> boxes) {
+      int n = lst.size();
+      if (n < 2)
+        return false;
+      int newIndex = boxes.size();
+      ColorCell newBox = new ColorCell(newIndex);
+      boxes.addLast(newBox);
+      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;
+      }
+      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));
+//        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));
+//        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));
+//        maxb = val - 0.001f;
+//        newBox.minb = val;
+        break;
+      }
+      return true;
+    }
+
+    @Override
+    public String toString() {
+      return index + " " + Integer.toHexString(rgb);
+    }
+  }
+
   /**
-   * 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.
+   * Generate a palette and quantize all colors into it. 
    * 
    */
-  private void createColorTable() {
-    ColorVector colors = getColors();
-    Map<Integer, AdaptiveColorCollection> colors256 = getBest256(colors);
-    int nTotal = colors256.size();
+  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);
-    colorMap = finalizeColorMap(colors, colors256);
+    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() {
-    ColorVector colorVector = new ColorVector();
-    Map<Integer, ColorItem> ciHash = new Hashtable<Integer, ColorItem>();
-    int nColors = 0;
-    Integer key;
-    int ptTransparent = -1;
-    
-    for (int pt = 0, row = 0, transparentRgb = -1; row < height; ++row) {
-      for (int col = 0; col < width; ++col, pt++) {
-        int rgb = pixels[pt];
-        boolean isTransparent = (rgb >= 0);
-        if (isTransparent) {
-          if (ptTransparent < 0) {
-            // First transparent color; remember it.
-            ptTransparent = nColors;
-            transparentRgb = rgb;
-          } else if (rgb != transparentRgb) {
-            // A second transparent color; replace it with
-            // the first one.
-            pixels[pt] = rgb = transparentRgb;
-          }
+  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;
         }
-        ColorItem item = ciHash.get(key = Integer.valueOf(rgb));
-        if (item == null) {
-          item = new ColorItem(rgb, 1);
-          ciHash.put(key, item);
-          colorVector.addLast(item);
-          nColors++;
-        } else {
-          item.count++;
-        }
       }
+      if (maxCell == null || !maxCell.splitBox(boxes))
+        break;
     }
-    ciHash = null;
-   
-    if (logging)
-      System.out.println("# total image colors = " + nColors);
-    // sort by frequency
-    colorVector.sort();
-    return colorVector;
+    for (int i = 0; i < n; i++) {
+      ColorCell b = boxes.get(i);
+      palette[i] = b.setColor();
+      colorMap.put(Integer.valueOf(b.rgb), b);
+    }
+    System.out.println("GIF final color count: " + boxes.size());
+    quantizePixels(colorMap, boxes);
   }
 
   /**
-   * reduce GIF color collection to 256 or fewer by grouping shadings;
-   * create an initial color hash that is only to the final colors.
    * 
-   * @param colorVector
-   * @return nTotal;
+   * 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 Map<Integer, AdaptiveColorCollection> getBest256(ColorVector 
colorVector) {
-    // mask allows reducing colors by shading changes
-    int mask = 0x010101;
-    int nColors = colorVector.size();
-    int nMax = Math.max(nColors - 1, 0); // leave top 1 untouched
-    int nTotal = Integer.MAX_VALUE;
-    int index = 0;
-    Map<Integer, AdaptiveColorCollection> ht = null;
-    while (nTotal > 255) {
-      nTotal = nColors;
-      index = 0;
-      ht = new Hashtable<Integer, AdaptiveColorCollection>();
-      for (int i = 0; i < nMax; i++) {
-        ColorItem item = colorVector.get(i);
-        int rgb = (nTotal < 256 ? item.rgb : item.rgb & ~mask);
-        Integer key = Integer.valueOf(rgb);
-        if ((item.acc = ht.get(key)) == null)
-          ht.put(key, item.acc = new AdaptiveColorCollection(rgb, index++));
-        else
-          nTotal--;
+  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++) {
+        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, 1, pixelErr, p + width + 1);
+            }
+          }
+        }
+        pixels[p] = app.index;
       }
-      mask |= (mask <<= 1);
-      //if (Logger.debugging)
     }
-    ColorItem item = colorVector.get(nMax);
-    ht.put(Integer.valueOf(item.rgb),
-        item.acc = new AdaptiveColorCollection(item.rgb, index++));
-    if (logging)
-      System.out.println("# GIF colors = " + ht.size());
-    return ht;
   }
 
+  private void addError(P3 err, int f, P3[] pixelErr, int p) {
+    if (pixels[p] == backgroundColor)
+      return;
+    P3 errp = pixelErr[p];
+    if (errp == null)
+      errp = pixelErr[p] = new P3();
+    errp.scaleAdd2(f / 16f, err, errp);
+  }
+
+  //  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 ////////////////////////
+
   /**
-   * Create final color table red green blue arrays and generate final
-   * colorHash.
+   * includes logical screen descriptor
    * 
-   * @param colors
-   * @param colors256
-   * @return map from all unique colors to a specific index
+   * @throws IOException
    */
-  private Map<Integer, AdaptiveColorCollection> finalizeColorMap(
-                                                                 
Lst<ColorItem> colors,
-                                                                 Map<Integer, 
AdaptiveColorCollection> colors256) {
-    int mapSize = 1 << bitsPerPixel;
-    red = new int[mapSize];
-    green = new int[mapSize];
-    blue = new int[mapSize];
-    int nColors = colors.size();
-    Map<Integer, AdaptiveColorCollection> ht = new Hashtable<Integer, 
AdaptiveColorCollection>();
-    for (int i = 0; i < nColors; i++) {
-      ColorItem item = colors.get(i);
-      int rgb = item.rgb;
-      item.acc.addRgb(rgb, item.count);
-      ht.put(Integer.valueOf(rgb), item.acc);
-    }
-    for (AdaptiveColorCollection acc : colors256.values())
-      acc.setRgb();
-    return ht;
+  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 (transparentIndex != -1 || delayTime100ths >= 0) {
+    if (isTransparent || delayTime100ths >= 0) {
       putByte(0x21); // graphic control extension
       putByte(0xf9); // graphic control label
       putByte(4); // block size
-      int packedBytes = (transparentIndex == -1 ? 0 : 1) | (delayTime100ths > 
0 ? 2 : 0);
-      putByte(packedBytes); 
+      putByte((isTransparent ? 9 : 0) | (delayTime100ths > 0 ? 2 : 0)); // 
packed bytes 
       putWord(delayTime100ths > 0 ? delayTime100ths : 0);
-      putByte(transparentIndex == -1 ? 0 : transparentIndex);
+      putByte(0); // transparent index
       putByte(0); // end-of-block
     }
   }
 
-// see  
http://www.vurdalakov.net/misc/gif/netscape-looping-application-extension
-//      +---------------+
-//   0  |     0x21      |  Extension Label
-//      +---------------+
-//   1  |     0xFF      |  Application Extension Label
-//      +---------------+
-//   2  |     0x0B      |  Block Size
-//      +---------------+
-//   3  |               | 
-//      +-             -+
-//   4  |               | 
-//      +-             -+
-//   5  |               | 
-//      +-             -+
-//   6  |               | 
-//      +-  NETSCAPE   -+  Application Identifier (8 bytes)
-//   7  |               | 
-//      +-             -+
-//   8  |               | 
-//      +-             -+
-//   9  |               | 
-//      +-             -+
-//  10  |               | 
-//      +---------------+
-//  11  |               | 
-//      +-             -+
-//  12  |      2.0      |  Application Authentication Code (3 bytes)
-//      +-             -+
-//  13  |               | 
-//      +===============+                      --+
-//  14  |     0x03      |  Sub-block Data Size   |
-//      +---------------+                        |
-//  15  |     0x01      |  Sub-block ID          |
-//      +---------------+                        | Application Data Sub-block
-//  16  |               |                        |
-//      +-             -+  Loop Count (2 bytes)  |
-//  17  |               |                        |
-//      +===============+                      --+
-//  18  |     0x00      |  Block Terminator
-//      +---------------+
+  // see  
http://www.vurdalakov.net/misc/gif/netscape-looping-application-extension
+  //      +---------------+
+  //   0  |     0x21      |  Extension Label
+  //      +---------------+
+  //   1  |     0xFF      |  Application Extension Label
+  //      +---------------+
+  //   2  |     0x0B      |  Block Size
+  //      +---------------+
+  //   3  |               | 
+  //      +-             -+
+  //   4  |               | 
+  //      +-             -+
+  //   5  |               | 
+  //      +-             -+
+  //   6  |               | 
+  //      +-  NETSCAPE   -+  Application Identifier (8 bytes)
+  //   7  |               | 
+  //      +-             -+
+  //   8  |               | 
+  //      +-             -+
+  //   9  |               | 
+  //      +-             -+
+  //  10  |               | 
+  //      +---------------+
+  //  11  |               | 
+  //      +-             -+
+  //  12  |      2.0      |  Application Authentication Code (3 bytes)
+  //      +-             -+
+  //  13  |               | 
+  //      +===============+                      --+
+  //  14  |     0x03      |  Sub-block Data Size   |
+  //      +---------------+                        |
+  //  15  |     0x01      |  Sub-block ID          |
+  //      +---------------+                        | Application Data Sub-block
+  //  16  |               |                        |
+  //      +-             -+  Loop Count (2 bytes)  |
+  //  17  |               |                        |
+  //      +===============+                      --+
+  //  18  |     0x00      |  Block Terminator
+  //      +---------------+
 
   private void writeNetscapeLoopExtension() {
     putByte(0x21); // graphic control extension
     putByte(0xff); // netscape loop extension
     putByte(0x0B); // block size
     putString("NETSCAPE2.0");
-    putByte(3); 
-    putByte(1); 
+    putByte(3);
+    putByte(1);
     putWord(0); // loop indefinitely
     putByte(0); // end-of-block
-    
+
   }
 
   private int initCodeSize;
@@ -472,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();
@@ -488,14 +830,14 @@
   }
 
   ///// compression routines /////
-  
+
   private static final int EOF = -1;
 
   // Return the next pixel from the image
   private int nextPixel() {
     if (countDown-- == 0)
       return EOF;
-    int colorIndex = colorMap.get(Integer.valueOf(pixels[curpt])).index;
+    int colorIndex = pixels[curpt];
     // Bump the current X position
     ++curx;
     if (curx == width) {
@@ -506,26 +848,24 @@
       if (interlaced)
         updateY(INTERLACE_PARAMS[pass], INTERLACE_PARAMS[pass + 4]);
       else
-       ++cury;
+        ++cury;
     }
     curpt = cury * width + curx;
     return colorIndex & 0xff;
   }
 
-  private static final int[] INTERLACE_PARAMS = {
-    8, 8, 4, 2, 
-    4, 2, 1, 0};
+  private static final int[] INTERLACE_PARAMS = { 8, 8, 4, 2, 4, 2, 1, 0 };
 
   /**
    * 
-   *   Group 1 : Every 8th. row, starting with row 0.              (Pass 1)
-   *   
-   *   Group 2 : Every 8th. row, starting with row 4.              (Pass 2)
-   *   
-   *   Group 3 : Every 4th. row, starting with row 2.              (Pass 3)
-   *   
-   *   Group 4 : Every 2nd. row, starting with row 1.              (Pass 4)
+   * Group 1 : Every 8th. row, starting with row 0. (Pass 1)
    * 
+   * Group 2 : Every 8th. row, starting with row 4. (Pass 2)
+   * 
+   * Group 3 : Every 4th. row, starting with row 2. (Pass 3)
+   * 
+   * Group 4 : Every 2nd. row, starting with row 1. (Pass 4)
+   * 
    * @param yNext
    * @param yNew
    */
@@ -783,5 +1123,5 @@
       bufPt = 0;
     }
   }
-  
+
 }

Modified: branches/v14_2/Jmol/src/javajs/img/ImageEncoder.java
===================================================================
--- branches/v14_2/Jmol/src/javajs/img/ImageEncoder.java        2014-11-10 
02:28:42 UTC (rev 20096)
+++ branches/v14_2/Jmol/src/javajs/img/ImageEncoder.java        2014-11-10 
02:35:52 UTC (rev 20097)
@@ -108,8 +108,9 @@
 
   protected int[] pixels;
 
-  protected void putString(String str) {
-    out.append(str);
+  protected void putString(String s) {
+    byte[] b = s.getBytes();
+    out.write(b, 0, b.length);
   }
 
   protected void putByte(int b) {

Modified: branches/v14_2/Jmol/src/javajs/util/CU.java
===================================================================
--- branches/v14_2/Jmol/src/javajs/util/CU.java 2014-11-10 02:28:42 UTC (rev 
20096)
+++ branches/v14_2/Jmol/src/javajs/util/CU.java 2014-11-10 02:35:52 UTC (rev 
20097)
@@ -440,8 +440,8 @@
     return 0xFF000000 | (red << 16) | (grn << 8) | blu;
   }
 
-  public final static P3 colorPtFromString(String colorName, P3 pt) {
-    return toRGBpt(getArgbFromString(colorName), pt);
+  public final static P3 colorPtFromString(String colorName) {
+    return colorPtFromInt(getArgbFromString(colorName), null);
   }
 
   public final static P3 colorPtFromInt(int color, P3 pt) {
@@ -455,13 +455,6 @@
     return colorTriadToFFRGB(pt.x, pt.y, pt.z);
   }
 
-  public final static P3 toRGBpt(int color, P3 pt) {
-    pt.x = (color >> 16) & 0xFF;
-    pt.y = (color >> 8) & 0xFF;
-    pt.z = color & 0xFF;
-    return pt;
-  }
-
   public static void toRGB3f(int c, float[] f) {
     f[0] = ((c >> 16) & 0xFF) / 255f; // red
     f[1] = ((c >> 8) & 0xFF) / 255f;

Modified: branches/v14_2/Jmol/src/org/jmol/modelset/BondCollection.java
===================================================================
--- branches/v14_2/Jmol/src/org/jmol/modelset/BondCollection.java       
2014-11-10 02:28:42 UTC (rev 20096)
+++ branches/v14_2/Jmol/src/org/jmol/modelset/BondCollection.java       
2014-11-10 02:35:52 UTC (rev 20097)
@@ -49,8 +49,6 @@
   protected JmolMolecule[] molecules;
   protected int moleculeCount;
 
-  private boolean haveWarned;
-
   protected short defaultCovalentMad;
 
   private BS bsAromaticSingle;
@@ -171,7 +169,7 @@
     return bond;
   }
 
-  private Bond getOrAddBond(Atom atom, Atom atomOther, int order, short mad,
+  protected Bond getOrAddBond(Atom atom, Atom atomOther, int order, short mad,
                             BS bsBonds, float energy, boolean overrideBonding) 
{
     int i;
     if (order == Edge.BOND_ORDER_NULL || order == Edge.BOND_ORDER_ANY)
@@ -267,30 +265,6 @@
     return (distance2 > maxAcceptable2 ? (short) 0 : (short) 1);
   }
 
-  protected boolean checkValencesAndBond(Atom atomA, Atom atomB, int order, 
short mad,
-                            BS bsBonds) {
-    if (atomA.getCurrentBondCount() > JC.MAXIMUM_AUTO_BOND_COUNT
-        || atomB.getCurrentBondCount() > JC.MAXIMUM_AUTO_BOND_COUNT) {
-      if (!haveWarned)
-        Logger.warn("maximum auto bond count reached");
-      haveWarned = true;
-      return false;
-    }
-    int formalChargeA = atomA.getFormalCharge();
-    if (formalChargeA != 0) {
-      int formalChargeB = atomB.getFormalCharge();
-      if ((formalChargeA < 0 && formalChargeB < 0)
-          || (formalChargeA > 0 && formalChargeB > 0))
-        return false;
-    }
-    // don't connect differing altloc unless there are modulations
-    if (atomA.altloc != atomB.altloc
-        && atomA.altloc != '\0' && atomB.altloc != '\0' && 
getModulation(atomA.i) == null)
-      return false;
-    getOrAddBond(atomA, atomB, order, mad, bsBonds, 0, false);
-    return true;
-  }
-
   protected void deleteAllBonds2() {
     vwr.setShapeProperty(JC.SHAPE_STICKS, "reset", null);
     for (int i = bondCount; --i >= 0;) {

Modified: branches/v14_2/Jmol/src/org/jmol/modelset/ModelSet.java
===================================================================
--- branches/v14_2/Jmol/src/org/jmol/modelset/ModelSet.java     2014-11-10 
02:28:42 UTC (rev 20096)
+++ branches/v14_2/Jmol/src/org/jmol/modelset/ModelSet.java     2014-11-10 
02:35:52 UTC (rev 20097)
@@ -663,14 +663,18 @@
         int targetIndex = serialMap[targetSerial] - 1;
         if (sourceIndex < 0 || targetIndex < 0)
           continue;
+        Atom atomA = at[sourceIndex];
+        Atom atomB = at[targetIndex];
         if (bsExclude != null) {
-          if (at[sourceIndex].isHetero())
+          if (atomA.isHetero())
             bsExclude.set(sourceIndex);
-          if (at[targetIndex].isHetero())
+          if (atomB.isHetero())
             bsExclude.set(targetIndex);
         }
-        checkValencesAndBond(at[sourceIndex], at[targetIndex], order,
-            (order == Edge.BOND_H_REGULAR ? 1 : mad), null);
+        // don't connect differing altloc
+        if (atomA.altloc == atomB.altloc
+            || atomA.altloc == '\0' || atomB.altloc == '\0')
+          getOrAddBond(atomA, atomB, order, (order == Edge.BOND_H_REGULAR ? 1 
: mad), null, 0, false);
       }
     }
   }
@@ -2862,7 +2866,7 @@
             atomNear.getBondingRadius(), iter.foundDistance2(),
             minBondDistance2, bondTolerance);
         if (order > 0
-            && checkValencesAndBond(atom, atomNear, order, mad, bsBonds))
+            && autoBondCheck(atom, atomNear, order, mad, bsBonds))
           nNew++;
       }
       iter.release();
@@ -2872,6 +2876,32 @@
     return nNew;
   }
 
+  private boolean maxBondWarned;
+
+  private boolean autoBondCheck(Atom atomA, Atom atomB, int order,
+                                             short mad, BS bsBonds) {
+    if (atomA.getCurrentBondCount() > JC.MAXIMUM_AUTO_BOND_COUNT
+        || atomB.getCurrentBondCount() > JC.MAXIMUM_AUTO_BOND_COUNT) {
+      if (!maxBondWarned)
+        Logger.warn("maximum auto bond count reached");
+      maxBondWarned = true;
+      return false;
+    }
+    int formalChargeA = atomA.getFormalCharge();
+    if (formalChargeA != 0) {
+      int formalChargeB = atomB.getFormalCharge();
+      if ((formalChargeA < 0 && formalChargeB < 0)
+          || (formalChargeA > 0 && formalChargeB > 0))
+        return false;
+    }
+    // don't connect differing altloc unless there are modulations
+    if (atomA.altloc != atomB.altloc && atomA.altloc != '\0'
+        && atomB.altloc != '\0' && getModulation(atomA.i) == null)
+      return false;
+    getOrAddBond(atomA, atomB, order, mad, bsBonds, 0, false);
+    return true;
+  }
+
   private int autoBond_Pre_11_9_24(BS bsA, BS bsB, BS bsExclude, BS bsBonds,
                                    short mad) {
     if (ac == 0)
@@ -2945,7 +2975,7 @@
             atomNear.getBondingRadius(), iter.foundDistance2(),
             minBondDistance2, bondTolerance);
         if (order > 0) {
-          if (checkValencesAndBond(atom, atomNear, order, mad, bsBonds))
+          if (autoBondCheck(atom, atomNear, order, mad, bsBonds))
             nNew++;
         }
       }

Modified: branches/v14_2/Jmol/src/org/jmol/script/ScriptExpr.java
===================================================================
--- branches/v14_2/Jmol/src/org/jmol/script/ScriptExpr.java     2014-11-10 
02:28:42 UTC (rev 20096)
+++ branches/v14_2/Jmol/src/org/jmol/script/ScriptExpr.java     2014-11-10 
02:35:52 UTC (rev 20097)
@@ -1801,7 +1801,7 @@
           }
           break;
         case T.color:
-          CU.toRGBpt(vwr.getColorArgbOrGray(bond.colix), ptT);
+          CU.colorPtFromInt(vwr.getColorArgbOrGray(bond.colix), ptT);
           switch (minmaxtype) {
           case T.all:
             vout.addLast(P3.newP(ptT));

Modified: branches/v14_2/Jmol/src/org/jmol/script/ScriptMathProcessor.java
===================================================================
--- branches/v14_2/Jmol/src/org/jmol/script/ScriptMathProcessor.java    
2014-11-10 02:28:42 UTC (rev 20096)
+++ branches/v14_2/Jmol/src/org/jmol/script/ScriptMathProcessor.java    
2014-11-10 02:35:52 UTC (rev 20097)
@@ -981,9 +981,7 @@
         switch (x2.tok) {
         case T.string:
         case T.varray:
-          s = SV.sValue(x2);
-          pt = new P3();
-          return addXPt(CU.colorPtFromString(s, pt));
+          return addXPt(CU.colorPtFromString(SV.sValue(x2)));
         case T.integer:
         case T.decimal:
           return addXPt(vwr.getColorPointForPropertyValue(SV.fValue(x2)));

Modified: branches/v14_2/Jmol/src/org/jmol/scriptext/CmdExt.java
===================================================================
--- branches/v14_2/Jmol/src/org/jmol/scriptext/CmdExt.java      2014-11-10 
02:28:42 UTC (rev 20096)
+++ branches/v14_2/Jmol/src/org/jmol/scriptext/CmdExt.java      2014-11-10 
02:35:52 UTC (rev 20097)
@@ -497,6 +497,10 @@
         fileName += ".gif";
       int i = 2;
       boolean isRock = false;
+      if (tok == T.loop) {
+        looping = true;
+        tok = tokAt(++i);
+      }
       switch (tokAt(i)) {
       case T.rock:
         isRock = true;
@@ -522,14 +526,12 @@
           axis = "y";
         boolean wf = vwr.g.waitForMoveTo;
         s = "set waitformoveto true;" + PT.rep(s, "Y", axis) + ";set 
waitformoveto " + wf;
-        s = "capture " + PT.esc(fileName) + " -1;" + s + ";capture;";
+        s = "capture " + PT.esc(fileName) + " LOOP;" + s + ";capture;";
         e.cmdScript(0, null, s);
         return;
       case T.decimal:
       case T.integer:
         endTime = floatParameter(2);
-        if (endTime < 0)
-          looping = true;
         break;
       }
       if (chk)
@@ -6075,6 +6077,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;"))
+          fileName = fileName.substring(0, fileName.length() - 1);
       }
       if (type.equals("MNU")) {
         type = "MENU";

Modified: branches/v14_2/Jmol/src/org/jmol/scriptext/MathExt.java
===================================================================
--- branches/v14_2/Jmol/src/org/jmol/scriptext/MathExt.java     2014-11-10 
02:28:42 UTC (rev 20096)
+++ branches/v14_2/Jmol/src/org/jmol/scriptext/MathExt.java     2014-11-10 
02:35:52 UTC (rev 20097)
@@ -295,17 +295,17 @@
     if (args.length == 2 && colorScheme.equalsIgnoreCase("TOHSL"))
       return mp.addXPt(CU.rgbToHSL(P3.newP(args[1].tok == T.point3f ? SV
           .ptValue(args[1])
-          : CU.colorPtFromString(args[1].asString(), new P3())), true));
+          : CU.colorPtFromString(args[1].asString())), true));
     if (args.length == 2 && colorScheme.equalsIgnoreCase("TORGB")) {
       P3 pt = P3.newP(args[1].tok == T.point3f ? SV.ptValue(args[1]) : CU
-          .colorPtFromString(args[1].asString(), new P3()));
+          .colorPtFromString(args[1].asString()));
       return mp.addXPt(args[1].tok == T.point3f ? CU.hslToRGB(pt) : pt);
     }
     if (args.length == 4 && (args[3].tok == T.on || args[3].tok == T.off)) {
       P3 pt1 = P3.newP(args[0].tok == T.point3f ? SV.ptValue(args[0]) : CU
-          .colorPtFromString(args[0].asString(), new P3()));
+          .colorPtFromString(args[0].asString()));
       P3 pt2 = P3.newP(args[1].tok == T.point3f ? SV.ptValue(args[1]) : CU
-          .colorPtFromString(args[1].asString(), new P3()));
+          .colorPtFromString(args[1].asString()));
       boolean usingHSL = (args[3].tok == T.on);
       if (usingHSL) {
         pt1 = CU.rgbToHSL(pt1, false);

Modified: branches/v14_2/Jmol/src/org/jmol/viewer/Jmol.properties
===================================================================
--- branches/v14_2/Jmol/src/org/jmol/viewer/Jmol.properties     2014-11-10 
02:28:42 UTC (rev 20096)
+++ branches/v14_2/Jmol/src/org/jmol/viewer/Jmol.properties     2014-11-10 
02:35:52 UTC (rev 20097)
@@ -4,8 +4,17 @@
 # THIS IS THE RELEASE BRANCH 
 # BUG FIXES ONLY, PLEASE
 
-Jmol.___JmolVersion="14.2.7_2014.10.27"
+Jmol.___JmolVersion="14.2.7_2014.11.09"
 
+bug fix: GIF writer not properly handling large numbers of colors
+  -- use of CIE L*a*b for color quantification
+  -- nearly identical to GIMP
+  -- uses MEAN_cut (not MEDIAN_cut) 
+  -- uses Floyd-Steinberg dithering
+  -- will not discolor background (as GIMP will do)
+
+JmolVersion="14.2.7_2014.10.27"
+
 bug fix: up-arrow in console may not return command if contains unicode
 bug fix: antialiasing can subtly change background color
 

Modified: branches/v14_2/Jmol/src/org/jmol/viewer/OutputManager.java
===================================================================
--- branches/v14_2/Jmol/src/org/jmol/viewer/OutputManager.java  2014-11-10 
02:28:42 UTC (rev 20096)
+++ branches/v14_2/Jmol/src/org/jmol/viewer/OutputManager.java  2014-11-10 
02:35:52 UTC (rev 20097)
@@ -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);
@@ -623,7 +624,6 @@
     if (captureMode != null) {
       doCheck = false; // will be checked later
       mustRender = false;
-      type = "GIF";
     }
     if (doCheck)
       fileName = getOutputFileNameFromDialog(fileName, quality);

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