Merge branch 'master' of https://source.jalview.org/git/jalviewjs.git
[jalviewjs.git] / src / javajs / img / PngEncoder.java
index dd07881..7c583a1 100644 (file)
-/* $RCSfile$\r
- * $Author: nicove $\r
- * $Date: 2007-03-30 12:26:16 -0500 (Fri, 30 Mar 2007) $\r
- * $Revision: 7275 $\r
- *\r
- * Copyright (C) 2002-2005  The Jmol Development Team\r
- *\r
- * Contact: jmol-developers@lists.sf.net\r
- *\r
- *  This library is free software; you can redistribute it and/or\r
- *  modify it under the terms of the GNU Lesser General Public\r
- *  License as published by the Free Software Foundation; either\r
- *  version 2.1 of the License, or (at your option) any later version.\r
- *\r
- *  This library is distributed in the hope that it will be useful,\r
- *  but WITHOUT ANY WARRANTY; without even the implied warranty of\r
- *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\r
- *  Lesser General Public License for more details.\r
- *\r
- *  You should have received a copy of the GNU Lesser General Public\r
- *  License along with this library; if not, write to the Free Software\r
- *  Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.\r
- */\r
-package javajs.img;\r
-\r
-import java.util.Map;\r
-import java.util.zip.Deflater;\r
-import java.util.zip.DeflaterOutputStream;\r
-import java.io.ByteArrayOutputStream;\r
-import java.io.IOException;\r
-\r
-\r
-\r
-/**\r
- * \r
- * Modified by Bob Hanson hansonr@stolaf.edu to be a subclass of ImageEncoder\r
- * and to use javajs.util.OutputChannel instead of just returning bytes. Also includes: \r
- *  \r
- * -- JavaScript-compatible image processing\r
- *  \r
- * -- transparent background option\r
- *  \r
- * -- more efficient calculation of needs for pngBytes \r
- * \r
- * -- option to use pre-created PNGJ image data (3/19/14; Jmol 14.1.12)\r
- * \r
- * -- PNGJ format:\r
- * \r
- * // IHDR chunk \r
- * \r
- * // tEXt chunk "Jmol type - <PNG0|PNGJ><0000000pt>+<000000len>" \r
- * \r
- * // tEXt chunk "Software - Jmol <version>"\r
- * \r
- * // tEXt chunk "Creation Time - <date>"\r
- * \r
- * // tRNS chunk transparent color, if desired\r
- *\r
- * // IDAT chunk (image data)\r
- * \r
- * // IEND chunk \r
- * \r
- * // [JMOL ZIP FILE APPENDIX]\r
- * \r
- * Original Comment:\r
- * \r
- * PngEncoder takes a Java Image object and creates a byte string which can be\r
- * saved as a PNG file. The Image is presumed to use the DirectColorModel.\r
- * \r
- * Thanks to Jay Denny at KeyPoint Software http://www.keypoint.com/ who let me\r
- * develop this code on company time.\r
- * \r
- * You may contact me with (probably very-much-needed) improvements, comments,\r
- * and bug fixes at:\r
- * \r
- * david@catcode.com\r
- * \r
- * @author J. David Eisenberg\r
- * @author http://catcode.com/pngencoder/\r
- * @author Christian Ribeaud (christian.ribeaud@genedata.com)\r
- * @author Bob Hanson (hansonr@stolaf.edu)\r
- * \r
- * @version 1.4, 31 March 2000\r
- */\r
-public class PngEncoder extends CRCEncoder {\r
-\r
-  /** Constants for filters */\r
-  public static final int FILTER_NONE = 0;\r
-  public static final int FILTER_SUB = 1;\r
-  public static final int FILTER_UP = 2;\r
-  public static final int FILTER_LAST = 2;\r
-  \r
-  private static final int PT_FIRST_TAG = 37;\r
-\r
-  private boolean encodeAlpha;\r
-  private int filter = FILTER_NONE;\r
-  private int bytesPerPixel;\r
-  private int compressionLevel;\r
-  private String type;\r
-  private Integer transparentColor;\r
-\r
-  private byte[] appData;\r
-  private String appPrefix;\r
-  private String comment;\r
-  private byte[] bytes;\r
-\r
-  \r
-  public PngEncoder() {\r
-    super();\r
-  }\r
-\r
-  @Override\r
-  protected void setParams(Map<String, Object> params) {\r
-    if (quality < 0)\r
-      quality = (params.containsKey("qualityPNG") ? ((Integer) params\r
-          .get("qualityPNG")).intValue() : 2);\r
-    if (quality > 9)\r
-      quality = 9;\r
-    encodeAlpha = false;\r
-    filter = FILTER_NONE;\r
-    compressionLevel = quality;\r
-    transparentColor = (Integer) params.get("transparentColor");\r
-    comment = (String) params.get("comment");\r
-    type = (params.get("type") + "0000").substring(0, 4);\r
-    bytes = (byte[]) params.get("pngImgData");\r
-    appData = (byte[]) params.get("pngAppData");\r
-    appPrefix = (String) params.get("pngAppPrefix");\r
-  }\r
-\r
-  \r
-\r
-  @Override\r
-  protected void generate() throws IOException {\r
-    if (bytes == null) {\r
-      if (!pngEncode()) {\r
-        out.cancel();\r
-        return;\r
-      }\r
-      bytes = getBytes();\r
-    } else {\r
-      dataLen = bytes.length;\r
-    }\r
-    int len = dataLen;\r
-    if (appData != null) {\r
-      setJmolTypeText(appPrefix, bytes, len, appData.length,\r
-          type);\r
-      out.write(bytes, 0, len);\r
-      len = (bytes = appData).length;\r
-    }\r
-    out.write(bytes, 0, len);\r
-  }\r
-\r
-\r
-  /**\r
-   * Creates an array of bytes that is the PNG equivalent of the current image,\r
-   * specifying whether to encode alpha or not.\r
-   * \r
-   * @return        true if successful\r
-   * \r
-   */\r
-  private boolean pngEncode() {\r
-\r
-    byte[] pngIdBytes = { -119, 80, 78, 71, 13, 10, 26, 10 };\r
-\r
-    writeBytes(pngIdBytes);\r
-    //hdrPos = bytePos;\r
-    writeHeader();\r
-    writeText(getApplicationText(appPrefix, type, 0, 0));\r
-\r
-    writeText("Software\0Jmol " + comment);\r
-    writeText("Creation Time\0" + date);\r
-\r
-    if (!encodeAlpha && transparentColor != null)\r
-      writeTransparentColor(transparentColor.intValue());\r
-    //dataPos = bytePos;\r
-    return writeImageData();\r
-  }\r
-\r
-  /**\r
-   * Fill in the Jmol type text area with number of bytes of PNG data and number\r
-   * of bytes of Jmol state data and fix checksum.\r
-   * \r
-   * If we do not do this, then the checksum will be wrong, and Jmol and some\r
-   * other programs may not be able to read the PNG image.\r
-   * \r
-   * This was corrected for Jmol 12.3.30. Between 12.3.7 and 12.3.29, PNG files\r
-   * created by Jmol have incorrect checksums.\r
-   * \r
-   * @param prefix \r
-   * \r
-   * @param b\r
-   * @param nPNG\r
-   * @param nState\r
-   * @param type\r
-   */\r
-  private static void setJmolTypeText(String prefix, byte[] b, int nPNG, int nState, String type) {\r
-    String s = "tEXt" + getApplicationText(prefix, type, nPNG, nState);\r
-    CRCEncoder encoder = new PngEncoder();\r
-    byte[] test = s.substring(0, 4 + prefix.length()).getBytes();\r
-    for (int i = test.length; -- i >= 0;) \r
-      if (b[i + PT_FIRST_TAG] != test[i]) {\r
-        System.out.println("image is not of the right form; appending data, but not adding tEXt tag.");\r
-        return;\r
-      }\r
-    encoder.setData(b, PT_FIRST_TAG);\r
-    encoder.writeString(s);\r
-    encoder.writeCRC();\r
-  }\r
-\r
-  private static String getApplicationText(String prefix, String type, int nPNG, int nState) {\r
-    String sPNG = "000000000" + nPNG;\r
-    sPNG = sPNG.substring(sPNG.length() - 9);\r
-    String sState = "000000000" + nState;\r
-    sState = sState.substring(sState.length() - 9);\r
-    return prefix + "\0" + type + (type.equals("PNG") ? "0" : "") + sPNG + "+"\r
-        + sState;\r
-  }\r
-\r
-  //  /**\r
-  //   * Set the filter to use\r
-  //   *\r
-  //   * @param whichFilter from constant list\r
-  //   */\r
-  //  public void setFilter(int whichFilter) {\r
-  //    this.filter = (whichFilter <= FILTER_LAST ? whichFilter : FILTER_NONE);\r
-  //  }\r
-\r
-  //  /**\r
-  //   * Retrieve filtering scheme\r
-  //   *\r
-  //   * @return int (see constant list)\r
-  //   */\r
-  //  public int getFilter() {\r
-  //    return filter;\r
-  //  }\r
-\r
-  //  /**\r
-  //   * Set the compression level to use\r
-  //   *\r
-  //   * @param level 0 through 9\r
-  //   */\r
-  //  public void setCompressionLevel(int level) {\r
-  //    if ((level >= 0) && (level <= 9)) {\r
-  //      this.compressionLevel = level;\r
-  //    }\r
-  //  }\r
-\r
-  //  /**\r
-  //   * Retrieve compression level\r
-  //   *\r
-  //   * @return int in range 0-9\r
-  //   */\r
-  //  public int getCompressionLevel() {\r
-  //    return compressionLevel;\r
-  //  }\r
-\r
-  /**\r
-   * Write a PNG "IHDR" chunk into the pngBytes array.\r
-   */\r
-  private void writeHeader() {\r
-\r
-    writeInt4(13);\r
-    startPos = bytePos;\r
-    writeString("IHDR");\r
-    writeInt4(width);\r
-    writeInt4(height);\r
-    writeByte(8); // bit depth\r
-    writeByte(encodeAlpha ? 6 : 2); // color type or direct model\r
-    writeByte(0); // compression method\r
-    writeByte(0); // filter method\r
-    writeByte(0); // no interlace\r
-    writeCRC();\r
-  }\r
-\r
-  private void writeText(String msg) {\r
-    writeInt4(msg.length());\r
-    startPos = bytePos;\r
-    writeString("tEXt" + msg);\r
-    writeCRC();\r
-  }\r
-\r
-  /**\r
-   * Write a PNG "tRNS" chunk into the pngBytes array.\r
-   * \r
-   * @param icolor\r
-   */\r
-  private void writeTransparentColor(int icolor) {\r
-\r
-    writeInt4(6);\r
-    startPos = bytePos;\r
-    writeString("tRNS");\r
-    writeInt2((icolor >> 16) & 0xFF);\r
-    writeInt2((icolor >> 8) & 0xFF);\r
-    writeInt2(icolor & 0xFF);\r
-    writeCRC();\r
-  }\r
-\r
-  private byte[] scanLines; // the scan lines to be compressed\r
-  private int byteWidth; // width * bytesPerPixel\r
-\r
-  //private int hdrPos, dataPos, endPos;\r
-  //private byte[] priorRow;\r
-  //private byte[] leftBytes;\r
-\r
-\r
-  /**\r
-   * Write the image data into the pngBytes array. This will write one or more\r
-   * PNG "IDAT" chunks. In order to conserve memory, this method grabs as many\r
-   * rows as will fit into 32K bytes, or the whole image; whichever is less.\r
-   * \r
-   * \r
-   * @return true if no errors; false if error grabbing pixels\r
-   */\r
-  private boolean writeImageData() {\r
-\r
-    bytesPerPixel = (encodeAlpha ? 4 : 3);\r
-    byteWidth = width * bytesPerPixel;\r
-\r
-    int scanWidth = byteWidth + 1; // the added 1 is for the filter byte\r
-\r
-    //boolean doFilter = (filter != FILTER_NONE);\r
-\r
-    int rowsLeft = height; // number of rows remaining to write\r
-    //int startRow = 0; // starting row to process this time through\r
-    int nRows; // how many rows to grab at a time\r
-\r
-    int scanPos; // where we are in the scan lines\r
-\r
-    Deflater deflater = new Deflater(compressionLevel);\r
-    ByteArrayOutputStream outBytes = new ByteArrayOutputStream(1024);\r
-\r
-    DeflaterOutputStream compBytes = new DeflaterOutputStream(outBytes,\r
-        deflater);\r
-\r
-    int pt = 0; // overall image byte pointer\r
-    \r
-    // Jmol note: The entire image has been stored in pixels[] already\r
-    \r
-    try {\r
-      while (rowsLeft > 0) {\r
-        nRows = Math.max(1, Math.min(32767 / scanWidth, rowsLeft));\r
-        scanLines = new byte[scanWidth * nRows];\r
-        //        if (doFilter)\r
-        //          switch (filter) {\r
-        //          case FILTER_SUB:\r
-        //            leftBytes = new byte[16];\r
-        //            break;\r
-        //          case FILTER_UP:\r
-        //            priorRow = new byte[scanWidth - 1];\r
-        //            break;\r
-        //          }\r
-        int nPixels = width * nRows;\r
-        scanPos = 0;\r
-        //startPos = 1;\r
-        for (int i = 0; i < nPixels; i++, pt++) {\r
-          if (i % width == 0) {\r
-            scanLines[scanPos++] = (byte) filter;\r
-            //startPos = scanPos;\r
-          }\r
-          scanLines[scanPos++] = (byte) ((pixels[pt] >> 16) & 0xff);\r
-          scanLines[scanPos++] = (byte) ((pixels[pt] >> 8) & 0xff);\r
-          scanLines[scanPos++] = (byte) ((pixels[pt]) & 0xff);\r
-          if (encodeAlpha) {\r
-            scanLines[scanPos++] = (byte) ((pixels[pt] >> 24) & 0xff);\r
-          }\r
-          //          if (doFilter && i % width == width - 1) {\r
-          //            switch (filter) {\r
-          //            case FILTER_SUB:\r
-          //              filterSub();\r
-          //              break;\r
-          //            case FILTER_UP:\r
-          //              filterUp();\r
-          //              break;\r
-          //            }\r
-          //          }\r
-        }\r
-\r
-        /*\r
-         * Write these lines to the output area\r
-         */\r
-        compBytes.write(scanLines, 0, scanPos);\r
-\r
-        //startRow += nRows;\r
-        rowsLeft -= nRows;\r
-      }\r
-      compBytes.close();\r
-\r
-      /*\r
-       * Write the compressed bytes\r
-       */\r
-      byte[] compressedLines = outBytes.toByteArray();\r
-      writeInt4(compressedLines.length);\r
-      startPos = bytePos;\r
-      writeString("IDAT");\r
-      writeBytes(compressedLines);\r
-      writeCRC();\r
-      writeEnd();\r
-      deflater.finish();\r
-      return true;\r
-    } catch (IOException e) {\r
-      System.err.println(e.toString());\r
-      return false;\r
-    }\r
-  }\r
-\r
-  /**\r
-   * Write a PNG "IEND" chunk into the pngBytes array.\r
-   */\r
-  private void writeEnd() {\r
-    writeInt4(0);\r
-    startPos = bytePos;\r
-    writeString("IEND");\r
-    writeCRC();\r
-  }\r
-\r
-  ///**\r
-  //* Perform "sub" filtering on the given row.\r
-  //* Uses temporary array leftBytes to store the original values\r
-  //* of the previous pixels.  The array is 16 bytes long, which\r
-  //* will easily hold two-byte samples plus two-byte alpha.\r
-  //*\r
-  //*/\r
-  //private void filterSub() {\r
-  // int offset = bytesPerPixel;\r
-  // int actualStart = startPos + offset;\r
-  // int leftInsert = offset;\r
-  // int leftExtract = 0;\r
-  // //byte current_byte;\r
-  //\r
-  // for (int i = actualStart; i < startPos + byteWidth; i++) {\r
-  //   leftBytes[leftInsert] = scanLines[i];\r
-  //   scanLines[i] = (byte) ((scanLines[i] - leftBytes[leftExtract]) % 256);\r
-  //   leftInsert = (leftInsert + 1) % 0x0f;\r
-  //   leftExtract = (leftExtract + 1) % 0x0f;\r
-  // }\r
-  //}\r
-  //\r
-  ///**\r
-  //* Perform "up" filtering on the given row. Side effect: refills the prior row\r
-  //* with current row\r
-  //* \r
-  //*/\r
-  //private void filterUp() {\r
-  // int nBytes = width * bytesPerPixel;\r
-  // for (int i = 0; i < nBytes; i++) {\r
-  //   int pt = startPos + i;\r
-  //   byte b = scanLines[pt];\r
-  //   scanLines[pt] = (byte) ((scanLines[pt] - priorRow[i]) % 256);\r
-  //   priorRow[i] = b;\r
-  // }\r
-  //}\r
-\r
-}\r
+/* $RCSfile$
+ * $Author: nicove $
+ * $Date: 2007-03-30 12:26:16 -0500 (Fri, 30 Mar 2007) $
+ * $Revision: 7275 $
+ *
+ * Copyright (C) 2002-2005  The Jmol Development Team
+ *
+ * Contact: jmol-developers@lists.sf.net
+ *
+ *  This library is free software; you can redistribute it and/or
+ *  modify it under the terms of the GNU Lesser General Public
+ *  License as published by the Free Software Foundation; either
+ *  version 2.1 of the License, or (at your option) any later version.
+ *
+ *  This library is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ *  Lesser General Public License for more details.
+ *
+ *  You should have received a copy of the GNU Lesser General Public
+ *  License along with this library; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+package javajs.img;
+
+import java.util.Map;
+import java.util.zip.Deflater;
+import java.util.zip.DeflaterOutputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+
+
+
+/**
+ * 
+ * Modified by Bob Hanson hansonr@stolaf.edu to be a subclass of ImageEncoder
+ * and to use javajs.util.OutputChannel instead of just returning bytes. Also includes: 
+ *  
+ * -- JavaScript-compatible image processing
+ *  
+ * -- transparent background option
+ *  
+ * -- more efficient calculation of needs for pngBytes 
+ * 
+ * -- option to use pre-created PNGJ image data (3/19/14; Jmol 14.1.12)
+ * 
+ * -- PNGJ format:
+ * 
+ * // IHDR chunk 
+ * 
+ * // tEXt chunk "Jmol type - <PNG0|PNGJ><0000000pt>+<000000len>" 
+ * 
+ * // tEXt chunk "Software - Jmol <version>"
+ * 
+ * // tEXt chunk "Creation Time - <date>"
+ * 
+ * // tRNS chunk transparent color, if desired
+ *
+ * // IDAT chunk (image data)
+ * 
+ * // IEND chunk 
+ * 
+ * // [JMOL ZIP FILE APPENDIX]
+ * 
+ * Original Comment:
+ * 
+ * PngEncoder takes a Java Image object and creates a byte string which can be
+ * saved as a PNG file. The Image is presumed to use the DirectColorModel.
+ * 
+ * Thanks to Jay Denny at KeyPoint Software http://www.keypoint.com/ who let me
+ * develop this code on company time.
+ * 
+ * You may contact me with (probably very-much-needed) improvements, comments,
+ * and bug fixes at:
+ * 
+ * david@catcode.com
+ * 
+ * @author J. David Eisenberg
+ * @author http://catcode.com/pngencoder/
+ * @author Christian Ribeaud (christian.ribeaud@genedata.com)
+ * @author Bob Hanson (hansonr@stolaf.edu)
+ * 
+ * @version 1.4, 31 March 2000
+ */
+public class PngEncoder extends CRCEncoder {
+
+  /** Constants for filters */
+  public static final int FILTER_NONE = 0;
+  public static final int FILTER_SUB = 1;
+  public static final int FILTER_UP = 2;
+  public static final int FILTER_LAST = 2;
+  
+  private static final int PT_FIRST_TAG = 37;
+
+  private boolean encodeAlpha;
+  private int filter = FILTER_NONE;
+  private int bytesPerPixel;
+  private int compressionLevel;
+  private String type;
+  private Integer transparentColor;
+
+  private byte[] appData;
+  private String appPrefix;
+  private String comment;
+  private byte[] bytes;
+
+  
+  public PngEncoder() {
+    super();
+  }
+
+  @Override
+  protected void setParams(Map<String, Object> params) {
+    if (quality < 0)
+      quality = (params.containsKey("qualityPNG") ? ((Integer) params
+          .get("qualityPNG")).intValue() : 2);
+    if (quality > 9)
+      quality = 9;
+    encodeAlpha = false;
+    filter = FILTER_NONE;
+    compressionLevel = quality;
+    transparentColor = (Integer) params.get("transparentColor");
+    comment = (String) params.get("comment");
+    type = (params.get("type") + "0000").substring(0, 4);
+    bytes = (byte[]) params.get("pngImgData");
+    appData = (byte[]) params.get("pngAppData");
+    appPrefix = (String) params.get("pngAppPrefix");
+  }
+
+  
+
+  @Override
+  protected void generate() throws IOException {
+    if (bytes == null) {
+      if (!pngEncode()) {
+        out.cancel();
+        return;
+      }
+      bytes = getBytes();
+    } else {
+      dataLen = bytes.length;
+    }
+    int len = dataLen;
+    if (appData != null) {
+      setJmolTypeText(appPrefix, bytes, len, appData.length,
+          type);
+      out.write(bytes, 0, len);
+      len = (bytes = appData).length;
+    }
+    out.write(bytes, 0, len);
+  }
+
+
+  /**
+   * Creates an array of bytes that is the PNG equivalent of the current image,
+   * specifying whether to encode alpha or not.
+   * 
+   * @return        true if successful
+   * 
+   */
+  private boolean pngEncode() {
+
+    byte[] pngIdBytes = { -119, 80, 78, 71, 13, 10, 26, 10 };
+
+    writeBytes(pngIdBytes);
+    //hdrPos = bytePos;
+    writeHeader();
+    writeText(getApplicationText(appPrefix, type, 0, 0));
+
+    writeText("Software\0Jmol " + comment);
+    writeText("Creation Time\0" + date);
+
+    if (!encodeAlpha && transparentColor != null)
+      writeTransparentColor(transparentColor.intValue());
+    //dataPos = bytePos;
+    return writeImageData();
+  }
+
+  /**
+   * Fill in the Jmol type text area with number of bytes of PNG data and number
+   * of bytes of Jmol state data and fix checksum.
+   * 
+   * If we do not do this, then the checksum will be wrong, and Jmol and some
+   * other programs may not be able to read the PNG image.
+   * 
+   * This was corrected for Jmol 12.3.30. Between 12.3.7 and 12.3.29, PNG files
+   * created by Jmol have incorrect checksums.
+   * 
+   * @param prefix 
+   * 
+   * @param b
+   * @param nPNG
+   * @param nState
+   * @param type
+   */
+  private static void setJmolTypeText(String prefix, byte[] b, int nPNG, int nState, String type) {
+    String s = "tEXt" + getApplicationText(prefix, type, nPNG, nState);
+    CRCEncoder encoder = new PngEncoder();
+    byte[] test = s.substring(0, 4 + prefix.length()).getBytes();
+    for (int i = test.length; -- i >= 0;) 
+      if (b[i + PT_FIRST_TAG] != test[i]) {
+        System.out.println("image is not of the right form; appending data, but not adding tEXt tag.");
+        return;
+      }
+    encoder.setData(b, PT_FIRST_TAG);
+    encoder.writeString(s);
+    encoder.writeCRC();
+  }
+
+  private static String getApplicationText(String prefix, String type, int nPNG, int nState) {
+    String sPNG = "000000000" + nPNG;
+    sPNG = sPNG.substring(sPNG.length() - 9);
+    String sState = "000000000" + nState;
+    sState = sState.substring(sState.length() - 9);
+    return prefix + "\0" + type + (type.equals("PNG") ? "0" : "") + sPNG + "+"
+        + sState;
+  }
+
+  //  /**
+  //   * Set the filter to use
+  //   *
+  //   * @param whichFilter from constant list
+  //   */
+  //  public void setFilter(int whichFilter) {
+  //    this.filter = (whichFilter <= FILTER_LAST ? whichFilter : FILTER_NONE);
+  //  }
+
+  //  /**
+  //   * Retrieve filtering scheme
+  //   *
+  //   * @return int (see constant list)
+  //   */
+  //  public int getFilter() {
+  //    return filter;
+  //  }
+
+  //  /**
+  //   * Set the compression level to use
+  //   *
+  //   * @param level 0 through 9
+  //   */
+  //  public void setCompressionLevel(int level) {
+  //    if ((level >= 0) && (level <= 9)) {
+  //      this.compressionLevel = level;
+  //    }
+  //  }
+
+  //  /**
+  //   * Retrieve compression level
+  //   *
+  //   * @return int in range 0-9
+  //   */
+  //  public int getCompressionLevel() {
+  //    return compressionLevel;
+  //  }
+
+  /**
+   * Write a PNG "IHDR" chunk into the pngBytes array.
+   */
+  private void writeHeader() {
+
+    writeInt4(13);
+    startPos = bytePos;
+    writeString("IHDR");
+    writeInt4(width);
+    writeInt4(height);
+    writeByte(8); // bit depth
+    writeByte(encodeAlpha ? 6 : 2); // color type or direct model
+    writeByte(0); // compression method
+    writeByte(0); // filter method
+    writeByte(0); // no interlace
+    writeCRC();
+  }
+
+  private void writeText(String msg) {
+    writeInt4(msg.length());
+    startPos = bytePos;
+    writeString("tEXt" + msg);
+    writeCRC();
+  }
+
+  /**
+   * Write a PNG "tRNS" chunk into the pngBytes array.
+   * 
+   * @param icolor
+   */
+  private void writeTransparentColor(int icolor) {
+
+    writeInt4(6);
+    startPos = bytePos;
+    writeString("tRNS");
+    writeInt2((icolor >> 16) & 0xFF);
+    writeInt2((icolor >> 8) & 0xFF);
+    writeInt2(icolor & 0xFF);
+    writeCRC();
+  }
+
+  private byte[] scanLines; // the scan lines to be compressed
+  private int byteWidth; // width * bytesPerPixel
+
+  //private int hdrPos, dataPos, endPos;
+  //private byte[] priorRow;
+  //private byte[] leftBytes;
+
+
+  /**
+   * Write the image data into the pngBytes array. This will write one or more
+   * PNG "IDAT" chunks. In order to conserve memory, this method grabs as many
+   * rows as will fit into 32K bytes, or the whole image; whichever is less.
+   * 
+   * 
+   * @return true if no errors; false if error grabbing pixels
+   */
+  private boolean writeImageData() {
+
+    bytesPerPixel = (encodeAlpha ? 4 : 3);
+    byteWidth = width * bytesPerPixel;
+
+    int scanWidth = byteWidth + 1; // the added 1 is for the filter byte
+
+    //boolean doFilter = (filter != FILTER_NONE);
+
+    int rowsLeft = height; // number of rows remaining to write
+    //int startRow = 0; // starting row to process this time through
+    int nRows; // how many rows to grab at a time
+
+    int scanPos; // where we are in the scan lines
+
+    Deflater deflater = new Deflater(compressionLevel);
+    ByteArrayOutputStream outBytes = new ByteArrayOutputStream(1024);
+
+    DeflaterOutputStream compBytes = new DeflaterOutputStream(outBytes,
+        deflater);
+
+    int pt = 0; // overall image byte pointer
+    
+    // Jmol note: The entire image has been stored in pixels[] already
+    
+    try {
+      while (rowsLeft > 0) {
+        nRows = Math.max(1, Math.min(32767 / scanWidth, rowsLeft));
+        scanLines = new byte[scanWidth * nRows];
+        //        if (doFilter)
+        //          switch (filter) {
+        //          case FILTER_SUB:
+        //            leftBytes = new byte[16];
+        //            break;
+        //          case FILTER_UP:
+        //            priorRow = new byte[scanWidth - 1];
+        //            break;
+        //          }
+        int nPixels = width * nRows;
+        scanPos = 0;
+        //startPos = 1;
+        for (int i = 0; i < nPixels; i++, pt++) {
+          if (i % width == 0) {
+            scanLines[scanPos++] = (byte) filter;
+            //startPos = scanPos;
+          }
+          scanLines[scanPos++] = (byte) ((pixels[pt] >> 16) & 0xff);
+          scanLines[scanPos++] = (byte) ((pixels[pt] >> 8) & 0xff);
+          scanLines[scanPos++] = (byte) ((pixels[pt]) & 0xff);
+          if (encodeAlpha) {
+            scanLines[scanPos++] = (byte) ((pixels[pt] >> 24) & 0xff);
+          }
+          //          if (doFilter && i % width == width - 1) {
+          //            switch (filter) {
+          //            case FILTER_SUB:
+          //              filterSub();
+          //              break;
+          //            case FILTER_UP:
+          //              filterUp();
+          //              break;
+          //            }
+          //          }
+        }
+
+        /*
+         * Write these lines to the output area
+         */
+        compBytes.write(scanLines, 0, scanPos);
+
+        //startRow += nRows;
+        rowsLeft -= nRows;
+      }
+      compBytes.close();
+
+      /*
+       * Write the compressed bytes
+       */
+      byte[] compressedLines = outBytes.toByteArray();
+      writeInt4(compressedLines.length);
+      startPos = bytePos;
+      writeString("IDAT");
+      writeBytes(compressedLines);
+      writeCRC();
+      writeEnd();
+      deflater.finish();
+      return true;
+    } catch (IOException e) {
+      System.err.println(e.toString());
+      return false;
+    }
+  }
+
+  /**
+   * Write a PNG "IEND" chunk into the pngBytes array.
+   */
+  private void writeEnd() {
+    writeInt4(0);
+    startPos = bytePos;
+    writeString("IEND");
+    writeCRC();
+  }
+
+  ///**
+  //* Perform "sub" filtering on the given row.
+  //* Uses temporary array leftBytes to store the original values
+  //* of the previous pixels.  The array is 16 bytes long, which
+  //* will easily hold two-byte samples plus two-byte alpha.
+  //*
+  //*/
+  //private void filterSub() {
+  // int offset = bytesPerPixel;
+  // int actualStart = startPos + offset;
+  // int leftInsert = offset;
+  // int leftExtract = 0;
+  // //byte current_byte;
+  //
+  // for (int i = actualStart; i < startPos + byteWidth; i++) {
+  //   leftBytes[leftInsert] = scanLines[i];
+  //   scanLines[i] = (byte) ((scanLines[i] - leftBytes[leftExtract]) % 256);
+  //   leftInsert = (leftInsert + 1) % 0x0f;
+  //   leftExtract = (leftExtract + 1) % 0x0f;
+  // }
+  //}
+  //
+  ///**
+  //* Perform "up" filtering on the given row. Side effect: refills the prior row
+  //* with current row
+  //* 
+  //*/
+  //private void filterUp() {
+  // int nBytes = width * bytesPerPixel;
+  // for (int i = 0; i < nBytes; i++) {
+  //   int pt = startPos + i;
+  //   byte b = scanLines[pt];
+  //   scanLines[pt] = (byte) ((scanLines[pt] - priorRow[i]) % 256);
+  //   priorRow[i] = b;
+  // }
+  //}
+
+}