Revision: 20087 http://sourceforge.net/p/jmol/code/20087 Author: hansonr Date: 2014-10-27 09:35:20 +0000 (Mon, 27 Oct 2014) Log Message: ----------- Jmol.___JmolVersion="14.3.8_2014.10.20"
bug fix: working on GIF writer -- some success with 256 colors using median-cut with RGB new feature: load HISTORY "saved.his" (Gabor Oszlanyi) -- loads command history with script in saved.his new feature: CTRL-PAGE_UP CTRL-PAGE_DOWN in console (Gabor Oszlanyi) -- searches for next instance of current start of command up or down command history bug fix: antialiasing can subtly change background color Modified Paths: -------------- trunk/Jmol/src/javajs/img/GifEncoder.java trunk/Jmol/src/org/jmol/console/ScriptEditor.java trunk/Jmol/src/org/jmol/g3d/Graphics3D.java trunk/Jmol/src/org/jmol/script/ScriptCompiler.java trunk/Jmol/src/org/jmol/script/ScriptEval.java trunk/Jmol/src/org/jmol/util/CommandHistory.java trunk/Jmol/src/org/jmol/viewer/Jmol.properties trunk/Jmol/src/org/jmol/viewer/OutputManager.java trunk/Jmol/src/org/jmol/viewer/TransformManager4D.java trunk/Jmol/src/org/jmol/viewer/Viewer.java trunk/Jmol/src/org/openscience/jmol/app/jmolpanel/console/AppConsole.java Modified: trunk/Jmol/src/javajs/img/GifEncoder.java =================================================================== --- trunk/Jmol/src/javajs/img/GifEncoder.java 2014-10-20 11:54:37 UTC (rev 20086) +++ trunk/Jmol/src/javajs/img/GifEncoder.java 2014-10-27 09:35:20 UTC (rev 20087) @@ -22,6 +22,8 @@ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. */ +// useful page: http://www.htmlhexcolor.com/0ac906 +// // GifEncoder - write out an image as a GIF // // Transparency handling and variable bit size courtesy of Jack Palevich. @@ -62,9 +64,11 @@ package javajs.img; +import javajs.util.AU; +import javajs.util.CU; import javajs.util.Lst; -import java.util.Collections; -import java.util.Comparator; + +import java.util.Arrays; import java.util.Hashtable; import java.util.Map; import java.io.IOException; @@ -73,6 +77,10 @@ * * GifEncoder extensively modified for Jmol by Bob Hanson * + * -- using median-cut with rgb + * + * -- TODO use median-cut with HSL + * * -- much simplified interface with ImageEncoder * * -- uses simple Hashtable with Integer() @@ -97,81 +105,351 @@ public class GifEncoder extends ImageEncoder { - private Map<Integer, AdaptiveColorCollection> colorMap; - protected int[] red, green, blue; + private int bitsPerPixel = 1; + private int[][] errors; + 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 class ColorItem { - AdaptiveColorCollection acc; int rgb; + int r; + int g; + int b; int count; - ColorItem(int rgb, int count) { + ColorItem(int rgb) { this.rgb = rgb; - this.count = count; + r = (rgb >> 16) & 0xFF; + g = (rgb >> 8) & 0xFF; + b = rgb & 0xFF; } + + @Override + public String toString() { + return Integer.toHexString(rgb) + " " + r + " " + g + " " + b; + } } protected class ColorVector extends Lst<ColorItem> { - void sort() { - CountComparator comparator = new CountComparator(); - Collections.sort(this, comparator); + 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(); } - - 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 boolean splitBoxes() { + int n = boxes.size(); + double maxVol = 0; + int imax = -1; + for (int i = n; --i >= 1;) { + double v = boxes.get(i).getVolume(); + if (v > maxVol) { + maxVol = v; + imax = i; + } } + if (imax < 0) + return false; + //System.out.println("splitting box " + imax); + boxes.get(imax).splitBox(boxes); + return true; } + } - private class AdaptiveColorCollection { - //int rgb; + 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 int r; - private int g; - private int b; - private int count; + private int r, g, b; + // min and max based on 0 0 0 for this rgb + private int 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 int 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 int 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; + int rgb; + Lst<ColorItem> lst; + private double volume; - AdaptiveColorCollection(int rgb, int index) { - //this.rgb = rgb; + ColorCell(int index) { this.index = index; - if (rgb >= 0 || rgb == transparentColor) - transparentIndex = index; + lst = new Lst<ColorItem>(); } - void addRgb(int rgb, int count) { - this.count += count; - b += (rgb & 0xFF) * count; - g += ((rgb >> 8) & 0xFF) * count; - r += ((rgb >> 16) & 0xFF) * count; + public double getVolume() { + if (volume != 0) + return volume; + if (lst.size() < 2) + return -1; + double 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; + int n = lst.size(); + for (int i = n; --i >= 0;) { + ColorItem c = lst.get(i); + if (c.r < rminr) + rminr = c.r; + if (c.g < rming) + rming = c.g; + if (c.b < rminb) + rminb = c.b; + if (c.r > rmaxr) + rmaxr = c.r; + if (c.g > rmaxg) + rmaxg = c.g; + if (c.b > rmaxb) + rmaxb = c.b; + } + return volume = ((d = rmaxr - rminr) * d + + (d = rmaxg - rming) * d + (d = rmaxb - rminb) * d); } - void setRgb() { - red[index] = (r / count) & 0xff; - green[index] =(g / count) & 0xff; - blue[index] = (b / count) & 0xff; + void setErrors() { + if (nextr != null) + maxre = ((nextr.minr + maxr) >> 1) - r; + if (nextg != null) + maxge = ((nextg.ming + maxg) >> 1) - g; + if (nextb != null) + maxbe = ((nextb.minb + maxb) >> 1) - b; + if (prevr != null) + minre = ((prevr.maxr + minr) >> 1) - r; + if (prevg != null) + minge = ((prevg.maxg + ming) >> 1) - g; + if (prevb != null) + minbe = ((prevb.maxb + minb) >> 1) - b; } + + void addItem(ColorItem c) { + lst.addLast(c); + } + + ColorItem average() { + int count = lst.size(); + for (int i = count; --i >= 0;) { + ColorItem c = lst.get(i); + colorMap.put(Integer.valueOf(c.rgb), this); + r += c.r; + g += c.g; + b += c.b; + } + r = (r / count) & 0xff; + g = (g / count) & 0xff; + b = (b / count) & 0xff; + rgb = CU.rgb(r, g, b); + red[index] = r; + green[index] = g; + blue[index] = b; + /* + 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(this.rgb), this); + //System.out.println(index + " " + r + " " + g + " " + b + " " + (maxr - minr)+ " " + (maxg - ming) + " " + (maxb-minb)); + return new ColorItem(rgb); + } + + private int[] ar, ag, ab; + + /** + * use median_cut algorithm to split the box, + * creating a doubly linked list + * + * @param boxes + */ + protected void splitBox(Lst<ColorCell> boxes) { + int n = lst.size(); + if (n < 2) + return; + int newIndex = boxes.size(); + ColorCell newBox = new ColorCell(newIndex); + boxes.addLast(newBox); + for (int i = 0; i < 3; i++) + getArray(i); + int ranger = ar[n - 1] - ar[0]; + int rangeg = ag[n - 1] - ag[0]; + int rangeb = ab[n - 1] - ab[0]; + int mode = (ranger >= rangeg ? (ranger >= rangeb ? 0 : 2) + : rangeg >= rangeb ? 1 : 2); + int[] a = (mode == 0 ? ar : mode == 1 ? ag : ab); + int median = n / 2; + int 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]; + 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); + volume = 0; + switch (mode) { + case 0: + for (int i = lst.size(); --i >= 0;) + if (lst.get(i).r >= val) + newBox.addItem(lst.remove(i)); + newBox.prevr = this; + nextr = newBox; + maxr = val - 1; + newBox.minr = val; + break; + case 1: + for (int i = lst.size(); --i >= 0;) + if (lst.get(i).g >= val) + newBox.addItem(lst.remove(i)); + newBox.prevg = this; + nextg = newBox; + maxg = val - 1; + newBox.ming = val; + break; + case 2: + for (int i = lst.size(); --i >= 0;) + if (lst.get(i).b >= val) + newBox.addItem(lst.remove(i)); + newBox.prevb = this; + nextb = newBox; + maxb = val - 1; + newBox.minb = val; + break; + } + //System.out.println(this + " -"+mode+"-> " + newBox +" " + lst.size() + "/" + newBox.lst.size()); + } + + /** + * Get sorted array of unique component entries + * + * @param ic 0(red) 1(green) 2(blue) + */ + private void getArray(int ic) { + int[] a = new int[lst.size()]; + for (int i = a.length; --i >= 0;) { + ColorItem c = lst.get(i); + a[i] = (ic == 0 ? c.r : ic == 1 ? c.g : c.b); + } + 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 [r g b] + * @param rgb + * @param err + * @return color cell + * + */ + ColorCell findCell(int rgb, int[] err) { + err[0] = ((rgb >> 16) & 0xFF) - r; + //System.out.println(Integer.toHexString(rgb) + " " + this + " " + PT.toJSON(null, err)); + if (err[0] > maxre && nextr != null) + return nextr.findCell(rgb, err); + if (err[0] < minre && prevr != null) + return prevr.findCell(rgb, err); + + err[1] = ((rgb >> 8) & 0xFF) - g; + if (err[1] > maxge && nextg != null) + return nextg.findCell(rgb, err); + if (err[1] < minge && prevg != null) + return prevg.findCell(rgb, err); + + err[2] = (rgb & 0xFF) - b; + if (err[2] > maxbe && nextb != null) + return nextb.findCell(rgb, err); + if (err[2] < minbe && prevb != null) + return prevb.findCell(rgb, err); + return this; // in this box or best we can do + } + + @Override + public String toString() { + return index + " " + Integer.toHexString(rgb); + } } - 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; - int transparentColor; - private int backgroundColor; - private boolean reducedColors; - private boolean floydSteinberg = true; - /** * we allow for animated GIF by being able to re-enter * the code with different parameters held in params @@ -180,16 +458,21 @@ */ @Override protected void setParams(Map<String, Object> params) { - this.params = params; - reducedColors = (params.get("reducedColors") == Boolean.TRUE); + 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 = transparentColor = ic.intValue(); + backgroundColor = ic.intValue(); + isTransparent = true; } + + floydSteinberg = false; + + logging = true; + interlaced = (Boolean.TRUE == params.get("interlaced")); if (interlaced || !params.containsKey("captureMode")) return; @@ -199,6 +482,7 @@ // ignore } int imode = "maec".indexOf(((String) params.get("captureMode")).substring(0, 1)); + if (logging) System.out.println("GIF capture mode " + imode); switch (imode) { @@ -227,13 +511,6 @@ } - // 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) @@ -272,17 +549,25 @@ } /** - * 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. + * 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(); - Map<Integer, AdaptiveColorCollection> colors256 = getBest256(colors); - int nTotal = colors256.size(); + int nTotal = colors.size();//colors256.size(); + setBitsPerPixel(nTotal); + colors.indexColors(); + ditherPixels(); + } + + private void setBitsPerPixel(int nTotal) { bitsPerPixel = (nTotal <= 2 ? 1 : nTotal <= 4 ? 2 : nTotal <= 16 ? 4 : 8); - colorMap = finalizeColorMap(colors, colors256); + int mapSize = 1 << bitsPerPixel; + red = new int[mapSize]; + green = new int[mapSize]; + blue = new int[mapSize]; } /** @@ -291,249 +576,108 @@ * @return the vector */ private ColorVector getColors() { + int n = pixels.length; + errors = AU.newInt2(n); + indexes = new int[n]; ColorVector colorVector = new ColorVector(); Map<Integer, ColorItem> ciHash = new Hashtable<Integer, ColorItem>(); int nColors = 0; - int ptTransparent = -1; - out: 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; - } - } - if ((nColors = addColor(colorVector, ciHash, rgb, nColors)) > 250) { - colorVector = ditherPixels(); - break out; - } - } - } + for (int i = 0; i < n; i++) + nColors += addColor(colorVector, ciHash, i); ciHash = null; - colorVector.sort(); - if (logging) - System.out.println("# total image colors = " + nColors); - // sort by frequency + //colorVector.sort(); + System.out.println("# total image colors = " + nColors); + // dont sort by frequency return colorVector; } private int addColor(ColorVector colorVector, Map<Integer, ColorItem> ciHash, - int rgb, int nColors) { + int pt) { + int rgb = pixels[pt]; Integer key = Integer.valueOf(rgb); ColorItem item = ciHash.get(key); if (item == null) { - item = new ColorItem(rgb, 1); + item = new ColorItem(rgb); ciHash.put(key, item); colorVector.addLast(item); - nColors++; - } else { - item.count++; + return 1; } - return nColors; + item.count++; + return 0; } - private ColorVector ditherPixels() { - ColorVector colorVector = new ColorVector(); - Map<Integer, ColorItem> ciHash = new Hashtable<Integer, ColorItem>(); - int nColors = 0; - int[] sb = toByteARGB(pixels); - int w4 = width * 4; - int r1 = 25; - int r2 = 2 * r1 + 1; - int ci = -1; - if (reducedColors) { - // Atkinson - // X 1 1 - // 1 1 1 - // 1 - // http://www.tannerhelland.com/4660/dithering-eleven-algorithms-source-code/ - for (int i = 0; i < height; ++i) { - boolean lastRow = (i == height - 1); - boolean notNextToLastRow = (i < height - 2); - for (int j = 0; j < width; ++j) { - if (sb[++ci] != backgroundColor) { - boolean notLastCol = (j < width - 1); - boolean notNextToLastCol = (j < width - 2); - for (int k = 0; k < 3; k++) { - int trueColor = sb[++ci]; - int roundedColor = Math.round((trueColor + r1) / r2) * r2; - int err = (trueColor - roundedColor) >> 3; - sb[ci] = roundedColor; + /** + * + * Idea is to find the closest known color + * and then spread out the error over four pixels + * + */ + private void ditherPixels() { + 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); + try { + ColorCell app = colors256.get(Integer.valueOf(rgb)); + if (app == null) { + int[] err = new int[3]; + app = colorMap.get(Integer.valueOf(pixels[p])); + if (floydSteinberg) { + app = app.findCell(rgb, err); + colorMap.put(Integer.valueOf(rgb), app); + boolean notLastCol = (j < width - 1); if (notLastCol) - sb[ci + 4] += err; - if (notNextToLastCol) - sb[ci + 8] += err; - if (notNextToLastRow) - sb[ci + w4 + w4] += err; - if (lastRow) - continue; - if (j > 0) - sb[ci + w4 - 4] += err; - sb[ci + w4] += err; - if (notLastCol) - sb[ci + w4 + 4] += err; + 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); + } } } + indexes[p] = app.index; + } catch (Throwable e) { + System.out.println("GIF error: " + e); } } - } else if (floydSteinberg ) { - for (int i = 0; i < height; ++i) { - boolean lastRow = (i == height - 1); - for (int j = 0; j < width; ++j) { - if (sb[++ci] != backgroundColor) { - boolean notLastCol = (j < width - 1); - for (int k = 0; k < 3; k++) { - int cc = sb[++ci]; - int rc = Math.round((cc + r1) / r2) * r2; - int err = cc - rc; - sb[ci] = rc; - if (notLastCol) - sb[ci + 4] += (err * 7) >> 4; - if (lastRow) - continue; - if (j > 0) - sb[ci + w4 - 4] += (err * 3) >> 4; - sb[ci + w4] += (err * 5) >> 4; - if (notLastCol) - sb[ci + w4 + 4] += (err * 1) >> 4; - } - } - } - } } - pixels = toIntARGB(sb); - for (int i = 0, pt = 0; i < height; ++i) { - for (int j = 0; j < width; ++j, pt++) { - nColors = addColor(colorVector, ciHash, pixels[pt], nColors); - } - } - System.out.println("GIF dithered to " + nColors + " colors"); - return colorVector; } - - int[] toIntARGB(int[] imgData) { - /* - * red=imgData.data[0]; - * green=imgData.data[1]; - * blue=imgData.data[2]; - * alpha=imgData.data[3]; - */ - int n = imgData.length / 4; - int[] iData = new int[n]; - for (int i = 0, j = 0; i < n;) { - int alpha = imgData[j++]; - if (alpha == 0xFE) { - iData[i++] = backgroundColor; - j += 3; - continue; - } - iData[i++] = (alpha << 24) | (imgData[j++] << 16) | imgData[j++] << 8 | imgData[j++]; - } - return iData; - } - int[] toByteARGB(int[] argbs) { - /* - * red=imgData.data[0]; - * green=imgData.data[1]; - * blue=imgData.data[2]; - * alpha=imgData.data[3]; - */ - int n = argbs.length * 4; - int[] iData = new int[n]; - for (int i = 0, j = 0; i < n; j++) { - int a = argbs[j]; - iData[i++] = (a == backgroundColor ? 0xFE : (a >> 24) & 0xFF); - iData[i++] = (a >> 16) & 0xFF; - iData[i++] = (a >> 8) & 0xFF; - iData[i++] = a & 0xFF; - } - return iData; - } - + private int getRGB(int p) { + int[] err = errors[p]; + int rgb = pixels[p]; + if (err == null) + return rgb; + int r = clamp(((rgb >> 16) & 0xFF) + err[0]); + int g = clamp(((rgb >> 8) & 0xFF) + err[1]); + int b = clamp(((rgb) & 0xFF) + err[2]); + return CU.rgb(r, g, b); + } - /** - * 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; - */ - 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--; - } - 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(int[] err, int f, int p) { + int[] errp = errors[p]; + if (errp == null) + errp = errors[p] = new int[3]; + for (int i = 0; i < 3; i++) + errp[i] += (err[i] * f) >> 4; } - /** - * Create final color table red green blue arrays and generate final - * colorHash. - * - * @param colors - * @param colors256 - * @return map from all unique colors to a specific index - */ - 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; + int clamp(int c) { + return c < 0 ? 0 : c > 255 ? 255 : c; } + 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 - putByte((transparentIndex == -1 ? 0 : 9) | (delayTime100ths > 0 ? 2 : 0)); // packed bytes + 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 } } @@ -635,7 +779,7 @@ private int nextPixel() { if (countDown-- == 0) return EOF; - int colorIndex = colorMap.get(Integer.valueOf(pixels[curpt])).index; + int colorIndex = indexes[curpt]; // Bump the current X position ++curx; if (curx == width) { @@ -923,5 +1067,4 @@ bufPt = 0; } } - } Modified: trunk/Jmol/src/org/jmol/console/ScriptEditor.java =================================================================== --- trunk/Jmol/src/org/jmol/console/ScriptEditor.java 2014-10-20 11:54:37 UTC (rev 20086) +++ trunk/Jmol/src/org/jmol/console/ScriptEditor.java 2014-10-27 09:35:20 UTC (rev 20087) @@ -550,6 +550,9 @@ clearContent(); if (text == null) return ""; + int pt = text.indexOf('\1'); + if (pt >= 0) + text = text.substring(0, pt).trim(); if (!text.endsWith("\n")) text += "\n"; try { Modified: trunk/Jmol/src/org/jmol/g3d/Graphics3D.java =================================================================== --- trunk/Jmol/src/org/jmol/g3d/Graphics3D.java 2014-10-20 11:54:37 UTC (rev 20086) +++ trunk/Jmol/src/org/jmol/g3d/Graphics3D.java 2014-10-27 09:35:20 UTC (rev 20087) @@ -582,12 +582,17 @@ // television effect. We want to avoid that. Here we can do that // because the colors will be blurred anyway. - if (downsampleZBuffer) + if (downsampleZBuffer) { bgcheck += ((bgcheck & 0xFF) == 0xFF ? -1 : 1); + } else { + } + bgcheck &= 0xFFFFFF; for (int i = pbuf.length; --i >= 0;) if (pbuf[i] == 0) pbuf[i] = bgcheck; - bgcheck &= 0xFFFFFF; + int bg0 = ((bgcheck >> 2) & 0x3F3F3F3F)<< 2; + bg0 += (bg0 & 0xC0C0C0C0) >> 6; + for (int i = windowHeight; --i >= 0; offset4 += width4) for (int j = windowWidth; --j >= 0; ++offset1) { @@ -609,6 +614,9 @@ + ((pbuf[offset4] >> 2) & 0x3F3F3F3F) + ((pbuf[offset4++ + width4] >> 2) & 0x3F3F3F3F); argb += (argb & 0xC0C0C0C0) >> 6; + if (argb == bg0) + argb = bgcheck; + /** * I don't know why this is necessary. * Modified: trunk/Jmol/src/org/jmol/script/ScriptCompiler.java =================================================================== --- trunk/Jmol/src/org/jmol/script/ScriptCompiler.java 2014-10-20 11:54:37 UTC (rev 20086) +++ trunk/Jmol/src/org/jmol/script/ScriptCompiler.java 2014-10-27 09:35:20 UTC (rev 20087) @@ -1077,6 +1077,7 @@ case T.menu: case T.orientation: case T.append: + case T.history: if (nTokens != 1) return ERROR; //$FALL-THROUGH$ Modified: trunk/Jmol/src/org/jmol/script/ScriptEval.java =================================================================== --- trunk/Jmol/src/org/jmol/script/ScriptEval.java 2014-10-20 11:54:37 UTC (rev 20086) +++ trunk/Jmol/src/org/jmol/script/ScriptEval.java 2014-10-27 09:35:20 UTC (rev 20087) @@ -4102,12 +4102,18 @@ // load TEMPERATURE // load OCCUPANCY // load PARTIALCHARGE + // load HISTORY switch (tok) { case T.menu: String m = paramAsStr(checkLast(2)); if (!chk) vwr.setMenu(m, true); return; + case T.history: + String h = paramAsStr(checkLast(2)); + if (!chk) + vwr.setHistory(h); + return; case T.data: isData = true; loadScript.append(" /*data*/ data"); Modified: trunk/Jmol/src/org/jmol/util/CommandHistory.java =================================================================== --- trunk/Jmol/src/org/jmol/util/CommandHistory.java 2014-10-20 11:54:37 UTC (rev 20086) +++ trunk/Jmol/src/org/jmol/util/CommandHistory.java 2014-10-27 09:35:20 UTC (rev 20087) @@ -207,6 +207,20 @@ return str; } + public String find(String cmd, int dir) { + int cpos = cursorPos; + String c = cmd; + while (c != null) { + c = getSetHistory(dir); + if (c == null) + break; + if (c.startsWith(cmd)) + return c; + } + cursorPos = cpos; + return null; + } + public String removeCommand() { return removeCommand(nextCommand - 1); } @@ -240,5 +254,5 @@ //for (int i = 0; i < nextCommand; i++) //System.out.println("HISTORY:" + i+" "+commandList.get(i)); } - + } Modified: trunk/Jmol/src/org/jmol/viewer/Jmol.properties =================================================================== --- trunk/Jmol/src/org/jmol/viewer/Jmol.properties 2014-10-20 11:54:37 UTC (rev 20086) +++ trunk/Jmol/src/org/jmol/viewer/Jmol.properties 2014-10-27 09:35:20 UTC (rev 20087) @@ -15,8 +15,20 @@ 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.10.15b" +Jmol.___JmolVersion="14.3.8_2014.10.20" +bug fix: working on GIF writer -- some success with 256 colors using median-cut with RGB + +new feature: load HISTORY "saved.his" (Gabor Oszlanyi) + -- loads command history with script in saved.his + +new feature: CTRL-PAGE_UP CTRL-PAGE_DOWN in console (Gabor Oszlanyi) + -- searches for next instance of current start of command up or down command history + +bug fix: antialiasing can subtly change background color + +JmolVersion="14.3.8_2014.10.15b" + bug fix: GIFT (transparent-background GIF) dithering transparent background JmolVersion="14.3.8_2014.10.15" Modified: trunk/Jmol/src/org/jmol/viewer/OutputManager.java =================================================================== --- trunk/Jmol/src/org/jmol/viewer/OutputManager.java 2014-10-20 11:54:37 UTC (rev 20086) +++ trunk/Jmol/src/org/jmol/viewer/OutputManager.java 2014-10-27 09:35:20 UTC (rev 20087) @@ -289,6 +289,8 @@ } boolean doClose = true; try { + if (type.equals("Gif") && vwr.getTestFlag(2)) + params.put("reducedColors", Boolean.TRUE); int w = objImage == null ? -1 : PT.isAI(objImage) ? ((Integer) params .get("width")).intValue() : vwr.apiPlatform .getImageWidth(objImage); Modified: trunk/Jmol/src/org/jmol/viewer/TransformManager4D.java =================================================================== --- trunk/Jmol/src/org/jmol/viewer/TransformManager4D.java 2014-10-20 11:54:37 UTC (rev 20086) +++ trunk/Jmol/src/org/jmol/viewer/TransformManager4D.java 2014-10-27 09:35:20 UTC (rev 20087) @@ -138,7 +138,7 @@ @Override public synchronized void calcTransformMatrix() { super.calcTransformMatrix(); - is4D = vwr.getTestFlag(2); + //is4D = vwr.getTestFlag(2); doTransform4D = (is4D && !stereoFrame && mode != MODE_NAVIGATION); if (!doTransform4D) return; Modified: trunk/Jmol/src/org/jmol/viewer/Viewer.java =================================================================== --- trunk/Jmol/src/org/jmol/viewer/Viewer.java 2014-10-20 11:54:37 UTC (rev 20086) +++ trunk/Jmol/src/org/jmol/viewer/Viewer.java 2014-10-27 09:35:20 UTC (rev 20087) @@ -6939,6 +6939,7 @@ case 2: // passed to MOCalcuation, but not used // nciCalculation special params.testFlag = 2 "absolute" calc. + // GIF reducedColors return g.testFlag2; case 3: // isosurface numbers @@ -8161,6 +8162,16 @@ return commandHistory.getSetHistory(howFarBack); } + public String historyFind(String cmd, int dir) { + return commandHistory.find(cmd, dir); + } + + public void setHistory(String f) { + commandHistory.getSetHistory(Integer.MIN_VALUE); + commandHistory.addCommand(getFileAsString(f, true)); + } + + // /////////////////////////////////////////////////////////////// // image and file export // /////////////////////////////////////////////////////////////// @@ -9754,5 +9765,4 @@ } } - } Modified: trunk/Jmol/src/org/openscience/jmol/app/jmolpanel/console/AppConsole.java =================================================================== --- trunk/Jmol/src/org/openscience/jmol/app/jmolpanel/console/AppConsole.java 2014-10-20 11:54:37 UTC (rev 20086) +++ trunk/Jmol/src/org/openscience/jmol/app/jmolpanel/console/AppConsole.java 2014-10-27 09:35:20 UTC (rev 20087) @@ -577,6 +577,7 @@ private EnterListener enterListener; boolean checking = false; + private String pageUpBuffer; ConsoleTextPane(AppConsole appConsole) { super(new ConsoleDocument()); @@ -683,12 +684,19 @@ } nTab = 0; } + if ((kcode == KeyEvent.VK_PAGE_UP || kcode == KeyEvent.VK_PAGE_DOWN) + && ke.isControlDown()) { + if (kid == KeyEvent.KEY_PRESSED) + recallCommand(kcode == KeyEvent.VK_PAGE_UP, true); + return; + } + pageUpBuffer = null; if (kcode == KeyEvent.VK_UP && kid == KeyEvent.KEY_PRESSED && !ke.isControlDown()) { - recallCommand(true); + recallCommand(true, false); } else if (kcode == KeyEvent.VK_DOWN && kid == KeyEvent.KEY_PRESSED && !ke.isControlDown()) { - recallCommand(false); + recallCommand(false, false); } else if ((kcode == KeyEvent.VK_DOWN || kcode == KeyEvent.VK_UP) && kid == KeyEvent.KEY_PRESSED && ke.isControlDown()) { // If Control key is down, redefines the event as if it @@ -718,10 +726,16 @@ * Recall command history. * * @param up - * - history up or down + * - history up or down + * @param pageup + * TODO */ - void recallCommand(boolean up) { - String cmd = vwr.getSetHistory(up ? -1 : 1); + void recallCommand(boolean up, boolean pageup) { + String cmd = (pageup ? vwr + .historyFind(pageUpBuffer == null ? (pageUpBuffer = consoleDoc + .getCommandString()) : pageUpBuffer, up ? -1 + : 1) : vwr.getSetHistory(up ? -1 + : 1)); if (cmd == null) { return; } 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