From 40830196070013432bc5f453eb31cfe4c64e0510 Mon Sep 17 00:00:00 2001
From: Sven Gothel <sgothel@jausoft.com>
Date: Sat, 7 Apr 2012 15:28:37 +0200
Subject: Merge PNGJ 0.85 into namespace

PNGJ Version 0.85  (1 April 2012)

Apache 2.0 License

http://code.google.com/p/pngj/

Merged code:
  - Changed namespace ar.com.hjg.pngj -> jogamp.opengl.util.pngj
    to avoid collision when using a different version of PNGJ.

  - Removed test and lossy packages and helper classes
    to reduce footprint.

License information is added in main LICENSE.txt file.
---
 .../jogamp/opengl/util/pngj/FilterType.java        |  94 +++++
 .../opengl/util/pngj/FilterWriteStrategy.java      |  97 +++++
 .../classes/jogamp/opengl/util/pngj/ImageInfo.java | 208 ++++++++++
 .../classes/jogamp/opengl/util/pngj/ImageLine.java | 175 ++++++++
 .../classes/jogamp/opengl/util/pngj/PngHelper.java | 213 ++++++++++
 .../opengl/util/pngj/PngIDatChunkInputStream.java  | 153 +++++++
 .../opengl/util/pngj/PngIDatChunkOutputStream.java |  31 ++
 .../classes/jogamp/opengl/util/pngj/PngReader.java | 415 ++++++++++++++++++
 .../classes/jogamp/opengl/util/pngj/PngWriter.java | 462 +++++++++++++++++++++
 .../opengl/util/pngj/PngjBadCrcException.java      |  20 +
 .../jogamp/opengl/util/pngj/PngjException.java     |  23 +
 .../opengl/util/pngj/PngjInputException.java       |  20 +
 .../opengl/util/pngj/PngjOutputException.java      |  20 +
 .../opengl/util/pngj/PngjUnsupportedException.java |  24 ++
 .../opengl/util/pngj/ProgressiveOutputStream.java  |  71 ++++
 .../util/pngj/chunks/ChunkCopyBehaviour.java       |  24 ++
 .../opengl/util/pngj/chunks/ChunkHelper.java       | 134 ++++++
 .../jogamp/opengl/util/pngj/chunks/ChunkList.java  | 282 +++++++++++++
 .../util/pngj/chunks/ChunkLoadBehaviour.java       |  10 +
 .../jogamp/opengl/util/pngj/chunks/ChunkRaw.java   |  83 ++++
 .../jogamp/opengl/util/pngj/chunks/PngChunk.java   | 152 +++++++
 .../opengl/util/pngj/chunks/PngChunkBKGD.java      | 122 ++++++
 .../opengl/util/pngj/chunks/PngChunkCHRM.java      |  88 ++++
 .../opengl/util/pngj/chunks/PngChunkGAMA.java      |  56 +++
 .../opengl/util/pngj/chunks/PngChunkHIST.java      |  67 +++
 .../opengl/util/pngj/chunks/PngChunkICCP.java      |  85 ++++
 .../opengl/util/pngj/chunks/PngChunkIDAT.java      |  25 ++
 .../opengl/util/pngj/chunks/PngChunkIEND.java      |  26 ++
 .../opengl/util/pngj/chunks/PngChunkIHDR.java      | 126 ++++++
 .../opengl/util/pngj/chunks/PngChunkITXT.java      | 119 ++++++
 .../opengl/util/pngj/chunks/PngChunkPHYS.java      | 108 +++++
 .../opengl/util/pngj/chunks/PngChunkPLTE.java      |  93 +++++
 .../opengl/util/pngj/chunks/PngChunkSBIT.java      | 124 ++++++
 .../opengl/util/pngj/chunks/PngChunkSPLT.java      | 139 +++++++
 .../opengl/util/pngj/chunks/PngChunkSRGB.java      |  61 +++
 .../opengl/util/pngj/chunks/PngChunkTEXT.java      |  34 ++
 .../opengl/util/pngj/chunks/PngChunkTIME.java      |  83 ++++
 .../opengl/util/pngj/chunks/PngChunkTRNS.java      | 129 ++++++
 .../opengl/util/pngj/chunks/PngChunkTextVar.java   |  61 +++
 .../opengl/util/pngj/chunks/PngChunkUNKNOWN.java   |  51 +++
 .../opengl/util/pngj/chunks/PngChunkZTXT.java      |  62 +++
 .../opengl/util/pngj/chunks/PngMetadata.java       | 135 ++++++
 .../jogamp/opengl/util/pngj/chunks/package.html    |   9 +
 .../classes/jogamp/opengl/util/pngj/package.html   |  11 +
 44 files changed, 4525 insertions(+)
 create mode 100644 src/jogl/classes/jogamp/opengl/util/pngj/FilterType.java
 create mode 100644 src/jogl/classes/jogamp/opengl/util/pngj/FilterWriteStrategy.java
 create mode 100644 src/jogl/classes/jogamp/opengl/util/pngj/ImageInfo.java
 create mode 100644 src/jogl/classes/jogamp/opengl/util/pngj/ImageLine.java
 create mode 100644 src/jogl/classes/jogamp/opengl/util/pngj/PngHelper.java
 create mode 100644 src/jogl/classes/jogamp/opengl/util/pngj/PngIDatChunkInputStream.java
 create mode 100644 src/jogl/classes/jogamp/opengl/util/pngj/PngIDatChunkOutputStream.java
 create mode 100644 src/jogl/classes/jogamp/opengl/util/pngj/PngReader.java
 create mode 100644 src/jogl/classes/jogamp/opengl/util/pngj/PngWriter.java
 create mode 100644 src/jogl/classes/jogamp/opengl/util/pngj/PngjBadCrcException.java
 create mode 100644 src/jogl/classes/jogamp/opengl/util/pngj/PngjException.java
 create mode 100644 src/jogl/classes/jogamp/opengl/util/pngj/PngjInputException.java
 create mode 100644 src/jogl/classes/jogamp/opengl/util/pngj/PngjOutputException.java
 create mode 100644 src/jogl/classes/jogamp/opengl/util/pngj/PngjUnsupportedException.java
 create mode 100644 src/jogl/classes/jogamp/opengl/util/pngj/ProgressiveOutputStream.java
 create mode 100644 src/jogl/classes/jogamp/opengl/util/pngj/chunks/ChunkCopyBehaviour.java
 create mode 100644 src/jogl/classes/jogamp/opengl/util/pngj/chunks/ChunkHelper.java
 create mode 100644 src/jogl/classes/jogamp/opengl/util/pngj/chunks/ChunkList.java
 create mode 100644 src/jogl/classes/jogamp/opengl/util/pngj/chunks/ChunkLoadBehaviour.java
 create mode 100644 src/jogl/classes/jogamp/opengl/util/pngj/chunks/ChunkRaw.java
 create mode 100644 src/jogl/classes/jogamp/opengl/util/pngj/chunks/PngChunk.java
 create mode 100644 src/jogl/classes/jogamp/opengl/util/pngj/chunks/PngChunkBKGD.java
 create mode 100644 src/jogl/classes/jogamp/opengl/util/pngj/chunks/PngChunkCHRM.java
 create mode 100644 src/jogl/classes/jogamp/opengl/util/pngj/chunks/PngChunkGAMA.java
 create mode 100644 src/jogl/classes/jogamp/opengl/util/pngj/chunks/PngChunkHIST.java
 create mode 100644 src/jogl/classes/jogamp/opengl/util/pngj/chunks/PngChunkICCP.java
 create mode 100644 src/jogl/classes/jogamp/opengl/util/pngj/chunks/PngChunkIDAT.java
 create mode 100644 src/jogl/classes/jogamp/opengl/util/pngj/chunks/PngChunkIEND.java
 create mode 100644 src/jogl/classes/jogamp/opengl/util/pngj/chunks/PngChunkIHDR.java
 create mode 100644 src/jogl/classes/jogamp/opengl/util/pngj/chunks/PngChunkITXT.java
 create mode 100644 src/jogl/classes/jogamp/opengl/util/pngj/chunks/PngChunkPHYS.java
 create mode 100644 src/jogl/classes/jogamp/opengl/util/pngj/chunks/PngChunkPLTE.java
 create mode 100644 src/jogl/classes/jogamp/opengl/util/pngj/chunks/PngChunkSBIT.java
 create mode 100644 src/jogl/classes/jogamp/opengl/util/pngj/chunks/PngChunkSPLT.java
 create mode 100644 src/jogl/classes/jogamp/opengl/util/pngj/chunks/PngChunkSRGB.java
 create mode 100644 src/jogl/classes/jogamp/opengl/util/pngj/chunks/PngChunkTEXT.java
 create mode 100644 src/jogl/classes/jogamp/opengl/util/pngj/chunks/PngChunkTIME.java
 create mode 100644 src/jogl/classes/jogamp/opengl/util/pngj/chunks/PngChunkTRNS.java
 create mode 100644 src/jogl/classes/jogamp/opengl/util/pngj/chunks/PngChunkTextVar.java
 create mode 100644 src/jogl/classes/jogamp/opengl/util/pngj/chunks/PngChunkUNKNOWN.java
 create mode 100644 src/jogl/classes/jogamp/opengl/util/pngj/chunks/PngChunkZTXT.java
 create mode 100644 src/jogl/classes/jogamp/opengl/util/pngj/chunks/PngMetadata.java
 create mode 100644 src/jogl/classes/jogamp/opengl/util/pngj/chunks/package.html
 create mode 100644 src/jogl/classes/jogamp/opengl/util/pngj/package.html

(limited to 'src/jogl/classes/jogamp/opengl/util')

diff --git a/src/jogl/classes/jogamp/opengl/util/pngj/FilterType.java b/src/jogl/classes/jogamp/opengl/util/pngj/FilterType.java
new file mode 100644
index 000000000..a34f73ab2
--- /dev/null
+++ b/src/jogl/classes/jogamp/opengl/util/pngj/FilterType.java
@@ -0,0 +1,94 @@
+package jogamp.opengl.util.pngj;
+
+/**
+ * Internal PNG predictor filter, or strategy to select it.
+ * 
+ */
+public enum FilterType {
+	/**
+	 * No filter.
+	 */
+	FILTER_NONE(0),
+	/**
+	 * SUB filter (uses same row)
+	 */
+	FILTER_SUB(1),
+	/**
+	 * UP filter (uses previous row)
+	 */
+	FILTER_UP(2),
+	/**
+	 * AVERAGE filter
+	 */
+	FILTER_AVERAGE(3),
+	/**
+	 * PAETH predictor
+	 */
+	FILTER_PAETH(4),
+	/**
+	 * Default strategy: select one of the above filters depending on global image parameters
+	 */
+	FILTER_DEFAULT(-1),
+	/**
+	 * Aggresive strategy: select one of the above filters trying each of the filters (this is done every 8 rows)
+	 */
+	FILTER_AGGRESSIVE(-2),
+	/**
+	 * Uses all fiters, one for lines, cyciclally. Only for tests.
+	 */
+	FILTER_ALTERNATE(-3),
+	/**
+	 * Aggresive strategy: select one of the above filters trying each of the filters (this is done for every row!)
+	 */
+	FILTER_VERYAGGRESSIVE(-4), ;
+	public final int val;
+
+	private FilterType(int val) {
+		this.val = val;
+	}
+
+	public static FilterType getByVal(int i) {
+		for (FilterType ft : values()) {
+			if (ft.val == i)
+				return ft;
+		}
+		return null;
+	}
+
+	public static int unfilterRowNone(int r) {
+		return (int) (r & 0xFF);
+	}
+
+	public static int unfilterRowSub(int r, int left) {
+		return ((int) (r + left) & 0xFF);
+	}
+
+	public static int unfilterRowUp(int r, int up) {
+		return ((int) (r + up) & 0xFF);
+	}
+
+	public static int unfilterRowAverage(int r, int left, int up) {
+		return (r + (left + up) / 2) & 0xFF;
+	}
+
+	public static int unfilterRowPaeth(int r, int a, int b, int c) { // a = left, b = above, c = upper left
+		return (r + filterPaethPredictor(a, b, c)) & 0xFF;
+	}
+
+	public static int filterPaethPredictor(int a, int b, int c) {
+		// from http://www.libpng.org/pub/png/spec/1.2/PNG-Filters.html
+		// a = left, b = above, c = upper left
+		final int p = a + b - c;// ; initial estimate
+		final int pa = p >= a ? p - a : a - p;
+		final int pb = p >= b ? p - b : b - p;
+		final int pc = p >= c ? p - c : c - p;
+		// ; return nearest of a,b,c,
+		// ; breaking ties in order a,b,c.
+		if (pa <= pb && pa <= pc)
+			return a;
+		else if (pb <= pc)
+			return b;
+		else
+			return c;
+	}
+}
diff --git a/src/jogl/classes/jogamp/opengl/util/pngj/FilterWriteStrategy.java b/src/jogl/classes/jogamp/opengl/util/pngj/FilterWriteStrategy.java
new file mode 100644
index 000000000..27586b292
--- /dev/null
+++ b/src/jogl/classes/jogamp/opengl/util/pngj/FilterWriteStrategy.java
@@ -0,0 +1,97 @@
+package jogamp.opengl.util.pngj;
+
+/**
+ * Manages the writer strategy for selecting the internal png "filter"
+ */
+class FilterWriteStrategy {
+	private static final int COMPUTE_STATS_EVERY_N_LINES = 8;
+
+	final ImageInfo imgInfo;
+	public final FilterType configuredType; // can be negative (fin dout)
+	private FilterType currentType; // 0-4
+	private int lastRowTested = -1000000;
+	// performance of each filter (less is better) (can be negative)
+	private double[] lastSums = new double[5];
+	// performance of each filter (less is better) (can be negative)
+	private double[] lastEntropies = new double[5];
+	// a priori preference (NONE SUB UP AVERAGE PAETH)
+	private double[] preference = new double[] { 1.1, 1.1, 1.1, 1.1, 1.2 };
+	private int discoverEachLines = -1;
+	private double[] histogram1 = new double[256];
+
+	FilterWriteStrategy(ImageInfo imgInfo, FilterType configuredType) {
+		this.imgInfo = imgInfo;
+		this.configuredType = configuredType;
+		if (configuredType.val < 0) { // first guess
+			if ((imgInfo.rows < 8 && imgInfo.cols < 8) || imgInfo.indexed || imgInfo.bitDepth < 8)
+				currentType = FilterType.FILTER_NONE;
+			else
+				currentType = FilterType.FILTER_PAETH;
+		} else {
+			currentType = configuredType;
+		}
+		if (configuredType == FilterType.FILTER_AGGRESSIVE)
+			discoverEachLines = COMPUTE_STATS_EVERY_N_LINES;
+		if (configuredType == FilterType.FILTER_VERYAGGRESSIVE)
+			discoverEachLines = 1;
+	}
+
+	boolean shouldTestAll(int rown) {
+		if (discoverEachLines > 0 && lastRowTested + discoverEachLines <= rown) {
+			currentType = null;
+			return true;
+		} else
+			return false;
+	}
+
+	public void setPreference(double none, double sub, double up, double ave, double paeth) {
+		preference = new double[] { none, sub, up, ave, paeth };
+	}
+
+	public boolean computesStatistics() {
+		return (discoverEachLines > 0);
+	}
+
+	void fillResultsForFilter(int rown, FilterType type, double sum, int[] histo, boolean tentative) {
+		lastRowTested = rown;
+		lastSums[type.val] = sum;
+		if (histo != null) {
+			double v, alfa, beta, e;
+			alfa = rown == 0 ? 0.0 : 0.3;
+			beta = 1 - alfa;
+			e = 0.0;
+			for (int i = 0; i < 256; i++) {
+				v = ((double) histo[i]) / imgInfo.cols;
+				v = histogram1[i] * alfa + v * beta;
+				if (tentative)
+					e += v > 0.00000001 ? v * Math.log(v) : 0.0;
+				else
+					histogram1[i] = v;
+			}
+			lastEntropies[type.val] = (-e);
+		}
+	}
+
+	FilterType gimmeFilterType(int rown, boolean useEntropy) {
+		if (currentType == null) { // get better
+			if (rown == 0)
+				currentType = FilterType.FILTER_SUB;
+			else {
+				double bestval = Double.MAX_VALUE;
+				double val;
+				for (int i = 0; i < 5; i++) {
+					val = useEntropy ? lastEntropies[i] : lastSums[i];
+					val /= preference[i];
+					if (val <= bestval) {
+						bestval = val;
+						currentType = FilterType.getByVal(i);
+					}
+				}
+			}
+		}
+		if (configuredType == FilterType.FILTER_ALTERNATE) {
+			currentType = FilterType.getByVal((currentType.val + 1) % 5);
+		}
+		return currentType;
+	}
+}
diff --git a/src/jogl/classes/jogamp/opengl/util/pngj/ImageInfo.java b/src/jogl/classes/jogamp/opengl/util/pngj/ImageInfo.java
new file mode 100644
index 000000000..2f6b89e9c
--- /dev/null
+++ b/src/jogl/classes/jogamp/opengl/util/pngj/ImageInfo.java
@@ -0,0 +1,208 @@
+package jogamp.opengl.util.pngj;
+
+/**
+ * Simple immutable wrapper for basic image info.
+ * <p>
+ * Some parameters are redundant, but the constructor receives an 'orthogonal' subset.
+ * <p>
+ * ref: http://www.w3.org/TR/PNG/#11IHDR
+ */
+public class ImageInfo {
+
+	// very big value ; actually we are ok with 2**22=4M, perhaps even more
+	private static final int MAX_COLS_ROWS_VAL = 1000000;
+
+	/**
+	 * Image width, in pixels.
+	 */
+	public final int cols;
+
+	/**
+	 * Image height, in pixels
+	 */
+	public final int rows;
+
+	/**
+	 * Bits per sample (per channel) in the buffer (1-2-4-8-16). This is 8-16 for RGB/ARGB images, 1-2-4-8 for
+	 * grayscale. For indexed images, number of bits per palette index (1-2-4-8)
+	 */
+	public final int bitDepth;
+
+	/**
+	 * Number of channels, as used internally. This is 3 for RGB, 4 for RGBA, 2 for GA (gray with alpha), 1 for
+	 * grayscales or indexed.
+	 */
+	public final int channels;
+
+	/**
+	 * Flag: true if has alpha channel (RGBA/GA)
+	 */
+	public final boolean alpha;
+
+	/**
+	 * Flag: true if is grayscale (G/GA)
+	 */
+	public final boolean greyscale;
+
+	/**
+	 * Flag: true if image is indexed, i.e., it has a palette
+	 */
+	public final boolean indexed;
+
+	/**
+	 * Flag: true if image internally uses less than one byte per sample (bit depth 1-2-4)
+	 */
+	public final boolean packed;
+
+	/**
+	 * Bits used for each pixel in the buffer: channel * bitDepth
+	 */
+	public final int bitspPixel;
+
+	/**
+	 * rounded up value: this is only used internally for filter
+	 */
+	public final int bytesPixel;
+
+	/**
+	 * ceil(bitspp*cols/8)
+	 */
+	public final int bytesPerRow;
+
+	/**
+	 * Equals cols * channels
+	 */
+	public final int samplesPerRow;
+
+	/**
+	 * For internal use only. Samples available for our packed scanline. Equals samplesPerRow if not packed. Elsewhere,
+	 * it's lower
+	 */
+	final int samplesPerRowP;
+
+	/**
+	 * Short constructor: assumes truecolor (RGB/RGBA)
+	 */
+	public ImageInfo(int cols, int rows, int bitdepth, boolean alpha) {
+		this(cols, rows, bitdepth, alpha, false, false);
+	}
+
+	/**
+	 * Full constructor
+	 * 
+	 * @param cols
+	 *            Width in pixels
+	 * @param rows
+	 *            Height in pixels
+	 * @param bitdepth
+	 *            Bits per sample, in the buffer : 8-16 for RGB true color and greyscale
+	 * @param alpha
+	 *            Flag: has an alpha channel (RGBA or GA)
+	 * @param grayscale
+	 *            Flag: is gray scale (any bitdepth, with or without alpha)
+	 * @param indexed
+	 *            Flag: has palette
+	 */
+	public ImageInfo(int cols, int rows, int bitdepth, boolean alpha, boolean grayscale, boolean indexed) {
+		this.cols = cols;
+		this.rows = rows;
+		this.alpha = alpha;
+		this.indexed = indexed;
+		this.greyscale = grayscale;
+		if (greyscale && indexed)
+			throw new PngjException("palette and greyscale are mutually exclusive");
+		this.channels = (grayscale || indexed) ? (alpha ? 2 : 1) : (alpha ? 4 : 3);
+		// http://www.w3.org/TR/PNG/#11IHDR
+		this.bitDepth = bitdepth;
+		this.packed = bitdepth < 8;
+		this.bitspPixel = (channels * this.bitDepth);
+		this.bytesPixel = (bitspPixel + 7) / 8;
+		this.bytesPerRow = (bitspPixel * cols + 7) / 8;
+		this.samplesPerRow = channels * this.cols;
+		this.samplesPerRowP = packed ? bytesPerRow : samplesPerRow;
+		// several checks
+		switch (this.bitDepth) {
+		case 1:
+		case 2:
+		case 4:
+			if (!(this.indexed || this.greyscale))
+				throw new PngjException("only indexed or grayscale can have bitdepth=" + this.bitDepth);
+			break;
+		case 8:
+			break;
+		case 16:
+			if (this.indexed)
+				throw new PngjException("indexed can't have bitdepth=" + this.bitDepth);
+			break;
+		default:
+			throw new PngjException("invalid bitdepth=" + this.bitDepth);
+		}
+		if (cols < 1 || cols > MAX_COLS_ROWS_VAL)
+			throw new PngjException("invalid cols=" + cols + " ???");
+		if (rows < 1 || rows > MAX_COLS_ROWS_VAL)
+			throw new PngjException("invalid rows=" + rows + " ???");
+	}
+
+	@Override
+	public String toString() {
+		return "ImageInfo [cols=" + cols + ", rows=" + rows + ", bitDepth=" + bitDepth + ", channels=" + channels
+				+ ", bitspPixel=" + bitspPixel + ", bytesPixel=" + bytesPixel + ", bytesPerRow=" + bytesPerRow
+				+ ", samplesPerRow=" + samplesPerRow + ", samplesPerRowP=" + samplesPerRowP + ", alpha=" + alpha
+				+ ", greyscale=" + greyscale + ", indexed=" + indexed + ", packed=" + packed + "]";
+	}
+
+	@Override
+	public int hashCode() {
+		final int prime = 31;
+		int result = 1;
+		result = prime * result + (alpha ? 1231 : 1237);
+		result = prime * result + bitDepth;
+		result = prime * result + bitspPixel;
+		result = prime * result + bytesPerRow;
+		result = prime * result + bytesPixel;
+		result = prime * result + channels;
+		result = prime * result + cols;
+		result = prime * result + (greyscale ? 1231 : 1237);
+		result = prime * result + (indexed ? 1231 : 1237);
+		result = prime * result + (packed ? 1231 : 1237);
+		result = prime * result + rows;
+		result = prime * result + samplesPerRow;
+		return result;
+	}
+
+	@Override
+	public boolean equals(Object obj) {
+		if (this == obj)
+			return true;
+		if (obj == null)
+			return false;
+		if (getClass() != obj.getClass())
+			return false;
+		ImageInfo other = (ImageInfo) obj;
+		if (alpha != other.alpha)
+			return false;
+		if (bitDepth != other.bitDepth)
+			return false;
+		if (bitspPixel != other.bitspPixel)
+			return false;
+		if (bytesPerRow != other.bytesPerRow)
+			return false;
+		if (bytesPixel != other.bytesPixel)
+			return false;
+		if (channels != other.channels)
+			return false;
+		if (cols != other.cols)
+			return false;
+		if (greyscale != other.greyscale)
+			return false;
+		if (indexed != other.indexed)
+			return false;
+		if (packed != other.packed)
+			return false;
+		if (rows != other.rows)
+			return false;
+		if (samplesPerRow != other.samplesPerRow)
+			return false;
+		return true;
+	}
+}
diff --git a/src/jogl/classes/jogamp/opengl/util/pngj/ImageLine.java b/src/jogl/classes/jogamp/opengl/util/pngj/ImageLine.java
new file mode 100644
index 000000000..bfbb35b7c
--- /dev/null
+++ b/src/jogl/classes/jogamp/opengl/util/pngj/ImageLine.java
@@ -0,0 +1,175 @@
+package jogamp.opengl.util.pngj;
+
+import java.util.Arrays;
+
+/**
+ * Lightweight wrapper for an image scanline, used for read and write.
+ * <p>
+ * This object can be (usually it is) reused while iterating over the image lines.
+ * <p>
+ * See <code>scanline</code> field, to understand the format.
+ */
+public class ImageLine {
+	public final ImageInfo imgInfo;
+
+	/**
+	 * tracks the current row number (from 0 to rows-1)
+	 */
+	private int rown = 0;
+
+	/**
+	 * The 'scanline' is an array of integers, corresponds to an image line (row).
+	 * <p>
+	 * Except for 'packed' formats (gray/indexed with 1-2-4 bitdepth) each int is a "sample" (one for channel), (0-255
+	 * or 0-65535) in the respective PNG sequence sequence : (R G B R G B...) or (R G B A R G B A...) or (g g g ...) or
+	 * ( i i i) (palette index)
+	 * <p>
+	 * For bitdepth 1/2/4 , each element is a PACKED byte! To get an unpacked copy, see <code>tf_pack()</code> and its
+	 * inverse <code>tf_unpack()</code>
+	 * <p>
+	 * To convert a indexed line to RGB balues, see <code>ImageLineHelper.tf_palIdx2RGB()</code> (can't do the reverse)
+	 */
+	public final int[] scanline; // see explanation above!!
+
+	protected FilterType filterUsed; // informational ; only filled by the reader
+	public final int channels; // copied from imgInfo, more handy
+	public final int bitDepth; // copied from imgInfo, more handy
+
+	public ImageLine(ImageInfo imgInfo) {
+		this.imgInfo = imgInfo;
+		channels = imgInfo.channels;
+		scanline = new int[imgInfo.samplesPerRowP];
+		this.bitDepth = imgInfo.bitDepth;
+	}
+
+	/** This row number inside the image (0 is top) */
+	public int getRown() {
+		return rown;
+	}
+
+	/** Increments row number */
+	public void incRown() {
+		this.rown++;
+	}
+
+	/** Sets row number */
+	public void setRown(int n) {
+		this.rown = n;
+	}
+
+	/** Sets scanline, making copy from passed array */
+	public void setScanLine(int[] b) {
+		System.arraycopy(b, 0, scanline, 0, scanline.length);
+	}
+
+	/**
+	 * Returns a copy from scanline, in byte array.
+	 * 
+	 * You can (OPTIONALLY) pass an preallocated array to use.
+	 **/
+	public int[] getScanLineCopy(int[] b) {
+		if (b == null || b.length < scanline.length)
+			b = new int[scanline.length];
+		System.arraycopy(scanline, 0, b, 0, scanline.length);
+		return b;
+	}
+
+	/**
+	 * Unpacks scanline (for bitdepth 1-2-4) into buffer.
+	 * <p>
+	 * You can (OPTIONALLY) pass an preallocated array to use.
+	 * <p>
+	 * If scale==TRUE scales the value (just a bit shift).
+	 */
+	public int[] tf_unpack(int[] buf, boolean scale) {
+		int len = scanline.length;
+		if (bitDepth == 1)
+			len *= 8;
+		else if (bitDepth == 2)
+			len *= 4;
+		else if (bitDepth == 4)
+			len *= 2;
+		if (buf == null)
+			buf = new int[len];
+		if (bitDepth >= 8)
+			System.arraycopy(scanline, 0, buf, 0, scanline.length);
+		else {
+			int mask, offset, v;
+			int mask0 = getMaskForPackedFormats();
+			int offset0 = 8 - bitDepth;
+			mask = mask0;
+			offset = offset0;
+			for (int i = 0, j = 0; i < len; i++) {
+				v = (scanline[j] & mask) >> offset;
+				if (scale)
+					v <<= offset0;
+				buf[i] = v;
+				mask = mask >> bitDepth;
+				offset -= bitDepth;
+				if (mask == 0) { // new byte in source
+					mask = mask0;
+					offset = offset0;
+					j++;
+				}
+			}
+		}
+		return buf;
+	}
+
+	/**
+	 * Packs scanline (for bitdepth 1-2-4) from buffer.
+	 * <p>
+	 * If scale==TRUE scales the value (just a bit shift).
+	 */
+	public void tf_pack(int[] buf, boolean scale) { // writes scanline
+		int len = scanline.length;
+		if (bitDepth == 1)
+			len *= 8;
+		else if (bitDepth == 2)
+			len *= 4;
+		else if (bitDepth == 4)
+			len *= 2;
+		if (bitDepth >= 8)
+			System.arraycopy(buf, 0, scanline, 0, scanline.length);
+		else {
+			int offset0 = 8 - bitDepth;
+			int mask0 = getMaskForPackedFormats() >> offset0;
+			int offset, v;
+			offset = offset0;
+			Arrays.fill(scanline, 0);
+			for (int i = 0, j = 0; i < len; i++) {
+				v = buf[i];
+				if (scale)
+					v >>= offset0;
+				v = (v & mask0) << offset;
+				scanline[j] |= v;
+				offset -= bitDepth;
+				if (offset < 0) { // new byte in scanline
+					offset = offset0;
+					j++;
+				}
+			}
+		}
+	}
+
+	private int getMaskForPackedFormats() { // Utility function for pacj/unpack
+		if (bitDepth == 1)
+			return 0x80;
+		if (bitDepth == 2)
+			return 0xc0;
+		if (bitDepth == 4)
+			return 0xf0;
+		throw new RuntimeException("?");
+	}
+
+	public FilterType getFilterUsed() {
+		return filterUsed;
+	}
+
+	/**
+	 * Basic info
+	 */
+	public String toString() {
+		return "row=" + rown + " cols=" + imgInfo.cols + " bpc=" + imgInfo.bitDepth + " size=" + scanline.length;
+	}
+}
diff --git a/src/jogl/classes/jogamp/opengl/util/pngj/PngHelper.java b/src/jogl/classes/jogamp/opengl/util/pngj/PngHelper.java
new file mode 100644
index 000000000..1016b1b64
--- /dev/null
+++ b/src/jogl/classes/jogamp/opengl/util/pngj/PngHelper.java
@@ -0,0 +1,213 @@
+package jogamp.opengl.util.pngj;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.nio.charset.Charset;
+import java.util.HashSet;
+import java.util.Set;
+import java.util.zip.CRC32;
+
+/**
+ * Some utility static methods.
+ * <p>
+ * See also <code>FileHelper</code> (if not sandboxed).
+ * <p>
+ * Client code should rarely need these methods.
+ */
+public class PngHelper {
+	/**
+	 * Default charset, used internally by PNG for several things
+	 */
+	public static Charset charsetLatin1 = Charset.forName("ISO-8859-1");
+	public static Charset charsetUTF8 = Charset.forName("UTF-8"); // only for some chunks
+
+	static boolean DEBUG = false;
+
+	public static int readByte(InputStream is) {
+		try {
+			return is.read();
+		} catch (IOException e) {
+			throw new PngjOutputException(e);
+		}
+	}
+
+	/**
+	 * -1 if eof
+	 * 
+	 * PNG uses "network byte order"
+	 */
+	public static int readInt2(InputStream is) {
+		try {
+			int b1 = is.read();
+			int b2 = is.read();
+			if (b1 == -1 || b2 == -1)
+				return -1;
+			return (b1 << 8) + b2;
+		} catch (IOException e) {
+			throw new PngjInputException("error reading readInt2", e);
+		}
+	}
+
+	/**
+	 * -1 if eof
+	 */
+	public static int readInt4(InputStream is) {
+		try {
+			int b1 = is.read();
+			int b2 = is.read();
+			int b3 = is.read();
+			int b4 = is.read();
+			if (b1 == -1 || b2 == -1 || b3 == -1 || b4 == -1)
+				return -1;
+			return (b1 << 24) + (b2 << 16) + (b3 << 8) + b4;
+		} catch (IOException e) {
+			throw new PngjInputException("error reading readInt4", e);
+		}
+	}
+
+	public static int readInt1fromByte(byte[] b, int offset) {
+		return (b[offset] & 0xff);
+	}
+
+	public static int readInt2fromBytes(byte[] b, int offset) {
+		return ((b[offset] & 0xff) << 16) | ((b[offset + 1] & 0xff));
+	}
+
+	public static int readInt4fromBytes(byte[] b, int offset) {
+		return ((b[offset] & 0xff) << 24) | ((b[offset + 1] & 0xff) << 16) | ((b[offset + 2] & 0xff) << 8)
+				| (b[offset + 3] & 0xff);
+	}
+
+	public static void writeByte(OutputStream os, byte b) {
+		try {
+			os.write(b);
+		} catch (IOException e) {
+			throw new PngjOutputException(e);
+		}
+	}
+
+	public static void writeInt2(OutputStream os, int n) {
+		byte[] temp = { (byte) ((n >> 8) & 0xff), (byte) (n & 0xff) };
+		writeBytes(os, temp);
+	}
+
+	public static void writeInt4(OutputStream os, int n) {
+		byte[] temp = new byte[4];
+		writeInt4tobytes(n, temp, 0);
+		writeBytes(os, temp);
+	}
+
+	public static void writeInt2tobytes(int n, byte[] b, int offset) {
+		b[offset] = (byte) ((n >> 8) & 0xff);
+		b[offset + 1] = (byte) (n & 0xff);
+	}
+
+	public static void writeInt4tobytes(int n, byte[] b, int offset) {
+		b[offset] = (byte) ((n >> 24) & 0xff);
+		b[offset + 1] = (byte) ((n >> 16) & 0xff);
+		b[offset + 2] = (byte) ((n >> 8) & 0xff);
+		b[offset + 3] = (byte) (n & 0xff);
+	}
+
+	/**
+	 * guaranteed to read exactly len bytes. throws error if it cant
+	 */
+	public static void readBytes(InputStream is, byte[] b, int offset, int len) {
+		if (len == 0)
+			return;
+		try {
+			int read = 0;
+			while (read < len) {
+				int n = is.read(b, offset + read, len - read);
+				if (n < 1)
+					throw new RuntimeException("error reading bytes, " + n + " !=" + len);
+				read += n;
+			}
+		} catch (IOException e) {
+			throw new PngjInputException("error reading", e);
+		}
+	}
+
+	public static void writeBytes(OutputStream os, byte[] b) {
+		try {
+			os.write(b);
+		} catch (IOException e) {
+			throw new PngjOutputException(e);
+		}
+	}
+
+	public static void writeBytes(OutputStream os, byte[] b, int offset, int n) {
+		try {
+			os.write(b, offset, n);
+		} catch (IOException e) {
+			throw new PngjOutputException(e);
+		}
+	}
+
+	public static void logdebug(String msg) {
+		if (DEBUG)
+			System.out.println(msg);
+	}
+
+	public static Set<String> asSet(String... values) {
+		return new HashSet<String>(java.util.Arrays.asList(values));
+	}
+
+	public static Set<String> unionSets(Set<String> set1, Set<String> set2) {
+		Set<String> s = new HashSet<String>();
+		s.addAll(set1);
+		s.addAll(set2);
+		return s;
+	}
+
+	public static Set<String> unionSets(Set<String> set1, Set<String> set2, Set<String> set3) {
+		Set<String> s = new HashSet<String>();
+		s.addAll(set1);
+		s.addAll(set2);
+		s.addAll(set3);
+		return s;
+	}
+
+	private static final ThreadLocal<CRC32> crcProvider = new ThreadLocal<CRC32>() {
+		protected CRC32 initialValue() {
+			return new CRC32();
+		}
+	};
+
+	/** thread-singleton crc engine */
+	public static CRC32 getCRC() {
+		return crcProvider.get();
+	}
+
+	static final byte[] pngIdBytes = { -119, 80, 78, 71, 13, 10, 26, 10 }; // png magic
+
+	public static double resMetersToDpi(long res) {
+		return (double) res * 0.0254;
+	}
+
+	public static long resDpiToMeters(double dpi) {
+		return (long) (dpi / 0.0254 + 0.5);
+	}
+
+	public static int doubleToInt100000(double d) {
+		return (int) (d * 100000.0 + 0.5);
+	}
+
+	public static double intToDouble100000(int i) {
+		return i / 100000.0;
+	}
+
+	public static int clampTo_0_255(int i) {
+		return i > 255 ? 255 : (i < 0 ? 0 : i);
+	}
+
+	public static int clampTo_0_65535(int i) {
+		return i > 65535 ? 65535 : (i < 0 ? 0 : i);
+	}
+
+	public static int clampTo_128_127(int x) {
+		return x > 127 ? 127 : (x < -128 ? -128 : x);
+	}
+
+}
diff --git a/src/jogl/classes/jogamp/opengl/util/pngj/PngIDatChunkInputStream.java b/src/jogl/classes/jogamp/opengl/util/pngj/PngIDatChunkInputStream.java
new file mode 100644
index 000000000..66c4b49f0
--- /dev/null
+++ b/src/jogl/classes/jogamp/opengl/util/pngj/PngIDatChunkInputStream.java
@@ -0,0 +1,153 @@
+package jogamp.opengl.util.pngj;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+import java.util.zip.CRC32;
+
+import jogamp.opengl.util.pngj.chunks.ChunkHelper;
+
+
+/**
+ * Reads IDAT chunks
+ */
+class PngIDatChunkInputStream extends InputStream {
+	private final InputStream inputStream;
+	private final CRC32 crcEngine;
+	private int lenLastChunk;
+	private byte[] idLastChunk = new byte[4];
+	private int toReadThisChunk = 0;
+	private boolean ended = false;
+	private long offset; // offset inside inputstream
+
+	// just informational
+	static class IdatChunkInfo {
+		public final int len;
+		public final int offset;
+
+		private IdatChunkInfo(int len, int offset) {
+			this.len = len;
+			this.offset = offset;
+		}
+	}
+
+	List<IdatChunkInfo> foundChunksInfo = new ArrayList<IdatChunkInfo>();
+
+	/**
+	 * Constructor must be called just after reading length and id of first IDAT chunk
+	 **/
+	PngIDatChunkInputStream(InputStream iStream, int lenFirstChunk, int offset) {
+		this.offset = (long) offset;
+		inputStream = iStream;
+		crcEngine = new CRC32();
+		this.lenLastChunk = lenFirstChunk;
+		toReadThisChunk = lenFirstChunk;
+		// we know it's a IDAT
+		System.arraycopy(ChunkHelper.b_IDAT, 0, idLastChunk, 0, 4);
+		crcEngine.update(idLastChunk, 0, 4);
+		foundChunksInfo.add(new IdatChunkInfo(lenLastChunk, offset - 8));
+		// PngHelper.logdebug("IDAT Initial fragment: len=" + lenLastChunk);
+		if (this.lenLastChunk == 0)
+			endChunkGoForNext(); // rare, but...
+	}
+
+	/**
+	 * does NOT close the associated stream!
+	 */
+	@Override
+	public void close() throws IOException {
+		super.close(); // nothing
+	}
+
+	private void endChunkGoForNext() {
+		// Called after readging the last byte of chunk
+		// Checks CRC, and read ID from next CHUNK
+		// Those values are left in idLastChunk / lenLastChunk
+		// Skips empty IDATS
+		do {
+			int crc = PngHelper.readInt4(inputStream); //
+			offset += 4;
+			int crccalc = (int) crcEngine.getValue();
+			if (lenLastChunk > 0 && crc != crccalc)
+				throw new PngjBadCrcException("error reading idat; offset: " + offset);
+			crcEngine.reset();
+			lenLastChunk = PngHelper.readInt4(inputStream);
+			if (lenLastChunk < 0)
+				throw new PngjInputException("invalid len for chunk: " + lenLastChunk);
+			toReadThisChunk = lenLastChunk;
+			PngHelper.readBytes(inputStream, idLastChunk, 0, 4);
+			offset += 8;
+			ended = !Arrays.equals(idLastChunk, ChunkHelper.b_IDAT);
+			if (!ended) {
+				foundChunksInfo.add(new IdatChunkInfo(lenLastChunk, (int) (offset - 8)));
+				crcEngine.update(idLastChunk, 0, 4);
+			}
+			// PngHelper.logdebug("IDAT ended. next len= " + lenLastChunk + " idat?" +
+			// (!ended));
+		} while (lenLastChunk == 0 && !ended);
+		// rarely condition is true (empty IDAT ??)
+	}
+
+	/**
+	 * sometimes last row read does not fully consumes the chunk here we read the reamaing dummy bytes
+	 */
+	void forceChunkEnd() {
+		if (!ended) {
+			byte[] dummy = new byte[toReadThisChunk];
+			PngHelper.readBytes(inputStream, dummy, 0, toReadThisChunk);
+			crcEngine.update(dummy, 0, toReadThisChunk);
+			endChunkGoForNext();
+		}
+	}
+
+	/**
+	 * This can return less than len, but never 0 Returns -1 if "pseudo file" ended prematurely. That is our error.
+	 */
+	@Override
+	public int read(byte[] b, int off, int len) throws IOException {
+		if (toReadThisChunk == 0)
+			throw new RuntimeException("this should not happen");
+		int n = inputStream.read(b, off, len >= toReadThisChunk ? toReadThisChunk : len);
+		if (n > 0) {
+			crcEngine.update(b, off, n);
+			this.offset += n;
+			toReadThisChunk -= n;
+		}
+		if (toReadThisChunk == 0) { // end of chunk: prepare for next
+			endChunkGoForNext();
+		}
+		return n;
+	}
+
+	@Override
+	public int read(byte[] b) throws IOException {
+		return this.read(b, 0, b.length);
+	}
+
+	@Override
+	public int read() throws IOException {
+		// PngHelper.logdebug("read() should go here");
+		// inneficient - but this should be used rarely
+		byte[] b1 = new byte[1];
+		int r = this.read(b1, 0, 1);
+		return r < 0 ? -1 : (int) b1[0];
+	}
+
+	int getLenLastChunk() {
+		return lenLastChunk;
+	}
+
+	byte[] getIdLastChunk() {
+		return idLastChunk;
+	}
+
+	long getOffset() {
+		return offset;
+	}
+
+	boolean isEnded() {
+		return ended;
+	}
+}
diff --git a/src/jogl/classes/jogamp/opengl/util/pngj/PngIDatChunkOutputStream.java b/src/jogl/classes/jogamp/opengl/util/pngj/PngIDatChunkOutputStream.java
new file mode 100644
index 000000000..8b9fa5dae
--- /dev/null
+++ b/src/jogl/classes/jogamp/opengl/util/pngj/PngIDatChunkOutputStream.java
@@ -0,0 +1,31 @@
+package jogamp.opengl.util.pngj;
+
+import java.io.OutputStream;
+
+import jogamp.opengl.util.pngj.chunks.ChunkHelper;
+import jogamp.opengl.util.pngj.chunks.ChunkRaw;
+
+
+/**
+ * outputs the stream for IDAT chunk , fragmented at fixed size (16384 default).
+ */
+class PngIDatChunkOutputStream extends ProgressiveOutputStream {
+	private static final int SIZE_DEFAULT = 16384;
+	private final OutputStream outputStream;
+
+	PngIDatChunkOutputStream(OutputStream outputStream) {
+		this(outputStream, SIZE_DEFAULT);
+	}
+
+	PngIDatChunkOutputStream(OutputStream outputStream, int size) {
+		super(size);
+		this.outputStream = outputStream;
+	}
+
+	@Override
+	public final void flushBuffer(byte[] b, int len) {
+		ChunkRaw c = new ChunkRaw(len, ChunkHelper.b_IDAT, false);
+		c.data = b;
+		c.writeChunk(outputStream);
+	}
+}
diff --git a/src/jogl/classes/jogamp/opengl/util/pngj/PngReader.java b/src/jogl/classes/jogamp/opengl/util/pngj/PngReader.java
new file mode 100644
index 000000000..7343893b6
--- /dev/null
+++ b/src/jogl/classes/jogamp/opengl/util/pngj/PngReader.java
@@ -0,0 +1,415 @@
+package jogamp.opengl.util.pngj;
+
+import java.io.InputStream;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+import java.util.zip.InflaterInputStream;
+
+import jogamp.opengl.util.pngj.PngIDatChunkInputStream.IdatChunkInfo;
+import jogamp.opengl.util.pngj.chunks.ChunkHelper;
+import jogamp.opengl.util.pngj.chunks.ChunkList;
+import jogamp.opengl.util.pngj.chunks.ChunkLoadBehaviour;
+import jogamp.opengl.util.pngj.chunks.ChunkRaw;
+import jogamp.opengl.util.pngj.chunks.PngChunk;
+import jogamp.opengl.util.pngj.chunks.PngChunkIHDR;
+import jogamp.opengl.util.pngj.chunks.PngMetadata;
+
+
+/**
+ * Reads a PNG image, line by line
+ */
+public class PngReader {
+	/**
+	 * Basic image info - final and inmutable.
+	 */
+	public final ImageInfo imgInfo;
+	protected final String filename; // not necesarily a file, can be a description - merely informative
+
+	private static int MAX_BYTES_CHUNKS_TO_LOAD = 640000;
+	private ChunkLoadBehaviour chunkLoadBehaviour = ChunkLoadBehaviour.LOAD_CHUNK_ALWAYS;
+
+	private final InputStream is;
+	private InflaterInputStream idatIstream;
+	private PngIDatChunkInputStream iIdatCstream;
+
+	protected int currentChunkGroup = -1;
+	protected int rowNum = -1; // current row number
+	private int offset = 0;
+	private int bytesChunksLoaded; // bytes loaded from anciallary chunks
+
+	protected ImageLine imgLine;
+
+	// line as bytes, counting from 1 (index 0 is reserved for filter type)
+	protected byte[] rowb = null;
+	protected byte[] rowbprev = null; // rowb previous
+	protected byte[] rowbfilter = null; // current line 'filtered': exactly as in uncompressed stream
+
+	/**
+	 * All chunks loaded. Criticals are included, except that all IDAT chunks appearance are replaced by a single
+	 * dummy-marker IDAT chunk. These might be copied to the PngWriter
+	 */
+	private final ChunkList chunksList;
+	private final PngMetadata metadata; // this a wrapper over chunks
+
+	/**
+	 * Constructs a PngReader from an InputStream.
+	 * <p>
+	 * See also <code>FileHelper.createPngReader(File f)</code> if available.
+	 * 
+	 * Reads only the signature and first chunk (IDHR)
+	 * 
+	 * @param filenameOrDescription
+	 *            : Optional, can be a filename or a description. Just for error/debug messages
+	 * 
+	 */
+	public PngReader(InputStream inputStream, String filenameOrDescription) {
+		this.filename = filenameOrDescription == null ? "" : filenameOrDescription;
+		this.is = inputStream;
+		this.chunksList = new ChunkList(null);
+		this.metadata = new PngMetadata(chunksList, true);
+		// reads header (magic bytes)
+		byte[] pngid = new byte[PngHelper.pngIdBytes.length];
+		PngHelper.readBytes(is, pngid, 0, pngid.length);
+		offset += pngid.length;
+		if (!Arrays.equals(pngid, PngHelper.pngIdBytes))
+			throw new PngjInputException("Bad PNG signature");
+		// reads first chunk
+		currentChunkGroup = ChunkList.CHUNK_GROUP_0_IDHR;
+		int clen = PngHelper.readInt4(is);
+		offset += 4;
+		if (clen != 13)
+			throw new RuntimeException("IDHR chunk len != 13 ?? " + clen);
+		byte[] chunkid = new byte[4];
+		PngHelper.readBytes(is, chunkid, 0, 4);
+		if (!Arrays.equals(chunkid, ChunkHelper.b_IHDR))
+			throw new PngjInputException("IHDR not found as first chunk??? [" + ChunkHelper.toString(chunkid) + "]");
+		offset += 4;
+		ChunkRaw chunk = new ChunkRaw(clen, chunkid, true);
+		String chunkids = ChunkHelper.toString(chunkid);
+		offset += chunk.readChunkData(is);
+		PngChunkIHDR ihdr = (PngChunkIHDR) addChunkToList(chunk);
+		boolean alpha = (ihdr.getColormodel() & 0x04) != 0;
+		boolean palette = (ihdr.getColormodel() & 0x01) != 0;
+		boolean grayscale = (ihdr.getColormodel() == 0 || ihdr.getColormodel() == 4);
+		imgInfo = new ImageInfo(ihdr.getCols(), ihdr.getRows(), ihdr.getBitspc(), alpha, grayscale, palette);
+		imgLine = new ImageLine(imgInfo);
+		if (ihdr.getInterlaced() != 0)
+			throw new PngjUnsupportedException("PNG interlaced not supported by this library");
+		if (ihdr.getFilmeth() != 0 || ihdr.getCompmeth() != 0)
+			throw new PngjInputException("compmethod o filtermethod unrecognized");
+		if (ihdr.getColormodel() < 0 || ihdr.getColormodel() > 6 || ihdr.getColormodel() == 1
+				|| ihdr.getColormodel() == 5)
+			throw new PngjInputException("Invalid colormodel " + ihdr.getColormodel());
+		if (ihdr.getBitspc() != 1 && ihdr.getBitspc() != 2 && ihdr.getBitspc() != 4 && ihdr.getBitspc() != 8
+				&& ihdr.getBitspc() != 16)
+			throw new PngjInputException("Invalid bit depth " + ihdr.getBitspc());
+		// allocation: one extra byte for filter type one pixel
+		rowbfilter = new byte[imgInfo.bytesPerRow + 1];
+		rowb = new byte[imgInfo.bytesPerRow + 1];
+		rowbprev = new byte[rowb.length];
+	}
+
+	private static class FoundChunkInfo {
+		public final String id;
+		public final int len;
+		public final int offset;
+		public final boolean loaded;
+
+		private FoundChunkInfo(String id, int len, int offset, boolean loaded) {
+			this.id = id;
+			this.len = len;
+			this.offset = offset;
+			this.loaded = loaded;
+		}
+
+		public String toString() {
+			return "chunk " + id + " len=" + len + " offset=" + offset + (this.loaded ? " " : " X ");
+		}
+	}
+
+	private PngChunk addChunkToList(ChunkRaw chunk) {
+		// this requires that the currentChunkGroup is ok
+		PngChunk chunkType = PngChunk.factory(chunk, imgInfo);
+		if (!chunkType.crit) {
+			bytesChunksLoaded += chunk.len;
+		}
+		if (bytesChunksLoaded > MAX_BYTES_CHUNKS_TO_LOAD) {
+			throw new PngjInputException("Chunk exceeded available space (" + MAX_BYTES_CHUNKS_TO_LOAD + ") chunk: "
+					+ chunk + " See PngReader.MAX_BYTES_CHUNKS_TO_LOAD\n");
+		}
+		chunksList.appendReadChunk(chunkType, currentChunkGroup);
+		return chunkType;
+	}
+
+	/**
+	 * Reads chunks before first IDAT. Position before: after IDHR (crc included) Position after: just after the first
+	 * IDAT chunk id
+	 * 
+	 * This can be called several times (tentatively), it does nothing if already run
+	 * 
+	 * (Note: when should this be called? in the constructor? hardly, because we loose the opportunity to call
+	 * setChunkLoadBehaviour() and perhaps other settings before reading the first row? but sometimes we want to access
+	 * some metadata (plte, phys) before. Because of this, this method can be called explicitly but is also called
+	 * implicititly in some methods (getMetatada(), getChunks())
+	 * 
+	 **/
+	public void readFirstChunks() {
+		if (!firstChunksNotYetRead())
+			return;
+		int clen = 0;
+		boolean found = false;
+		byte[] chunkid = new byte[4]; // it's important to reallocate in each iteration
+		currentChunkGroup = ChunkList.CHUNK_GROUP_1_AFTERIDHR;
+		while (!found) {
+			clen = PngHelper.readInt4(is);
+			offset += 4;
+			if (clen < 0)
+				break;
+			PngHelper.readBytes(is, chunkid, 0, 4);
+			offset += 4;
+			if (Arrays.equals(chunkid, ChunkHelper.b_IDAT)) {
+				found = true;
+				currentChunkGroup = ChunkList.CHUNK_GROUP_4_IDAT;
+				// add dummy idat chunk to list
+				ChunkRaw chunk = new ChunkRaw(0, chunkid, false);
+				addChunkToList(chunk);
+				break;
+			} else if (Arrays.equals(chunkid, ChunkHelper.b_IEND)) {
+				throw new PngjInputException("END chunk found before image data (IDAT) at offset=" + offset);
+			}
+			ChunkRaw chunk = new ChunkRaw(clen, chunkid, true);
+			String chunkids = ChunkHelper.toString(chunkid);
+			boolean loadchunk = ChunkHelper.shouldLoad(chunkids, chunkLoadBehaviour);
+			offset += chunk.readChunkData(is);
+			if (chunkids.equals(ChunkHelper.PLTE))
+				currentChunkGroup = ChunkList.CHUNK_GROUP_2_PLTE;
+			if (loadchunk)
+				addChunkToList(chunk);
+			if (chunkids.equals(ChunkHelper.PLTE))
+				currentChunkGroup = ChunkList.CHUNK_GROUP_3_AFTERPLTE;
+		}
+		int idatLen = found ? clen : -1;
+		if (idatLen < 0)
+			throw new PngjInputException("first idat chunk not found!");
+		iIdatCstream = new PngIDatChunkInputStream(is, idatLen, offset);
+		idatIstream = new InflaterInputStream(iIdatCstream);
+	}
+
+	/**
+	 * Reads (and processes) chunks after last IDAT.
+	 **/
+	private void readLastChunks() {
+		// PngHelper.logdebug("idat ended? " + iIdatCstream.isEnded());
+		currentChunkGroup = ChunkList.CHUNK_GROUP_5_AFTERIDAT;
+		if (!iIdatCstream.isEnded())
+			iIdatCstream.forceChunkEnd();
+		int clen = iIdatCstream.getLenLastChunk();
+		byte[] chunkid = iIdatCstream.getIdLastChunk();
+		boolean endfound = false;
+		boolean first = true;
+		boolean ignore = false;
+		while (!endfound) {
+			ignore = false;
+			if (!first) {
+				clen = PngHelper.readInt4(is);
+				offset += 4;
+				if (clen < 0)
+					throw new PngjInputException("bad len " + clen);
+				PngHelper.readBytes(is, chunkid, 0, 4);
+				offset += 4;
+			}
+			first = false;
+			if (Arrays.equals(chunkid, ChunkHelper.b_IDAT)) {
+				// PngHelper.logdebug("extra IDAT chunk len - ignoring : ");
+				ignore = true;
+			} else if (Arrays.equals(chunkid, ChunkHelper.b_IEND)) {
+				currentChunkGroup = ChunkList.CHUNK_GROUP_6_END;
+				endfound = true;
+			}
+			ChunkRaw chunk = new ChunkRaw(clen, chunkid, true);
+			String chunkids = ChunkHelper.toString(chunkid);
+			boolean loadchunk = ChunkHelper.shouldLoad(chunkids, chunkLoadBehaviour);
+			offset += chunk.readChunkData(is);
+			if (loadchunk && !ignore) {
+				addChunkToList(chunk);
+			}
+		}
+		if (!endfound)
+			throw new PngjInputException("end chunk not found - offset=" + offset);
+		// PngHelper.logdebug("end chunk found ok offset=" + offset);
+	}
+
+	/**
+	 * Calls <code>readRow(int[] buffer, int nrow)</code> using internal ImageLine as buffer. This doesn't allocate or
+	 * copy anything.
+	 * 
+	 * @return The ImageLine that also is available inside this object.
+	 */
+	public ImageLine readRow(int nrow) {
+		readRow(imgLine.scanline, nrow);
+		imgLine.filterUsed = FilterType.getByVal(rowbfilter[0]);
+		imgLine.setRown(nrow);
+		return imgLine;
+	}
+
+	/**
+	 * Reads a line and returns it as a int[] array.
+	 * 
+	 * You can pass (optionally) a prealocatted buffer.
+	 * 
+	 * @param buffer
+	 *            Prealocated buffer, or null.
+	 * @param nrow
+	 *            Row number (0 is top). This is mostly for checking, because this library reads rows in sequence.
+	 * 
+	 * @return The scanline in the same passwd buffer if it was allocated, a newly allocated one otherwise
+	 */
+	public int[] readRow(int[] buffer, int nrow) {
+		if (nrow < 0 || nrow >= imgInfo.rows)
+			throw new PngjInputException("invalid line");
+		if (nrow != rowNum + 1)
+			throw new PngjInputException("invalid line (expected: " + (rowNum + 1));
+		if (nrow == 0 && firstChunksNotYetRead())
+			readFirstChunks();
+		rowNum++;
+		if (buffer == null || buffer.length < imgInfo.samplesPerRowP)
+			buffer = new int[imgInfo.samplesPerRowP];
+		// swap
+		byte[] tmp = rowb;
+		rowb = rowbprev;
+		rowbprev = tmp;
+		// loads in rowbfilter "raw" bytes, with filter
+		PngHelper.readBytes(idatIstream, rowbfilter, 0, rowbfilter.length);
+		rowb[0] = 0;
+		unfilterRow();
+		rowb[0] = rowbfilter[0];
+		convertRowFromBytes(buffer);
+		return buffer;
+	}
+
+	/**
+	 * This should be called after having read the last line. It reads extra chunks after IDAT, if present.
+	 */
+	public void end() {
+		offset = (int) iIdatCstream.getOffset();
+		try {
+			idatIstream.close();
+		} catch (Exception e) {
+		}
+		readLastChunks();
+		try {
+			is.close();
+		} catch (Exception e) {
+			throw new PngjInputException("error closing input stream!", e);
+		}
+	}
+
+	private void convertRowFromBytes(int[] buffer) {
+		// http://www.libpng.org/pub/png/spec/1.2/PNG-DataRep.html
+		int i, j;
+		if (imgInfo.bitDepth <= 8) {
+			for (i = 0, j = 1; i < imgInfo.samplesPerRowP; i++) {
+				buffer[i] = (rowb[j++] & 0xFF);
+			}
+		} else { // 16 bitspc
+			for (i = 0, j = 1; i < imgInfo.samplesPerRowP; i++) {
+				buffer[i] = ((rowb[j++] & 0xFF) << 8) + (rowb[j++] & 0xFF);
+			}
+		}
+	}
+
+	private void unfilterRow() {
+		int ftn = rowbfilter[0];
+		FilterType ft = FilterType.getByVal(ftn);
+		if (ft == null)
+			throw new PngjInputException("Filter type " + ftn + " invalid");
+		switch (ft) {
+		case FILTER_NONE:
+			unfilterRowNone();
+			break;
+		case FILTER_SUB:
+			unfilterRowSub();
+			break;
+		case FILTER_UP:
+			unfilterRowUp();
+			break;
+		case FILTER_AVERAGE:
+			unfilterRowAverage();
+			break;
+		case FILTER_PAETH:
+			unfilterRowPaeth();
+			break;
+		default:
+			throw new PngjInputException("Filter type " + ftn + " not implemented");
+		}
+	}
+
+	private void unfilterRowNone() {
+		for (int i = 1; i <= imgInfo.bytesPerRow; i++) {
+			rowb[i] = (byte) (rowbfilter[i]);
+		}
+	}
+
+	private void unfilterRowSub() {
+		int i, j;
+		for (i = 1; i <= imgInfo.bytesPixel; i++) {
+			rowb[i] = (byte) (rowbfilter[i]);
+		}
+		for (j = 1, i = imgInfo.bytesPixel + 1; i <= imgInfo.bytesPerRow; i++, j++) {
+			rowb[i] = (byte) (rowbfilter[i] + rowb[j]);
+		}
+	}
+
+	private void unfilterRowUp() {
+		for (int i = 1; i <= imgInfo.bytesPerRow; i++) {
+			rowb[i] = (byte) (rowbfilter[i] + rowbprev[i]);
+		}
+	}
+
+	private void unfilterRowAverage() {
+		int i, j, x;
+		for (j = 1 - imgInfo.bytesPixel, i = 1; i <= imgInfo.bytesPerRow; i++, j++) {
+			x = j > 0 ? (rowb[j] & 0xff) : 0;
+			rowb[i] = (byte) (rowbfilter[i] + (x + (rowbprev[i] & 0xFF)) / 2);
+		}
+	}
+
+	private void unfilterRowPaeth() {
+		int i, j, x, y;
+		for (j = 1 - imgInfo.bytesPixel, i = 1; i <= imgInfo.bytesPerRow; i++, j++) {
+			x = j > 0 ? (rowb[j] & 0xFF) : 0;
+			y = j > 0 ? (rowbprev[j] & 0xFF) : 0;
+			rowb[i] = (byte) (rowbfilter[i] + FilterType.filterPaethPredictor(x, rowbprev[i] & 0xFF, y));
+		}
+	}
+
+	public ChunkLoadBehaviour getChunkLoadBehaviour() {
+		return chunkLoadBehaviour;
+	}
+
+	public void setChunkLoadBehaviour(ChunkLoadBehaviour chunkLoadBehaviour) {
+		this.chunkLoadBehaviour = chunkLoadBehaviour;
+	}
+
+	private boolean firstChunksNotYetRead() {
+		return currentChunkGroup < ChunkList.CHUNK_GROUP_1_AFTERIDHR;
+	}
+
+	public ChunkList getChunksList() {
+		if (firstChunksNotYetRead())
+			readFirstChunks();
+		return chunksList;
+	}
+
+	public PngMetadata getMetadata() {
+		if (firstChunksNotYetRead())
+			readFirstChunks();
+		return metadata;
+	}
+
+	public String toString() { // basic info
+		return "filename=" + filename + " " + imgInfo.toString();
+	}
+
+}
diff --git a/src/jogl/classes/jogamp/opengl/util/pngj/PngWriter.java b/src/jogl/classes/jogamp/opengl/util/pngj/PngWriter.java
new file mode 100644
index 000000000..ee8472bf0
--- /dev/null
+++ b/src/jogl/classes/jogamp/opengl/util/pngj/PngWriter.java
@@ -0,0 +1,462 @@
+package jogamp.opengl.util.pngj;
+
+import java.io.IOException;
+import java.io.OutputStream;
+import java.util.Arrays;
+import java.util.List;
+import java.util.zip.Deflater;
+import java.util.zip.DeflaterOutputStream;
+
+import jogamp.opengl.util.pngj.chunks.ChunkCopyBehaviour;
+import jogamp.opengl.util.pngj.chunks.ChunkHelper;
+import jogamp.opengl.util.pngj.chunks.ChunkList;
+import jogamp.opengl.util.pngj.chunks.PngChunk;
+import jogamp.opengl.util.pngj.chunks.PngChunkIEND;
+import jogamp.opengl.util.pngj.chunks.PngChunkIHDR;
+import jogamp.opengl.util.pngj.chunks.PngChunkTextVar;
+import jogamp.opengl.util.pngj.chunks.PngMetadata;
+
+
+/**
+ * Writes a PNG image, line by line.
+ */
+public class PngWriter {
+
+	public final ImageInfo imgInfo;
+
+	protected int compLevel = 6; // zip compression level 0 - 9
+	private int deflaterStrategy = Deflater.FILTERED;
+	protected FilterWriteStrategy filterStrat;
+
+	protected int currentChunkGroup = -1;
+	protected int rowNum = -1; // current line number
+
+	// current line, one (packed) sample per element (layout differnt from rowb!)
+	protected int[] scanline = null;
+	protected byte[] rowb = null; // element 0 is filter type!
+	protected byte[] rowbprev = null; // rowb prev
+	protected byte[] rowbfilter = null; // current line with filter
+
+	protected final OutputStream os;
+	protected final String filename; // optional, can be a description
+
+	private PngIDatChunkOutputStream datStream;
+	private DeflaterOutputStream datStreamDeflated;
+
+	private final ChunkList chunkList;
+	private final PngMetadata metadata; // high level wrapper over chunkList
+
+	public PngWriter(OutputStream outputStream, ImageInfo imgInfo) {
+		this(outputStream, imgInfo, "[NO FILENAME AVAILABLE]");
+	}
+
+	/**
+	 * Constructs a new PngWriter from a output stream.
+	 * <p>
+	 * See also <code>FileHelper.createPngWriter()</code> if available.
+	 * 
+	 * @param outputStream
+	 *            Opened stream for binary writing
+	 * @param imgInfo
+	 *            Basic image parameters
+	 * @param filenameOrDescription
+	 *            Optional, just for error/debug messages
+	 */
+	public PngWriter(OutputStream outputStream, ImageInfo imgInfo, String filenameOrDescription) {
+		this.filename = filenameOrDescription == null ? "" : filenameOrDescription;
+		this.os = outputStream;
+		this.imgInfo = imgInfo;
+		// prealloc
+		scanline = new int[imgInfo.samplesPerRowP];
+		rowb = new byte[imgInfo.bytesPerRow + 1];
+		rowbprev = new byte[rowb.length];
+		rowbfilter = new byte[rowb.length];
+		datStream = new PngIDatChunkOutputStream(this.os);
+		chunkList = new ChunkList(imgInfo);
+		metadata = new PngMetadata(chunkList, false);
+		filterStrat = new FilterWriteStrategy(imgInfo, FilterType.FILTER_DEFAULT);
+	}
+
+	/**
+	 * Write id signature and also "IHDR" chunk
+	 */
+	private void writeSignatureAndIHDR() {
+		currentChunkGroup = ChunkList.CHUNK_GROUP_0_IDHR;
+		if (datStreamDeflated == null) {
+			Deflater def = new Deflater(compLevel);
+			def.setStrategy(deflaterStrategy);
+			datStreamDeflated = new DeflaterOutputStream(datStream, def, 8192);
+		}
+		PngHelper.writeBytes(os, PngHelper.pngIdBytes); // signature
+		PngChunkIHDR ihdr = new PngChunkIHDR(imgInfo);
+		// http://www.libpng.org/pub/png/spec/1.2/PNG-Chunks.html
+		ihdr.setCols(imgInfo.cols);
+		ihdr.setRows(imgInfo.rows);
+		ihdr.setBitspc(imgInfo.bitDepth);
+		int colormodel = 0;
+		if (imgInfo.alpha)
+			colormodel += 0x04;
+		if (imgInfo.indexed)
+			colormodel += 0x01;
+		if (!imgInfo.greyscale)
+			colormodel += 0x02;
+		ihdr.setColormodel(colormodel);
+		ihdr.setCompmeth(0); // compression method 0=deflate
+		ihdr.setFilmeth(0); // filter method (0)
+		ihdr.setInterlaced(0); // we never interlace
+		ihdr.createChunk().writeChunk(os);
+
+	}
+
+	private void writeFirstChunks() {
+		int nw = 0;
+		currentChunkGroup = ChunkList.CHUNK_GROUP_1_AFTERIDHR;
+		nw = chunkList.writeChunks(os, currentChunkGroup);
+		currentChunkGroup = ChunkList.CHUNK_GROUP_2_PLTE;
+		nw = chunkList.writeChunks(os, currentChunkGroup);
+		if (nw > 0 && imgInfo.greyscale)
+			throw new PngjOutputException("cannot write palette for this format");
+		if (nw == 0 && imgInfo.indexed)
+			throw new PngjOutputException("missing palette");
+		currentChunkGroup = ChunkList.CHUNK_GROUP_3_AFTERPLTE;
+		nw = chunkList.writeChunks(os, currentChunkGroup);
+		currentChunkGroup = ChunkList.CHUNK_GROUP_4_IDAT;
+	}
+
+	private void writeLastChunks() { // not including end
+		currentChunkGroup = ChunkList.CHUNK_GROUP_5_AFTERIDAT;
+		chunkList.writeChunks(os, currentChunkGroup);
+		// should not be unwriten chunks
+		List<PngChunk> pending = chunkList.getQueuedChunks();
+		if (!pending.isEmpty())
+			throw new PngjOutputException(pending.size() + " chunks were not written! Eg: " + pending.get(0).toString());
+		currentChunkGroup = ChunkList.CHUNK_GROUP_6_END;
+	}
+
+	private void writeEndChunk() {
+		PngChunkIEND c = new PngChunkIEND(imgInfo);
+		c.createChunk().writeChunk(os);
+	}
+
+	/**
+	 * Writes a full image row. This must be called sequentially from n=0 to n=rows-1 One integer per sample , in the
+	 * natural order: R G B R G B ... (or R G B A R G B A... if has alpha) The values should be between 0 and 255 for 8
+	 * bitspc images, and between 0- 65535 form 16 bitspc images (this applies also to the alpha channel if present) The
+	 * array can be reused.
+	 * 
+	 * @param newrow
+	 *            Array of pixel values
+	 * @param rown
+	 *            Row number, from 0 (top) to rows-1 (bottom). This is just used as a check. Pass -1 if you want to
+	 *            autocompute it
+	 */
+	public void writeRow(int[] newrow, int rown) {
+		if (rown == 0) {
+			writeSignatureAndIHDR();
+			writeFirstChunks();
+		}
+		if (rown < -1 || rown > imgInfo.rows)
+			throw new RuntimeException("invalid value for row " + rown);
+		rowNum++;
+		if (rown >= 0 && rowNum != rown)
+			throw new RuntimeException("rows must be written in strict consecutive order: tried to write row " + rown
+					+ ", expected=" + rowNum);
+		scanline = newrow;
+		// swap
+		byte[] tmp = rowb;
+		rowb = rowbprev;
+		rowbprev = tmp;
+		convertRowToBytes();
+		filterRow(rown);
+		try {
+			datStreamDeflated.write(rowbfilter, 0, imgInfo.bytesPerRow + 1);
+		} catch (IOException e) {
+			throw new PngjOutputException(e);
+		}
+	}
+
+	/**
+	 * Same as writeRow(int[] newrow, int rown), but does not check row number
+	 * 
+	 * @param newrow
+	 */
+	public void writeRow(int[] newrow) {
+		writeRow(newrow, -1);
+	}
+
+	/**
+	 * Writes line. See writeRow(int[] newrow, int rown)
+	 */
+	public void writeRow(ImageLine imgline, int rownumber) {
+		writeRow(imgline.scanline, rownumber);
+	}
+
+	/**
+	 * Writes line, checks that the row number is consistent with that of the ImageLine See writeRow(int[] newrow, int
+	 * rown)
+	 * 
+	 * @deprecated Better use writeRow(ImageLine imgline, int rownumber)
+	 */
+	public void writeRow(ImageLine imgline) {
+		writeRow(imgline.scanline, imgline.getRown());
+	}
+
+	/**
+	 * Finalizes the image creation and closes the stream. This MUST be called after writing the lines.
+	 */
+	public void end() {
+		if (rowNum != imgInfo.rows - 1)
+			throw new PngjOutputException("all rows have not been written");
+		try {
+			datStreamDeflated.finish();
+			datStream.flush();
+			writeLastChunks();
+			writeEndChunk();
+			os.close();
+		} catch (IOException e) {
+			throw new PngjOutputException(e);
+		}
+	}
+
+	private int[] histox = new int[256]; // auxiliar buffer, only used by reportResultsForFilter
+
+	private void reportResultsForFilter(int rown, FilterType type, boolean tentative) {
+		Arrays.fill(histox, 0);
+		int s = 0, v;
+		for (int i = 1; i <= imgInfo.bytesPerRow; i++) {
+			v = rowbfilter[i];
+			if (v < 0)
+				s -= (int) v;
+			else
+				s += (int) v;
+			histox[v & 0xFF]++;
+		}
+		filterStrat.fillResultsForFilter(rown, type, s, histox, tentative);
+	}
+
+	private void filterRow(int rown) {
+		// warning: filters operation rely on: "previos row" (rowbprev) is
+		// initialized to 0 the first time
+		if (filterStrat.shouldTestAll(rown)) {
+			filterRowNone();
+			reportResultsForFilter(rown, FilterType.FILTER_NONE, true);
+			filterRowSub();
+			reportResultsForFilter(rown, FilterType.FILTER_SUB, true);
+			filterRowUp();
+			reportResultsForFilter(rown, FilterType.FILTER_UP, true);
+			filterRowAverage();
+			reportResultsForFilter(rown, FilterType.FILTER_AVERAGE, true);
+			filterRowPaeth();
+			reportResultsForFilter(rown, FilterType.FILTER_PAETH, true);
+		}
+		FilterType filterType = filterStrat.gimmeFilterType(rown, true);
+		rowbfilter[0] = (byte) filterType.val;
+		switch (filterType) {
+		case FILTER_NONE:
+			filterRowNone();
+			break;
+		case FILTER_SUB:
+			filterRowSub();
+			break;
+		case FILTER_UP:
+			filterRowUp();
+			break;
+		case FILTER_AVERAGE:
+			filterRowAverage();
+			break;
+		case FILTER_PAETH:
+			filterRowPaeth();
+			break;
+		default:
+			throw new PngjOutputException("Filter type " + filterType + " not implemented");
+		}
+		reportResultsForFilter(rown, filterType, false);
+	}
+
+	protected int sumRowbfilter() { // sums absolute value
+		int s = 0;
+		for (int i = 1; i <= imgInfo.bytesPerRow; i++)
+			if (rowbfilter[i] < 0)
+				s -= (int) rowbfilter[i];
+			else
+				s += (int) rowbfilter[i];
+		return s;
+	}
+
+	protected void filterRowNone() {
+		for (int i = 1; i <= imgInfo.bytesPerRow; i++) {
+			rowbfilter[i] = (byte) rowb[i];
+		}
+	}
+
+	protected void filterRowSub() {
+		int i, j;
+		for (i = 1; i <= imgInfo.bytesPixel; i++)
+			rowbfilter[i] = (byte) rowb[i];
+		for (j = 1, i = imgInfo.bytesPixel + 1; i <= imgInfo.bytesPerRow; i++, j++) {
+			rowbfilter[i] = (byte) (rowb[i] - rowb[j]);
+		}
+	}
+
+	protected void filterRowUp() {
+		for (int i = 1; i <= imgInfo.bytesPerRow; i++) {
+			rowbfilter[i] = (byte) (rowb[i] - rowbprev[i]);
+		}
+	}
+
+	protected void filterRowAverage() {
+		int i, j;
+		for (j = 1 - imgInfo.bytesPixel, i = 1; i <= imgInfo.bytesPerRow; i++, j++) {
+			rowbfilter[i] = (byte) (rowb[i] - ((rowbprev[i] & 0xFF) + (j > 0 ? (rowb[j] & 0xFF) : 0)) / 2);
+		}
+	}
+
+	protected void filterRowPaeth() {
+		int i, j;
+		for (j = 1 - imgInfo.bytesPixel, i = 1; i <= imgInfo.bytesPerRow; i++, j++) {
+			rowbfilter[i] = (byte) (rowb[i] - FilterType.filterPaethPredictor(j > 0 ? (rowb[j] & 0xFF) : 0,
+					rowbprev[i] & 0xFF, j > 0 ? (rowbprev[j] & 0xFF) : 0));
+		}
+	}
+
+	protected void convertRowToBytes() {
+		// http://www.libpng.org/pub/png/spec/1.2/PNG-DataRep.html
+		int i, j;
+		if (imgInfo.bitDepth <= 8) {
+			for (i = 0, j = 1; i < imgInfo.samplesPerRowP; i++) {
+				rowb[j++] = (byte) (scanline[i]);
+			}
+		} else { // 16 bitspc
+			for (i = 0, j = 1; i < imgInfo.samplesPerRowP; i++) {
+				// x = (int) (scanline[i]) & 0xFFFF;
+				rowb[j++] = (byte) (scanline[i] >> 8);
+				rowb[j++] = (byte) (scanline[i]);
+			}
+		}
+	}
+
+	// /// several getters / setters - all this setters are optional
+
+	/**
+	 * Filename or description, from the optional constructor argument.
+	 */
+	public String getFilename() {
+		return filename;
+	}
+
+	/**
+	 * Sets internal prediction filter type, or strategy to choose it.
+	 * <p>
+	 * This must be called just after constructor, before starting writing.
+	 * <p>
+	 * See also setCompLevel()
+	 * 
+	 * @param filterType
+	 *            One of the five prediction types or strategy to choose it (see <code>PngFilterType</code>) Recommended
+	 *            values: DEFAULT (default) or AGGRESIVE
+	 */
+	public void setFilterType(FilterType filterType) {
+		filterStrat = new FilterWriteStrategy(imgInfo, filterType);
+	}
+
+	/**
+	 * Sets compression level of ZIP algorithm.
+	 * <p>
+	 * This must be called just after constructor, before starting writing.
+	 * <p>
+	 * See also setFilterType()
+	 * 
+	 * @param compLevel
+	 *            between 0 and 9 (default:6 , recommended: 6 or more)
+	 */
+	public void setCompLevel(int compLevel) {
+		if (compLevel < 0 || compLevel > 9)
+			throw new PngjException("Compression level invalid (" + compLevel + ") Must be 0..9");
+		this.compLevel = compLevel;
+	}
+
+	/**
+	 * copy chunks from reader - copy_mask : see ChunksToWrite.COPY_XXX
+	 * 
+	 * If we are after idat, only considers those chunks after IDAT in PngReader TODO: this should be more customizable
+	 */
+	private void copyChunks(PngReader reader, int copy_mask, boolean onlyAfterIdat) {
+		boolean idatDone = currentChunkGroup >= ChunkList.CHUNK_GROUP_4_IDAT;
+		for (PngChunk chunk : reader.getChunksList().getChunks()) {
+			int group = chunk.getChunkGroup();
+			if (group < ChunkList.CHUNK_GROUP_4_IDAT && idatDone)
+				continue;
+			boolean copy = false;
+			if (chunk.crit) {
+				if (chunk.id.equals(ChunkHelper.PLTE)) {
+					if (imgInfo.indexed && ChunkHelper.maskMatch(copy_mask, ChunkCopyBehaviour.COPY_PALETTE))
+						copy = true;
+					if (!imgInfo.greyscale && ChunkHelper.maskMatch(copy_mask, ChunkCopyBehaviour.COPY_ALL))
+						copy = true;
+				}
+			} else { // ancillary
+				boolean text = (chunk instanceof PngChunkTextVar);
+				boolean safe = chunk.safe;
+				// notice that these if are not exclusive
+				if (ChunkHelper.maskMatch(copy_mask, ChunkCopyBehaviour.COPY_ALL))
+					copy = true;
+				if (safe && ChunkHelper.maskMatch(copy_mask, ChunkCopyBehaviour.COPY_ALL_SAFE))
+					copy = true;
+				if (chunk.id.equals(ChunkHelper.tRNS)
+						&& ChunkHelper.maskMatch(copy_mask, ChunkCopyBehaviour.COPY_TRANSPARENCY))
+					copy = true;
+				if (chunk.id.equals(ChunkHelper.pHYs) && ChunkHelper.maskMatch(copy_mask, ChunkCopyBehaviour.COPY_PHYS))
+					copy = true;
+				if (text && ChunkHelper.maskMatch(copy_mask, ChunkCopyBehaviour.COPY_TEXTUAL))
+					copy = true;
+				if (ChunkHelper.maskMatch(copy_mask, ChunkCopyBehaviour.COPY_ALMOSTALL)
+						&& !(ChunkHelper.isUnknown(chunk) || text || chunk.id.equals(ChunkHelper.hIST) || chunk.id
+								.equals(ChunkHelper.tIME)))
+					copy = true;
+			}
+			if (copy) {
+				chunkList.queueChunk(PngChunk.cloneChunk(chunk, imgInfo), !chunk.allowsMultiple(), false);
+			}
+		}
+	}
+
+	/**
+	 * Copies first (pre IDAT) ancillary chunks from a PngReader.
+	 * <p>
+	 * Should be called when creating an image from another, before starting writing lines, to copy relevant chunks.
+	 * <p>
+	 * 
+	 * @param reader
+	 *            : PngReader object, already opened.
+	 * @param copy_mask
+	 *            : Mask bit (OR), see <code>ChunksToWrite.COPY_XXX</code> constants
+	 */
+	public void copyChunksFirst(PngReader reader, int copy_mask) {
+		copyChunks(reader, copy_mask, false);
+	}
+
+	/**
+	 * Copies last (post IDAT) ancillary chunks from a PngReader.
+	 * <p>
+	 * Should be called when creating an image from another, after writing all lines, before closing the writer, to copy
+	 * additional chunks.
+	 * <p>
+	 * 
+	 * @param reader
+	 *            : PngReader object, already opened and fully read.
+	 * @param copy_mask
+	 *            : Mask bit (OR), see <code>ChunksToWrite.COPY_XXX</code> constants
+	 */
+	public void copyChunksLast(PngReader reader, int copy_mask) {
+		copyChunks(reader, copy_mask, true);
+	}
+
+	public ChunkList getChunkList() {
+		return chunkList;
+	}
+
+	public PngMetadata getMetadata() {
+		return metadata;
+	}
+
+}
diff --git a/src/jogl/classes/jogamp/opengl/util/pngj/PngjBadCrcException.java b/src/jogl/classes/jogamp/opengl/util/pngj/PngjBadCrcException.java
new file mode 100644
index 000000000..3b74f862f
--- /dev/null
+++ b/src/jogl/classes/jogamp/opengl/util/pngj/PngjBadCrcException.java
@@ -0,0 +1,20 @@
+package jogamp.opengl.util.pngj;
+
+/**
+ * Exception thrown by bad CRC check
+ */
+public class PngjBadCrcException extends PngjInputException {
+	private static final long serialVersionUID = 1L;
+
+	public PngjBadCrcException(String message, Throwable cause) {
+		super(message, cause);
+	}
+
+	public PngjBadCrcException(String message) {
+		super(message);
+	}
+
+	public PngjBadCrcException(Throwable cause) {
+		super(cause);
+	}
+}
diff --git a/src/jogl/classes/jogamp/opengl/util/pngj/PngjException.java b/src/jogl/classes/jogamp/opengl/util/pngj/PngjException.java
new file mode 100644
index 000000000..4a45cb5bf
--- /dev/null
+++ b/src/jogl/classes/jogamp/opengl/util/pngj/PngjException.java
@@ -0,0 +1,23 @@
+package jogamp.opengl.util.pngj;
+
+/**
+ * Generic exception
+ * 
+ * @author Hernan J Gonzalez
+ * 
+ */
+public class PngjException extends RuntimeException {
+	private static final long serialVersionUID = 1L;
+
+	public PngjException(String message, Throwable cause) {
+		super(message, cause);
+	}
+
+	public PngjException(String message) {
+		super(message);
+	}
+
+	public PngjException(Throwable cause) {
+		super(cause);
+	}
+}
diff --git a/src/jogl/classes/jogamp/opengl/util/pngj/PngjInputException.java b/src/jogl/classes/jogamp/opengl/util/pngj/PngjInputException.java
new file mode 100644
index 000000000..5cc36b99a
--- /dev/null
+++ b/src/jogl/classes/jogamp/opengl/util/pngj/PngjInputException.java
@@ -0,0 +1,20 @@
+package jogamp.opengl.util.pngj;
+
+/**
+ * Exception thrown by reading process
+ */
+public class PngjInputException extends PngjException {
+	private static final long serialVersionUID = 1L;
+
+	public PngjInputException(String message, Throwable cause) {
+		super(message, cause);
+	}
+
+	public PngjInputException(String message) {
+		super(message);
+	}
+
+	public PngjInputException(Throwable cause) {
+		super(cause);
+	}
+}
diff --git a/src/jogl/classes/jogamp/opengl/util/pngj/PngjOutputException.java b/src/jogl/classes/jogamp/opengl/util/pngj/PngjOutputException.java
new file mode 100644
index 000000000..c8cd36acb
--- /dev/null
+++ b/src/jogl/classes/jogamp/opengl/util/pngj/PngjOutputException.java
@@ -0,0 +1,20 @@
+package jogamp.opengl.util.pngj;
+
+/**
+ * Exception thrown by writing process
+ */
+public class PngjOutputException extends PngjException {
+	private static final long serialVersionUID = 1L;
+
+	public PngjOutputException(String message, Throwable cause) {
+		super(message, cause);
+	}
+
+	public PngjOutputException(String message) {
+		super(message);
+	}
+
+	public PngjOutputException(Throwable cause) {
+		super(cause);
+	}
+}
diff --git a/src/jogl/classes/jogamp/opengl/util/pngj/PngjUnsupportedException.java b/src/jogl/classes/jogamp/opengl/util/pngj/PngjUnsupportedException.java
new file mode 100644
index 000000000..0801e33bb
--- /dev/null
+++ b/src/jogl/classes/jogamp/opengl/util/pngj/PngjUnsupportedException.java
@@ -0,0 +1,24 @@
+package jogamp.opengl.util.pngj;
+
+/**
+ * Exception thrown because of some valid feature of PNG standard that this library does not support
+ */
+public class PngjUnsupportedException extends RuntimeException {
+	private static final long serialVersionUID = 1L;
+
+	public PngjUnsupportedException() {
+		super();
+	}
+
+	public PngjUnsupportedException(String message, Throwable cause) {
+		super(message, cause);
+	}
+
+	public PngjUnsupportedException(String message) {
+		super(message);
+	}
+
+	public PngjUnsupportedException(Throwable cause) {
+		super(cause);
+	}
+}
diff --git a/src/jogl/classes/jogamp/opengl/util/pngj/ProgressiveOutputStream.java b/src/jogl/classes/jogamp/opengl/util/pngj/ProgressiveOutputStream.java
new file mode 100644
index 000000000..bbec247fb
--- /dev/null
+++ b/src/jogl/classes/jogamp/opengl/util/pngj/ProgressiveOutputStream.java
@@ -0,0 +1,71 @@
+package jogamp.opengl.util.pngj;
+
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+
+/**
+ * stream that outputs to memory and allows to flush fragments every 'size' bytes to some other destination
+ */
+abstract class ProgressiveOutputStream extends ByteArrayOutputStream {
+	private final int size;
+
+	public ProgressiveOutputStream(int size) {
+		this.size = size;
+	}
+
+	@Override
+	public final void close() throws IOException {
+		flush();
+		super.close();
+	}
+
+	@Override
+	public final void flush() throws IOException {
+		super.flush();
+		checkFlushBuffer(true);
+	}
+
+	@Override
+	public final void write(byte[] b, int off, int len) {
+		super.write(b, off, len);
+		checkFlushBuffer(false);
+	}
+
+	@Override
+	public final void write(byte[] b) throws IOException {
+		super.write(b);
+		checkFlushBuffer(false);
+	}
+
+	@Override
+	public final void write(int arg0) {
+		super.write(arg0);
+		checkFlushBuffer(false);
+	}
+
+	@Override
+	public final synchronized void reset() {
+		super.reset();
+	}
+
+	/**
+	 * if it's time to flush data (or if forced==true) calls abstract method flushBuffer() and cleans those bytes from
+	 * own buffer
+	 */
+	private final void checkFlushBuffer(boolean forced) {
+		while (forced || count >= size) {
+			int nb = size;
+			if (nb > count)
+				nb = count;
+			if (nb == 0)
+				return;
+			flushBuffer(buf, nb);
+			int bytesleft = count - nb;
+			count = bytesleft;
+			if (bytesleft > 0)
+				System.arraycopy(buf, nb, buf, 0, bytesleft);
+		}
+	}
+
+	public abstract void flushBuffer(byte[] b, int n);
+}
\ No newline at end of file
diff --git a/src/jogl/classes/jogamp/opengl/util/pngj/chunks/ChunkCopyBehaviour.java b/src/jogl/classes/jogamp/opengl/util/pngj/chunks/ChunkCopyBehaviour.java
new file mode 100644
index 000000000..43c0cb135
--- /dev/null
+++ b/src/jogl/classes/jogamp/opengl/util/pngj/chunks/ChunkCopyBehaviour.java
@@ -0,0 +1,24 @@
+package jogamp.opengl.util.pngj.chunks;
+
+/**
+ * Chunk copy policy to apply when copyng from a pngReader to a pngWriter http://www.w3.org/TR/PNG/#14
+ * <p>
+ * These are masks, can be OR-ed
+ **/
+public class ChunkCopyBehaviour {
+
+	/** dont copy anywhing */
+	public static final int COPY_NONE = 0;
+
+	/** copy the palette */
+	public static final int COPY_PALETTE = 1;
+
+	/** copy all 'safe to copy' chunks */
+	public static final int COPY_ALL_SAFE = 1 << 2;
+	public static final int COPY_ALL = 1 << 3; // includes palette!
+	public static final int COPY_PHYS = 1 << 4; // dpi
+	public static final int COPY_TEXTUAL = 1 << 5; // all textual types
+	public static final int COPY_TRANSPARENCY = 1 << 6; //
+	public static final int COPY_UNKNOWN = 1 << 7; // all unknown (by the factory!)
+	public static final int COPY_ALMOSTALL = 1 << 8; // almost all known (except HIST and TIME and textual)
+}
diff --git a/src/jogl/classes/jogamp/opengl/util/pngj/chunks/ChunkHelper.java b/src/jogl/classes/jogamp/opengl/util/pngj/chunks/ChunkHelper.java
new file mode 100644
index 000000000..26dafd4eb
--- /dev/null
+++ b/src/jogl/classes/jogamp/opengl/util/pngj/chunks/ChunkHelper.java
@@ -0,0 +1,134 @@
+package jogamp.opengl.util.pngj.chunks;
+
+// see http://www.libpng.org/pub/png/spec/1.2/PNG-Chunks.html
+// http://www.w3.org/TR/PNG/#5Chunk-naming-conventions
+// http://www.w3.org/TR/PNG/#table53
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.util.Set;
+import java.util.zip.DeflaterOutputStream;
+import java.util.zip.InflaterInputStream;
+
+import jogamp.opengl.util.pngj.PngHelper;
+import jogamp.opengl.util.pngj.PngjException;
+
+
+public class ChunkHelper {
+	public static final String IHDR = "IHDR";
+	public static final String PLTE = "PLTE";
+	public static final String IDAT = "IDAT";
+	public static final String IEND = "IEND";
+	public static final byte[] b_IHDR = toBytes(IHDR);
+	public static final byte[] b_PLTE = toBytes(PLTE);
+	public static final byte[] b_IDAT = toBytes(IDAT);
+	public static final byte[] b_IEND = toBytes(IEND);
+
+	public static final String cHRM = "cHRM";
+	public static final String gAMA = "gAMA";
+	public static final String iCCP = "iCCP";
+	public static final String sBIT = "sBIT";
+	public static final String sRGB = "sRGB";
+	public static final String bKGD = "bKGD";
+	public static final String hIST = "hIST";
+	public static final String tRNS = "tRNS";
+	public static final String pHYs = "pHYs";
+	public static final String sPLT = "sPLT";
+	public static final String tIME = "tIME";
+	public static final String iTXt = "iTXt";
+	public static final String tEXt = "tEXt";
+	public static final String zTXt = "zTXt";
+
+	public static Set<String> KNOWN_CHUNKS_CRITICAL = PngHelper.asSet(IHDR, PLTE, IDAT, IEND);
+
+	public static byte[] toBytes(String x) {
+		return x.getBytes(PngHelper.charsetLatin1);
+	}
+
+	public static String toString(byte[] x) {
+		return new String(x, PngHelper.charsetLatin1);
+	}
+
+	public static boolean isCritical(String id) { // critical chunk ?
+		// first letter is uppercase
+		return (Character.isUpperCase(id.charAt(0)));
+	}
+
+	public static boolean isPublic(String id) { // public chunk?
+		// second letter is uppercase
+		return (Character.isUpperCase(id.charAt(1)));
+	}
+
+	/**
+	 * "Unknown" just means that our chunk factory (even when it has been augmented by client code) did not recognize its id
+	 */
+	public static boolean isUnknown(PngChunk c) {
+		return c instanceof PngChunkUNKNOWN;
+	}
+
+	public static boolean isSafeToCopy(String id) { // safe to copy?
+		// fourth letter is lower case
+		return (!Character.isUpperCase(id.charAt(3)));
+	}
+
+	public static int posNullByte(byte[] b) {
+		for (int i = 0; i < b.length; i++)
+			if (b[i] == 0)
+				return i;
+		return -1;
+	}
+
+	public static boolean shouldLoad(String id, ChunkLoadBehaviour behav) {
+		if (isCritical(id))
+			return true;
+		boolean kwown = PngChunk.isKnown(id);
+		switch (behav) {
+		case LOAD_CHUNK_ALWAYS:
+			return true;
+		case LOAD_CHUNK_IF_SAFE:
+			return kwown || isSafeToCopy(id);
+		case LOAD_CHUNK_KNOWN:
+			return kwown;
+		case LOAD_CHUNK_NEVER:
+			return false;
+		}
+		return false; // should not reach here
+	}
+
+	public final static byte[] compressBytes(byte[] ori, boolean compress) {
+		return compressBytes(ori, 0, ori.length, compress);
+	}
+
+	public static byte[] compressBytes(byte[] ori, int offset, int len, boolean compress) {
+		try {
+			ByteArrayInputStream inb = new ByteArrayInputStream(ori, offset, len);
+			InputStream in = compress ? inb : new InflaterInputStream(inb);
+			ByteArrayOutputStream outb = new ByteArrayOutputStream();
+			OutputStream out = compress ? new DeflaterOutputStream(outb) : outb;
+			shovelInToOut(in, out);
+			in.close();
+			out.close();
+			return outb.toByteArray();
+		} catch (Exception e) {
+			throw new PngjException(e);
+		}
+	}
+
+	/**
+	 * Shovels all data from an input stream to an output stream.
+	 */
+	private static void shovelInToOut(InputStream in, OutputStream out) throws IOException {
+		byte[] buffer = new byte[1024];
+		int len;
+		while ((len = in.read(buffer)) > 0) {
+			out.write(buffer, 0, len);
+		}
+	}
+
+	public static boolean maskMatch(int v, int mask) {
+		return (v & mask) != 0;
+	}
+
+}
diff --git a/src/jogl/classes/jogamp/opengl/util/pngj/chunks/ChunkList.java b/src/jogl/classes/jogamp/opengl/util/pngj/chunks/ChunkList.java
new file mode 100644
index 000000000..badbbd0e8
--- /dev/null
+++ b/src/jogl/classes/jogamp/opengl/util/pngj/chunks/ChunkList.java
@@ -0,0 +1,282 @@
+package jogamp.opengl.util.pngj.chunks;
+
+import java.io.OutputStream;
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.LinkedHashSet;
+import java.util.List;
+import java.util.Set;
+
+import jogamp.opengl.util.pngj.ImageInfo;
+import jogamp.opengl.util.pngj.PngjException;
+
+
+/**
+ * All chunks that form an image, read or to be written
+ * 
+ * chunks include all chunks, but IDAT is a single pseudo chunk without data
+ **/
+public class ChunkList {
+	// ref: http://www.w3.org/TR/PNG/#table53
+	public static final int CHUNK_GROUP_0_IDHR = 0; // required - single
+	public static final int CHUNK_GROUP_1_AFTERIDHR = 1; // optional - multiple
+	public static final int CHUNK_GROUP_2_PLTE = 2; // optional - single
+	public static final int CHUNK_GROUP_3_AFTERPLTE = 3; // optional - multple
+	public static final int CHUNK_GROUP_4_IDAT = 4; // required (single pseudo chunk)
+	public static final int CHUNK_GROUP_5_AFTERIDAT = 5; // optional - multple
+	public static final int CHUNK_GROUP_6_END = 6; // only 1 chunk - requried
+
+	/**
+	 * All chunks, read, written (does not include IHDR, IDAT, END for written)
+	 */
+	private List<PngChunk> chunks = new ArrayList<PngChunk>();
+
+	/**
+	 * chunks not yet writen - does not include IHDR, IDAT, END, perhaps yes PLTE
+	 */
+	private Set<PngChunk> queuedChunks = new LinkedHashSet<PngChunk>();
+
+	final ImageInfo imageInfo; // only required for writing
+
+	public ChunkList(ImageInfo imfinfo) {
+		this.imageInfo = imfinfo;
+	}
+
+	/**
+	 * Adds chunk in next position. This is used when reading
+	 */
+	public void appendReadChunk(PngChunk chunk, int chunkGroup) {
+		chunk.setChunkGroup(chunkGroup);
+		chunks.add(chunk);
+	}
+
+	public List<PngChunk> getById(String id, boolean includeQueued, boolean includeProcessed) {
+		List<PngChunk> list = new ArrayList<PngChunk>();
+		if (includeQueued)
+			for (PngChunk c : queuedChunks)
+				if (c.id.equals(id))
+					list.add(c);
+		if (includeProcessed)
+			for (PngChunk c : chunks)
+				if (c.id.equals(id))
+					list.add(c);
+		return list;
+	}
+
+	/**
+	 * Remove Chunk: only from queued
+	 */
+	public boolean removeChunk(PngChunk c) {
+		return queuedChunks.remove(c);
+	}
+
+	/**
+	 * add chunk to write queue
+	 */
+	public void queueChunk(PngChunk chunk, boolean replace, boolean priority) {
+		chunk.setPriority(priority);
+		if (replace) {
+			List<PngChunk> current = getById(chunk.id, true, false);
+			for (PngChunk chunk2 : current)
+				removeChunk(chunk2);
+		}
+		queuedChunks.add(chunk);
+	}
+
+	/**
+	 * this should be called only for ancillary chunks and PLTE (groups 1 - 3 - 5)
+	 **/
+	private static boolean shouldWrite(PngChunk c, int currentGroup) {
+		if (currentGroup == CHUNK_GROUP_2_PLTE)
+			return c.id.equals(ChunkHelper.PLTE);
+		if (currentGroup % 2 == 0)
+			throw new RuntimeException("?");
+		int minChunkGroup, maxChunkGroup;
+		if (c.mustGoBeforePLTE())
+			minChunkGroup = maxChunkGroup = ChunkList.CHUNK_GROUP_1_AFTERIDHR;
+		else if (c.mustGoBeforeIDAT()) {
+			maxChunkGroup = ChunkList.CHUNK_GROUP_3_AFTERPLTE;
+			minChunkGroup = c.mustGoAfterPLTE() ? ChunkList.CHUNK_GROUP_3_AFTERPLTE : ChunkList.CHUNK_GROUP_1_AFTERIDHR;
+		} else {
+			maxChunkGroup = ChunkList.CHUNK_GROUP_5_AFTERIDAT;
+			minChunkGroup = ChunkList.CHUNK_GROUP_1_AFTERIDHR;
+		}
+
+		int preferred = maxChunkGroup;
+		if (c.isWritePriority())
+			preferred = minChunkGroup;
+		if (ChunkHelper.isUnknown(c) && c.getChunkGroup() > 0)
+			preferred = c.getChunkGroup();
+		if (currentGroup == preferred)
+			return true;
+		if (currentGroup > preferred && currentGroup <= maxChunkGroup)
+			return true;
+		return false;
+	}
+
+	public int writeChunks(OutputStream os, int currentGroup) {
+		int cont = 0;
+		Iterator<PngChunk> it = queuedChunks.iterator();
+		while (it.hasNext()) {
+			PngChunk c = it.next();
+			if (!shouldWrite(c, currentGroup))
+				continue;
+			c.write(os);
+			chunks.add(c);
+			c.setChunkGroup(currentGroup);
+			it.remove();
+			cont++;
+		}
+		return cont;
+	}
+
+	/**
+	 * returns a copy of processed (read or writen) chunks
+	 */
+	public List<PngChunk> getChunks() {
+		return new ArrayList<PngChunk>(chunks);
+	}
+
+	public List<String> getChunksUnkown() {
+		List<String> l = new ArrayList<String>();
+		for (PngChunk chunk : chunks)
+			if (ChunkHelper.isUnknown(chunk))
+				l.add(chunk.id);
+		return l;
+	}
+
+	/**
+	 * returns a copy of queued (for write) chunks
+	 */
+	public List<PngChunk> getQueuedChunks() {
+		return new ArrayList<PngChunk>(queuedChunks);
+	}
+
+	/**
+	 * behaviour:
+	 * 
+	 * a chunk already processed matches : exception a chunk queued matches and overwrite=true: replace it , return true
+	 * a chunk queued matches and overwrite=false: do nothing, return false no matching: set it, return true
+	 * 
+	 * @param c
+	 * @param overwriteIfPresent
+	 * @return true if added chunk
+	 */
+	public boolean setChunk(PngChunk c, boolean overwriteIfPresent) {
+		List<PngChunk> list = getMatching(c, false, true); // processed
+		if (!list.isEmpty())
+			throw new PngjException("chunk " + c.id + " already set ");
+		list = getMatching(c, true, false); // queued
+		if (!list.isEmpty()) {
+			if (overwriteIfPresent) {
+				for (PngChunk cx : list)
+					removeChunk(cx);
+				queueChunk(c, false, false);
+				return true;
+			}
+			return false;
+		}
+		queueChunk(c, false, false);
+		return true;
+	}
+
+	/**
+	 * returns only one chunk or null if nothing found - does not include queued
+	 * 
+	 * If innerid!=null , the chunk is assumed to be PngChunkTextVar or PngChunkSPLT, and filtered by that id
+	 * 
+	 * If more than one chunk (after filtering by inner id) is found, then an exception is thrown (failifMultiple=true)
+	 * or the last one is returned (failifMultiple=false)
+	 **/
+	public PngChunk getChunk1(String id, String innerid, boolean failIfMultiple) {
+		List<PngChunk> list = getChunks(id);
+		if (list.isEmpty())
+			return null;
+		if (innerid != null) {
+			List<PngChunk> list2 = new ArrayList<PngChunk>();
+			for (PngChunk c : list) {
+				if (c instanceof PngChunkTextVar)
+					if (((PngChunkTextVar) c).getKey().equals(innerid))
+						list2.add(c);
+				if (c instanceof PngChunkSPLT)
+					if (((PngChunkSPLT) c).getPalName().equals(innerid))
+						list2.add(c);
+			}
+			list = list2;
+		}
+		if (list.isEmpty())
+			return null;
+		if (list.size() > 1 && failIfMultiple)
+			throw new PngjException("unexpected multiple chunks id=" + id);
+		return list.get(list.size() - 1);
+	}
+
+	public PngChunk getChunk1(String id) {
+		return getChunk1(id, null, true);
+	}
+	
+	public List<PngChunk> getChunks(String id) { // not including queued
+		return getById(id, false, true);
+	}
+
+	private List<PngChunk> getMatching(PngChunk cnew, boolean includeQueued, boolean includeProcessed) {
+		List<PngChunk> list = new ArrayList<PngChunk>();
+		if (includeQueued)
+			for (PngChunk c : getQueuedChunks())
+				if (matches(cnew, c))
+					list.add(c);
+		if (includeProcessed)
+			for (PngChunk c : getChunks())
+				if (matches(cnew, c))
+					list.add(c);
+		return list;
+	}
+
+	/**
+	 * MY adhoc criteria: two chunks "match" if they have same id and (perhaps, if multiple are allowed) if the match
+	 * also in some "internal key" (eg: key for string values, palette for sPLT, etc)
+	 * 
+	 * @return true if "matches"
+	 */
+	public static boolean matches(PngChunk c2, PngChunk c1) {
+		if (c1 == null || c2 == null || !c1.id.equals(c2.id))
+			return false;
+		// same id
+		if (c1.getClass() != c2.getClass())
+			return false; // should not happen
+		if (!c2.allowsMultiple())
+			return true;
+		if (c1 instanceof PngChunkTextVar) {
+			return ((PngChunkTextVar) c1).getKey().equals(((PngChunkTextVar) c2).getKey());
+		}
+		if (c1 instanceof PngChunkSPLT) {
+			return ((PngChunkSPLT) c1).getPalName().equals(((PngChunkSPLT) c2).getPalName());
+		}
+		// unknown chunks that allow multiple? consider they don't match
+		return false;
+	}
+	
+	public String toString() {
+		return "ChunkList: processed: " + chunks.size() + " queue: " + queuedChunks.size();
+	}
+
+	/**
+	 * for debugging
+	 */
+	public String toStringFull() {
+		StringBuilder sb = new StringBuilder(toString());
+		sb.append("\n Processed:\n");
+		for (PngChunk chunk : chunks) {
+			sb.append(chunk).append(" G=" + chunk.getChunkGroup() + "\n");
+		}
+		if (!queuedChunks.isEmpty()) {
+			sb.append(" Queued:\n");
+			for (PngChunk chunk : chunks) {
+				sb.append(chunk).append("\n");
+			}
+
+		}
+		return sb.toString();
+	}
+
+}
diff --git a/src/jogl/classes/jogamp/opengl/util/pngj/chunks/ChunkLoadBehaviour.java b/src/jogl/classes/jogamp/opengl/util/pngj/chunks/ChunkLoadBehaviour.java
new file mode 100644
index 000000000..a3f85355c
--- /dev/null
+++ b/src/jogl/classes/jogamp/opengl/util/pngj/chunks/ChunkLoadBehaviour.java
@@ -0,0 +1,10 @@
+package jogamp.opengl.util.pngj.chunks;
+
+public enum ChunkLoadBehaviour {
+	// what to do with non critical chunks when reading?
+	LOAD_CHUNK_NEVER, /* ignore non-critical chunks */
+	LOAD_CHUNK_KNOWN, /* load chunk if 'known' */
+	LOAD_CHUNK_IF_SAFE, /* load chunk if 'known' or safe to copy */
+	LOAD_CHUNK_ALWAYS /* load chunk always */
+	;
+}
diff --git a/src/jogl/classes/jogamp/opengl/util/pngj/chunks/ChunkRaw.java b/src/jogl/classes/jogamp/opengl/util/pngj/chunks/ChunkRaw.java
new file mode 100644
index 000000000..6770d5e95
--- /dev/null
+++ b/src/jogl/classes/jogamp/opengl/util/pngj/chunks/ChunkRaw.java
@@ -0,0 +1,83 @@
+package jogamp.opengl.util.pngj.chunks;
+
+import java.io.ByteArrayInputStream;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.util.zip.CRC32;
+
+import jogamp.opengl.util.pngj.PngHelper;
+import jogamp.opengl.util.pngj.PngjBadCrcException;
+import jogamp.opengl.util.pngj.PngjOutputException;
+
+
+/**
+ * Wraps the raw chunk data Short lived object, to be created while serialing/deserializing Do not reuse it for
+ * different chunks
+ * 
+ * see http://www.libpng.org/pub/png/spec/1.2/PNG-Chunks.html
+ */
+public class ChunkRaw {
+	public final int len;
+	public final byte[] idbytes = new byte[4]; // 4 bytes
+	public byte[] data = null; // crc not included
+	private int crcval = 0;
+
+	// public int offset=-1; // only for read chunks - informational
+	public ChunkRaw(int len, byte[] idbytes, boolean alloc) {
+		this.len = len;
+		System.arraycopy(idbytes, 0, this.idbytes, 0, 4);
+		if (alloc)
+			allocData();
+	}
+
+	public void writeChunk(OutputStream os) {
+		if (idbytes.length != 4)
+			throw new PngjOutputException("bad chunkid [" + ChunkHelper.toString(idbytes) + "]");
+		computeCrc();
+		PngHelper.writeInt4(os, len);
+		PngHelper.writeBytes(os, idbytes);
+		if (len > 0)
+			PngHelper.writeBytes(os, data, 0, len);
+		// System.err.println("writing chunk " + this.toString() + "crc=" + crcval);
+
+		PngHelper.writeInt4(os, crcval);
+	}
+
+	/**
+	 * called after setting data, before writing to os
+	 */
+	private void computeCrc() {
+		CRC32 crcengine = PngHelper.getCRC();
+		crcengine.reset();
+		crcengine.update(idbytes, 0, 4);
+		if (len > 0)
+			crcengine.update(data, 0, len); //
+		crcval = (int) crcengine.getValue();
+	}
+
+	public String toString() {
+		return "chunkid=" + ChunkHelper.toString(idbytes) + " len=" + len;
+	}
+
+	/**
+	 * position before: just after chunk id. positon after: after crc Data should be already allocated. Checks CRC
+	 * Return number of byte read.
+	 */
+	public int readChunkData(InputStream is) {
+		PngHelper.readBytes(is, data, 0, len);
+		int crcori = PngHelper.readInt4(is);
+		computeCrc();
+		if (crcori != crcval)
+			throw new PngjBadCrcException("crc invalid for chunk " + toString() + " calc=" + crcval + " read=" + crcori);
+		return len + 4;
+	}
+
+	public ByteArrayInputStream getAsByteStream() { // only the data
+		return new ByteArrayInputStream(data);
+	}
+
+	private void allocData() {
+		if (data == null || data.length < len)
+			data = new byte[len];
+	}
+}
diff --git a/src/jogl/classes/jogamp/opengl/util/pngj/chunks/PngChunk.java b/src/jogl/classes/jogamp/opengl/util/pngj/chunks/PngChunk.java
new file mode 100644
index 000000000..2df9fd1f3
--- /dev/null
+++ b/src/jogl/classes/jogamp/opengl/util/pngj/chunks/PngChunk.java
@@ -0,0 +1,152 @@
+package jogamp.opengl.util.pngj.chunks;
+
+import java.io.OutputStream;
+import java.lang.reflect.Constructor;
+import java.util.HashMap;
+import java.util.Map;
+
+import jogamp.opengl.util.pngj.ImageInfo;
+import jogamp.opengl.util.pngj.PngjException;
+
+
+// see http://www.libpng.org/pub/png/spec/1.2/PNG-Chunks.html
+public abstract class PngChunk {
+
+	public final String id; // 4 letters
+	public final boolean crit, pub, safe;
+	private int lenori = -1; // merely informational, for read chunks
+
+	private boolean writePriority = false; // for queued chunks
+	protected final ImageInfo imgInfo;
+
+	private int chunkGroup = -1; // chunk group where it was read or writen
+
+	/**
+	 * This static map defines which PngChunk class correspond to which ChunkID The client can add other chunks to this
+	 * map statically, before reading
+	 */
+	public final static Map<String, Class<? extends PngChunk>> factoryMap = new HashMap<String, Class<? extends PngChunk>>();
+	static {
+		factoryMap.put(ChunkHelper.IDAT, PngChunkIDAT.class);
+		factoryMap.put(ChunkHelper.IHDR, PngChunkIHDR.class);
+		factoryMap.put(ChunkHelper.PLTE, PngChunkPLTE.class);
+		factoryMap.put(ChunkHelper.IEND, PngChunkIEND.class);
+		factoryMap.put(ChunkHelper.tEXt, PngChunkTEXT.class);
+		factoryMap.put(ChunkHelper.iTXt, PngChunkITXT.class);
+		factoryMap.put(ChunkHelper.zTXt, PngChunkZTXT.class);
+		factoryMap.put(ChunkHelper.bKGD, PngChunkBKGD.class);
+		factoryMap.put(ChunkHelper.gAMA, PngChunkGAMA.class);
+		factoryMap.put(ChunkHelper.pHYs, PngChunkPHYS.class);
+		factoryMap.put(ChunkHelper.iCCP, PngChunkICCP.class);
+		factoryMap.put(ChunkHelper.tIME, PngChunkTIME.class);
+		factoryMap.put(ChunkHelper.tRNS, PngChunkTRNS.class);
+		factoryMap.put(ChunkHelper.cHRM, PngChunkCHRM.class);
+		factoryMap.put(ChunkHelper.sBIT, PngChunkSBIT.class);
+		factoryMap.put(ChunkHelper.sRGB, PngChunkSRGB.class);
+		factoryMap.put(ChunkHelper.hIST, PngChunkHIST.class);
+		factoryMap.put(ChunkHelper.sPLT, PngChunkSPLT.class);
+	}
+
+	protected PngChunk(String id, ImageInfo imgInfo) {
+		this.id = id;
+		this.imgInfo = imgInfo;
+		this.crit = ChunkHelper.isCritical(id);
+		this.pub = ChunkHelper.isPublic(id);
+		this.safe = ChunkHelper.isSafeToCopy(id);
+	}
+
+	public abstract ChunkRaw createChunk();
+
+	public abstract void parseFromChunk(ChunkRaw c);
+
+	// override to make deep copy from read data to write
+	public abstract void cloneDataFromRead(PngChunk other);
+
+	@SuppressWarnings("unchecked")
+	public static <T extends PngChunk> T cloneChunk(T chunk, ImageInfo info) {
+		PngChunk cn = factoryFromId(chunk.id, info);
+		if (cn.getClass() != chunk.getClass())
+			throw new PngjException("bad class cloning chunk: " + cn.getClass() + " " + chunk.getClass());
+		cn.cloneDataFromRead(chunk);
+		return (T) cn;
+	}
+
+	public static PngChunk factory(ChunkRaw chunk, ImageInfo info) {
+		PngChunk c = factoryFromId(ChunkHelper.toString(chunk.idbytes), info);
+		c.lenori = chunk.len;
+		c.parseFromChunk(chunk);
+		return c;
+	}
+
+	public static PngChunk factoryFromId(String cid, ImageInfo info) {
+		PngChunk chunk = null;
+		try {
+			Class<? extends PngChunk> cla = factoryMap.get(cid);
+			if (cla != null) {
+				Constructor<? extends PngChunk> constr = cla.getConstructor(ImageInfo.class);
+				chunk = constr.newInstance(info);
+			}
+		} catch (Exception e) {
+			// this can happend for unkown chunks
+		}
+		if (chunk == null)
+			chunk = new PngChunkUNKNOWN(cid, info);
+		return chunk;
+	}
+
+	protected ChunkRaw createEmptyChunk(int len, boolean alloc) {
+		ChunkRaw c = new ChunkRaw(len, ChunkHelper.toBytes(id), alloc);
+		return c;
+	}
+
+	@Override
+	public String toString() {
+		return "chunk id= " + id + " (" + lenori + ") c=" + getClass().getSimpleName();
+	}
+
+	void setPriority(boolean highPrioriy) {
+		writePriority = highPrioriy;
+	}
+
+	void write(OutputStream os) {
+		ChunkRaw c = createChunk();
+		if (c == null)
+			throw new PngjException("null chunk ! creation failed for " + this);
+		c.writeChunk(os);
+	}
+
+	public boolean isWritePriority() {
+		return writePriority;
+	}
+
+	/** must be overriden - only relevant for ancillary chunks */
+	public boolean allowsMultiple() {
+		return false; // override if allows multiple ocurrences
+	}
+
+	/** mustGoBeforeXX/After must be overriden - only relevant for ancillary chunks */
+	public boolean mustGoBeforeIDAT() {
+		return false;
+	}
+
+	public boolean mustGoBeforePLTE() {
+		return false;
+	}
+
+	public boolean mustGoAfterPLTE() {
+		return false;
+	}
+
+	static boolean isKnown(String id) {
+		return factoryMap.containsKey(id);
+	}
+
+	public int getChunkGroup() {
+		return chunkGroup;
+	}
+
+	public void setChunkGroup(int chunkGroup) {
+		this.chunkGroup = chunkGroup;
+	}
+
+}
diff --git a/src/jogl/classes/jogamp/opengl/util/pngj/chunks/PngChunkBKGD.java b/src/jogl/classes/jogamp/opengl/util/pngj/chunks/PngChunkBKGD.java
new file mode 100644
index 000000000..51bbcb832
--- /dev/null
+++ b/src/jogl/classes/jogamp/opengl/util/pngj/chunks/PngChunkBKGD.java
@@ -0,0 +1,122 @@
+package jogamp.opengl.util.pngj.chunks;
+
+import jogamp.opengl.util.pngj.ImageInfo;
+import jogamp.opengl.util.pngj.PngHelper;
+import jogamp.opengl.util.pngj.PngjException;
+
+/*
+ */
+public class PngChunkBKGD extends PngChunk {
+	// http://www.w3.org/TR/PNG/#11bKGD
+	// this chunk structure depends on the image type
+	// only one of these is meaningful
+	private int gray;
+	private int red, green, blue;
+	private int paletteIndex;
+
+	public PngChunkBKGD(ImageInfo info) {
+		super(ChunkHelper.bKGD, info);
+	}
+
+	@Override
+	public boolean mustGoBeforeIDAT() {
+		return true;
+	}
+
+	@Override
+	public boolean mustGoAfterPLTE() {
+		return true;
+	}
+
+	@Override
+	public ChunkRaw createChunk() {
+		ChunkRaw c = null;
+		if (imgInfo.greyscale) {
+			c = createEmptyChunk(2, true);
+			PngHelper.writeInt2tobytes(gray, c.data, 0);
+		} else if (imgInfo.indexed) {
+			c = createEmptyChunk(1, true);
+			c.data[0] = (byte) paletteIndex;
+		} else {
+			c = createEmptyChunk(6, true);
+			PngHelper.writeInt2tobytes(red, c.data, 0);
+			PngHelper.writeInt2tobytes(green, c.data, 0);
+			PngHelper.writeInt2tobytes(blue, c.data, 0);
+		}
+		return c;
+	}
+
+	@Override
+	public void parseFromChunk(ChunkRaw c) {
+		if (imgInfo.greyscale) {
+			gray = PngHelper.readInt2fromBytes(c.data, 0);
+		} else if (imgInfo.indexed) {
+			paletteIndex = (int) (c.data[0] & 0xff);
+		} else {
+			red = PngHelper.readInt2fromBytes(c.data, 0);
+			green = PngHelper.readInt2fromBytes(c.data, 2);
+			blue = PngHelper.readInt2fromBytes(c.data, 4);
+		}
+	}
+
+	@Override
+	public void cloneDataFromRead(PngChunk other) {
+		PngChunkBKGD otherx = (PngChunkBKGD) other;
+		gray = otherx.gray;
+		red = otherx.red;
+		green = otherx.red;
+		blue = otherx.red;
+		paletteIndex = otherx.paletteIndex;
+	}
+
+	/**
+	 * Set gray value (0-255 if bitdept=8)
+	 * 
+	 * @param gray
+	 */
+	public void setGray(int gray) {
+		if (!imgInfo.greyscale)
+			throw new PngjException("only gray images support this");
+		this.gray = gray;
+	}
+
+	public int getGray() {
+		if (!imgInfo.greyscale)
+			throw new PngjException("only gray images support this");
+		return gray;
+	}
+
+	/**
+	 * Set pallette index
+	 * 
+	 */
+	public void setPaletteIndex(int i) {
+		if (!imgInfo.indexed)
+			throw new PngjException("only indexed (pallete) images support this");
+		this.paletteIndex = i;
+	}
+
+	public int getPaletteIndex() {
+		if (!imgInfo.indexed)
+			throw new PngjException("only indexed (pallete) images support this");
+		return paletteIndex;
+	}
+
+	/**
+	 * Set rgb values
+	 * 
+	 */
+	public void setRGB(int r, int g, int b) {
+		if (imgInfo.greyscale || imgInfo.indexed)
+			throw new PngjException("only rgb or rgba images support this");
+		red = r;
+		green = g;
+		blue = b;
+	}
+
+	public int[] getRGB() {
+		if (imgInfo.greyscale || imgInfo.indexed)
+			throw new PngjException("only rgb or rgba images support this");
+		return new int[] { red, green, blue };
+	}
+}
diff --git a/src/jogl/classes/jogamp/opengl/util/pngj/chunks/PngChunkCHRM.java b/src/jogl/classes/jogamp/opengl/util/pngj/chunks/PngChunkCHRM.java
new file mode 100644
index 000000000..4380761c7
--- /dev/null
+++ b/src/jogl/classes/jogamp/opengl/util/pngj/chunks/PngChunkCHRM.java
@@ -0,0 +1,88 @@
+package jogamp.opengl.util.pngj.chunks;
+
+import jogamp.opengl.util.pngj.ImageInfo;
+import jogamp.opengl.util.pngj.PngHelper;
+import jogamp.opengl.util.pngj.PngjException;
+
+/*
+ */
+public class PngChunkCHRM extends PngChunk {
+	// http://www.w3.org/TR/PNG/#11cHRM
+	private double whitex, whitey;
+	private double redx, redy;
+	private double greenx, greeny;
+	private double bluex, bluey;
+
+	public PngChunkCHRM(ImageInfo info) {
+		super(ChunkHelper.cHRM, info);
+	}
+
+	@Override
+	public boolean mustGoBeforeIDAT() {
+		return true;
+	}
+
+	@Override
+	public boolean mustGoBeforePLTE() {
+		return true;
+	}
+
+	@Override
+	public ChunkRaw createChunk() {
+		ChunkRaw c = null;
+		c = createEmptyChunk(32, true);
+		PngHelper.writeInt4tobytes(PngHelper.doubleToInt100000(whitex), c.data, 0);
+		PngHelper.writeInt4tobytes(PngHelper.doubleToInt100000(whitey), c.data, 4);
+		PngHelper.writeInt4tobytes(PngHelper.doubleToInt100000(redx), c.data, 8);
+		PngHelper.writeInt4tobytes(PngHelper.doubleToInt100000(redy), c.data, 12);
+		PngHelper.writeInt4tobytes(PngHelper.doubleToInt100000(greenx), c.data, 16);
+		PngHelper.writeInt4tobytes(PngHelper.doubleToInt100000(greeny), c.data, 20);
+		PngHelper.writeInt4tobytes(PngHelper.doubleToInt100000(bluex), c.data, 24);
+		PngHelper.writeInt4tobytes(PngHelper.doubleToInt100000(bluey), c.data, 28);
+		return c;
+	}
+
+	@Override
+	public void parseFromChunk(ChunkRaw c) {
+		if (c.len != 32)
+			throw new PngjException("bad chunk " + c);
+		whitex = PngHelper.intToDouble100000(PngHelper.readInt4fromBytes(c.data, 0));
+		whitey = PngHelper.intToDouble100000(PngHelper.readInt4fromBytes(c.data, 4));
+		redx = PngHelper.intToDouble100000(PngHelper.readInt4fromBytes(c.data, 8));
+		redy = PngHelper.intToDouble100000(PngHelper.readInt4fromBytes(c.data, 12));
+		greenx = PngHelper.intToDouble100000(PngHelper.readInt4fromBytes(c.data, 16));
+		greeny = PngHelper.intToDouble100000(PngHelper.readInt4fromBytes(c.data, 20));
+		bluex = PngHelper.intToDouble100000(PngHelper.readInt4fromBytes(c.data, 24));
+		bluey = PngHelper.intToDouble100000(PngHelper.readInt4fromBytes(c.data, 28));
+	}
+
+	@Override
+	public void cloneDataFromRead(PngChunk other) {
+		PngChunkCHRM otherx = (PngChunkCHRM) other;
+		whitex = otherx.whitex;
+		whitey = otherx.whitex;
+		redx = otherx.redx;
+		redy = otherx.redy;
+		greenx = otherx.greenx;
+		greeny = otherx.greeny;
+		bluex = otherx.bluex;
+		bluey = otherx.bluey;
+	}
+
+	public void setChromaticities(double whitex, double whitey, double redx, double redy, double greenx, double greeny,
+			double bluex, double bluey) {
+		this.whitex = whitex;
+		this.redx = redx;
+		this.greenx = greenx;
+		this.bluex = bluex;
+		this.whitey = whitey;
+		this.redy = redy;
+		this.greeny = greeny;
+		this.bluey = bluey;
+	}
+
+	public double[] getChromaticities() {
+		return new double[] { whitex, whitey, redx, redy, greenx, greeny, bluex, bluey };
+	}
+
+}
diff --git a/src/jogl/classes/jogamp/opengl/util/pngj/chunks/PngChunkGAMA.java b/src/jogl/classes/jogamp/opengl/util/pngj/chunks/PngChunkGAMA.java
new file mode 100644
index 000000000..184ee9ffa
--- /dev/null
+++ b/src/jogl/classes/jogamp/opengl/util/pngj/chunks/PngChunkGAMA.java
@@ -0,0 +1,56 @@
+package jogamp.opengl.util.pngj.chunks;
+
+import jogamp.opengl.util.pngj.ImageInfo;
+import jogamp.opengl.util.pngj.PngHelper;
+import jogamp.opengl.util.pngj.PngjException;
+
+/*
+ */
+public class PngChunkGAMA extends PngChunk {
+	// http://www.w3.org/TR/PNG/#11gAMA
+	private double gamma;
+
+	public PngChunkGAMA(ImageInfo info) {
+		super(ChunkHelper.gAMA, info);
+	}
+
+	@Override
+	public boolean mustGoBeforeIDAT() {
+		return true;
+	}
+
+	@Override
+	public boolean mustGoBeforePLTE() {
+		return true;
+	}
+
+	@Override
+	public ChunkRaw createChunk() {
+		ChunkRaw c = createEmptyChunk(4, true);
+		int g = (int) (gamma * 100000 + 0.5);
+		PngHelper.writeInt4tobytes(g, c.data, 0);
+		return c;
+	}
+
+	@Override
+	public void parseFromChunk(ChunkRaw chunk) {
+		if (chunk.len != 4)
+			throw new PngjException("bad chunk " + chunk);
+		int g = PngHelper.readInt4fromBytes(chunk.data, 0);
+		gamma = ((double) g) / 100000.0;
+	}
+
+	@Override
+	public void cloneDataFromRead(PngChunk other) {
+		gamma = ((PngChunkGAMA) other).gamma;
+	}
+
+	public double getGamma() {
+		return gamma;
+	}
+
+	public void setGamma(double gamma) {
+		this.gamma = gamma;
+	}
+
+}
diff --git a/src/jogl/classes/jogamp/opengl/util/pngj/chunks/PngChunkHIST.java b/src/jogl/classes/jogamp/opengl/util/pngj/chunks/PngChunkHIST.java
new file mode 100644
index 000000000..b0f02ea37
--- /dev/null
+++ b/src/jogl/classes/jogamp/opengl/util/pngj/chunks/PngChunkHIST.java
@@ -0,0 +1,67 @@
+package jogamp.opengl.util.pngj.chunks;
+
+import jogamp.opengl.util.pngj.ImageInfo;
+import jogamp.opengl.util.pngj.PngHelper;
+import jogamp.opengl.util.pngj.PngjException;
+
+/*
+ */
+public class PngChunkHIST extends PngChunk {
+	// http://www.w3.org/TR/PNG/#11hIST
+	// only for palette images
+
+	private int[] hist = new int[0]; // should have same lenght as palette
+
+	public PngChunkHIST(ImageInfo info) {
+		super(ChunkHelper.hIST, info);
+	}
+
+	@Override
+	public boolean mustGoBeforeIDAT() {
+		return true;
+	}
+
+	@Override
+	public boolean mustGoAfterPLTE() {
+		return true;
+	}
+
+	@Override
+	public void parseFromChunk(ChunkRaw c) {
+		if (!imgInfo.indexed)
+			throw new PngjException("only indexed images accept a HIST chunk");
+		int nentries = c.data.length / 2;
+		hist = new int[nentries];
+		for (int i = 0; i < hist.length; i++) {
+			hist[i] = PngHelper.readInt2fromBytes(c.data, i * 2);
+		}
+	}
+
+	@Override
+	public ChunkRaw createChunk() {
+		if (!imgInfo.indexed)
+			throw new PngjException("only indexed images accept a HIST chunk");
+		ChunkRaw c = null;
+		c = createEmptyChunk(hist.length * 2, true);
+		for (int i = 0; i < hist.length; i++) {
+			PngHelper.writeInt2tobytes(hist[i], c.data, i * 2);
+		}
+		return c;
+	}
+
+	@Override
+	public void cloneDataFromRead(PngChunk other) {
+		PngChunkHIST otherx = (PngChunkHIST) other;
+		hist = new int[otherx.hist.length];
+		System.arraycopy(otherx.hist, 0, hist, 0, otherx.hist.length);
+	}
+
+	public int[] getHist() {
+		return hist;
+	}
+
+	public void setHist(int[] hist) {
+		this.hist = hist;
+	}
+
+}
diff --git a/src/jogl/classes/jogamp/opengl/util/pngj/chunks/PngChunkICCP.java b/src/jogl/classes/jogamp/opengl/util/pngj/chunks/PngChunkICCP.java
new file mode 100644
index 000000000..db1c1ba64
--- /dev/null
+++ b/src/jogl/classes/jogamp/opengl/util/pngj/chunks/PngChunkICCP.java
@@ -0,0 +1,85 @@
+package jogamp.opengl.util.pngj.chunks;
+
+import jogamp.opengl.util.pngj.ImageInfo;
+import jogamp.opengl.util.pngj.PngHelper;
+
+/*
+ */
+public class PngChunkICCP extends PngChunk {
+	// http://www.w3.org/TR/PNG/#11iCCP
+	private String profileName;
+	private byte[] compressedProfile; // copmression/decopmresion is done in getter/setter
+
+	public PngChunkICCP(ImageInfo info) {
+		super(ChunkHelper.iCCP, info);
+	}
+
+	@Override
+	public boolean mustGoBeforeIDAT() {
+		return true;
+	}
+
+	@Override
+	public boolean mustGoBeforePLTE() {
+		return true;
+	}
+
+	@Override
+	public ChunkRaw createChunk() {
+		ChunkRaw c = createEmptyChunk(profileName.length() + compressedProfile.length + 2, true);
+		System.arraycopy(ChunkHelper.toBytes(profileName), 0, c.data, 0, profileName.length());
+		c.data[profileName.length()] = 0;
+		c.data[profileName.length() + 1] = 0;
+		System.arraycopy(compressedProfile, 0, c.data, profileName.length() + 2, compressedProfile.length);
+		return c;
+	}
+
+	@Override
+	public void parseFromChunk(ChunkRaw chunk) {
+		int pos0 = ChunkHelper.posNullByte(chunk.data);
+		profileName = new String(chunk.data, 0, pos0, PngHelper.charsetLatin1);
+		int comp = (chunk.data[pos0 + 1] & 0xff);
+		if (comp != 0)
+			throw new RuntimeException("bad compression for ChunkTypeICCP");
+		int compdatasize = chunk.data.length - (pos0 + 2);
+		compressedProfile = new byte[compdatasize];
+		System.arraycopy(chunk.data, pos0 + 2, compressedProfile, 0, compdatasize);
+	}
+
+	@Override
+	public void cloneDataFromRead(PngChunk other) {
+		PngChunkICCP otherx = (PngChunkICCP) other;
+		profileName = otherx.profileName;
+		compressedProfile = new byte[otherx.compressedProfile.length];
+		System.arraycopy(otherx.compressedProfile, 0, compressedProfile, 0, otherx.compressedProfile.length); // deep
+																												// copy
+	}
+
+	/**
+	 * The profile should be uncompressed bytes
+	 */
+	public void setProfileNameAndContent(String name, byte[] profile) {
+		profileName = name;
+		compressedProfile = ChunkHelper.compressBytes(profile, true);
+	}
+
+	public void setProfileNameAndContent(String name, String profile) {
+		setProfileNameAndContent(name, profile.getBytes(PngHelper.charsetLatin1));
+	}
+
+	public String getProfileName() {
+		return profileName;
+	}
+
+	/**
+	 * uncompressed
+	 **/
+	public byte[] getProfile() {
+		return ChunkHelper.compressBytes(compressedProfile, false);
+	}
+
+	public String getProfileAsString() {
+		return new String(getProfile(), PngHelper.charsetLatin1);
+	}
+
+}
diff --git a/src/jogl/classes/jogamp/opengl/util/pngj/chunks/PngChunkIDAT.java b/src/jogl/classes/jogamp/opengl/util/pngj/chunks/PngChunkIDAT.java
new file mode 100644
index 000000000..a7cb95dbf
--- /dev/null
+++ b/src/jogl/classes/jogamp/opengl/util/pngj/chunks/PngChunkIDAT.java
@@ -0,0 +1,25 @@
+package jogamp.opengl.util.pngj.chunks;
+
+import jogamp.opengl.util.pngj.ImageInfo;
+
+public class PngChunkIDAT extends PngChunk {
+	// http://www.w3.org/TR/PNG/#11IDAT
+	// This is dummy placeholder - we write/read this chunk (actually several)
+	// by special code.
+	public PngChunkIDAT(ImageInfo i) {
+		super(ChunkHelper.IDAT, i);
+	}
+
+	@Override
+	public ChunkRaw createChunk() {// does nothing
+		return null;
+	}
+
+	@Override
+	public void parseFromChunk(ChunkRaw c) { // does nothing
+	}
+
+	@Override
+	public void cloneDataFromRead(PngChunk other) {
+	}
+}
diff --git a/src/jogl/classes/jogamp/opengl/util/pngj/chunks/PngChunkIEND.java b/src/jogl/classes/jogamp/opengl/util/pngj/chunks/PngChunkIEND.java
new file mode 100644
index 000000000..0d5b266da
--- /dev/null
+++ b/src/jogl/classes/jogamp/opengl/util/pngj/chunks/PngChunkIEND.java
@@ -0,0 +1,26 @@
+package jogamp.opengl.util.pngj.chunks;
+
+import jogamp.opengl.util.pngj.ImageInfo;
+
+public class PngChunkIEND extends PngChunk {
+	// http://www.w3.org/TR/PNG/#11IEND
+	// this is a dummy placeholder
+	public PngChunkIEND(ImageInfo info) {
+		super(ChunkHelper.IEND, info);
+	}
+
+	@Override
+	public ChunkRaw createChunk() {
+		ChunkRaw c = new ChunkRaw(0, ChunkHelper.b_IEND, false);
+		return c;
+	}
+
+	@Override
+	public void parseFromChunk(ChunkRaw c) {
+		// this is not used
+	}
+
+	@Override
+	public void cloneDataFromRead(PngChunk other) {
+	}
+}
diff --git a/src/jogl/classes/jogamp/opengl/util/pngj/chunks/PngChunkIHDR.java b/src/jogl/classes/jogamp/opengl/util/pngj/chunks/PngChunkIHDR.java
new file mode 100644
index 000000000..fcb4150ff
--- /dev/null
+++ b/src/jogl/classes/jogamp/opengl/util/pngj/chunks/PngChunkIHDR.java
@@ -0,0 +1,126 @@
+package jogamp.opengl.util.pngj.chunks;
+
+import java.io.ByteArrayInputStream;
+
+import jogamp.opengl.util.pngj.ImageInfo;
+import jogamp.opengl.util.pngj.PngHelper;
+import jogamp.opengl.util.pngj.PngjException;
+
+
+/**
+ * this is a special chunk!
+ */
+public class PngChunkIHDR extends PngChunk {
+	private int cols;
+	private int rows;
+	private int bitspc;
+	private int colormodel;
+	private int compmeth;
+	private int filmeth;
+	private int interlaced;
+
+	// http://www.w3.org/TR/PNG/#11IHDR
+	//
+	public PngChunkIHDR(ImageInfo info) {
+		super(ChunkHelper.IHDR, info);
+	}
+
+	@Override
+	public ChunkRaw createChunk() {
+		ChunkRaw c = new ChunkRaw(13, ChunkHelper.b_IHDR, true);
+		int offset = 0;
+		PngHelper.writeInt4tobytes(cols, c.data, offset);
+		offset += 4;
+		PngHelper.writeInt4tobytes(rows, c.data, offset);
+		offset += 4;
+		c.data[offset++] = (byte) bitspc;
+		c.data[offset++] = (byte) colormodel;
+		c.data[offset++] = (byte) compmeth;
+		c.data[offset++] = (byte) filmeth;
+		c.data[offset++] = (byte) interlaced;
+		return c;
+	}
+
+	@Override
+	public void parseFromChunk(ChunkRaw c) {
+		if (c.len != 13)
+			throw new PngjException("Bad IDHR len " + c.len);
+		ByteArrayInputStream st = c.getAsByteStream();
+		cols = PngHelper.readInt4(st);
+		rows = PngHelper.readInt4(st);
+		// bit depth: number of bits per channel
+		bitspc = PngHelper.readByte(st);
+		colormodel = PngHelper.readByte(st);
+		compmeth = PngHelper.readByte(st);
+		filmeth = PngHelper.readByte(st);
+		interlaced = PngHelper.readByte(st);
+	}
+
+	@Override
+	public void cloneDataFromRead(PngChunk other) {
+		PngChunkIHDR otherx = (PngChunkIHDR) other;
+		cols = otherx.cols;
+		rows = otherx.rows;
+		bitspc = otherx.bitspc;
+		colormodel = otherx.colormodel;
+		compmeth = otherx.compmeth;
+		filmeth = otherx.filmeth;
+		interlaced = otherx.interlaced;
+	}
+
+	public int getCols() {
+		return cols;
+	}
+
+	public void setCols(int cols) {
+		this.cols = cols;
+	}
+
+	public int getRows() {
+		return rows;
+	}
+
+	public void setRows(int rows) {
+		this.rows = rows;
+	}
+
+	public int getBitspc() {
+		return bitspc;
+	}
+
+	public void setBitspc(int bitspc) {
+		this.bitspc = bitspc;
+	}
+
+	public int getColormodel() {
+		return colormodel;
+	}
+
+	public void setColormodel(int colormodel) {
+		this.colormodel = colormodel;
+	}
+
+	public int getCompmeth() {
+		return compmeth;
+	}
+
+	public void setCompmeth(int compmeth) {
+		this.compmeth = compmeth;
+	}
+
+	public int getFilmeth() {
+		return filmeth;
+	}
+
+	public void setFilmeth(int filmeth) {
+		this.filmeth = filmeth;
+	}
+
+	public int getInterlaced() {
+		return interlaced;
+	}
+
+	public void setInterlaced(int interlaced) {
+		this.interlaced = interlaced;
+	}
+}
diff --git a/src/jogl/classes/jogamp/opengl/util/pngj/chunks/PngChunkITXT.java b/src/jogl/classes/jogamp/opengl/util/pngj/chunks/PngChunkITXT.java
new file mode 100644
index 000000000..4e5c7c74a
--- /dev/null
+++ b/src/jogl/classes/jogamp/opengl/util/pngj/chunks/PngChunkITXT.java
@@ -0,0 +1,119 @@
+package jogamp.opengl.util.pngj.chunks;
+
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+
+import jogamp.opengl.util.pngj.ImageInfo;
+import jogamp.opengl.util.pngj.PngHelper;
+import jogamp.opengl.util.pngj.PngjException;
+
+
+/**
+ * UNTESTED!
+ */
+public class PngChunkITXT extends PngChunkTextVar {
+
+	private boolean compressed = false;
+	private String langTag = "";
+	private String translatedTag = "";
+
+	// http://www.w3.org/TR/PNG/#11iTXt
+	public PngChunkITXT(ImageInfo info) {
+		super(ChunkHelper.iTXt, info);
+	}
+
+	@Override
+	public ChunkRaw createChunk() {
+		if (val.isEmpty() || key.isEmpty())
+			return null;
+		try {
+			ByteArrayOutputStream ba = new ByteArrayOutputStream();
+			ba.write(key.getBytes(PngHelper.charsetLatin1));
+			ba.write(0); // separator
+			ba.write(compressed ? 1 : 0);
+			ba.write(0); // compression method (always 0)
+			ba.write(langTag.getBytes(PngHelper.charsetUTF8));
+			ba.write(0); // separator
+			ba.write(translatedTag.getBytes(PngHelper.charsetUTF8));
+			ba.write(0); // separator
+			byte[] textbytes = val.getBytes(PngHelper.charsetUTF8);
+			if (compressed) {
+				textbytes = ChunkHelper.compressBytes(textbytes, true);
+			}
+			ba.write(textbytes);
+			byte[] b = ba.toByteArray();
+			ChunkRaw chunk = createEmptyChunk(b.length, false);
+			chunk.data = b;
+			return chunk;
+		} catch (IOException e) {
+			throw new PngjException(e);
+		}
+	}
+
+	@Override
+	public void parseFromChunk(ChunkRaw c) {
+		int nullsFound = 0;
+		int[] nullsIdx = new int[3];
+		for (int i = 0; i < c.data.length; i++) {
+			if (c.data[i] != 0)
+				continue;
+			nullsIdx[nullsFound] = i;
+			nullsFound++;
+			if (nullsFound == 1)
+				i += 2;
+			if (nullsFound == 3)
+				break;
+		}
+		if (nullsFound != 3)
+			throw new PngjException("Bad formed PngChunkITXT chunk");
+		key = new String(c.data, 0, nullsIdx[0], PngHelper.charsetLatin1);
+		int i = nullsIdx[0] + 1;
+		compressed = c.data[i] == 0 ? false : true;
+		i++;
+		if (compressed && c.data[i] != 0)
+			throw new PngjException("Bad formed PngChunkITXT chunk - bad compression method ");
+		langTag = new String(c.data, i, nullsIdx[1] - i, PngHelper.charsetLatin1);
+		translatedTag = new String(c.data, nullsIdx[1] + 1, nullsIdx[2] - nullsIdx[1] - 1, PngHelper.charsetUTF8);
+		i = nullsIdx[2] + 1;
+		if (compressed) {
+			byte[] bytes = ChunkHelper.compressBytes(c.data, i, c.data.length - i, false);
+			val = new String(bytes, PngHelper.charsetUTF8);
+		} else {
+			val = new String(c.data, i, c.data.length - i, PngHelper.charsetUTF8);
+		}
+	}
+
+	@Override
+	public void cloneDataFromRead(PngChunk other) {
+		PngChunkITXT otherx = (PngChunkITXT) other;
+		key = otherx.key;
+		val = otherx.val;
+		compressed = otherx.compressed;
+		langTag = otherx.langTag;
+		translatedTag = otherx.translatedTag;
+	}
+
+	public boolean isCompressed() {
+		return compressed;
+	}
+
+	public void setCompressed(boolean compressed) {
+		this.compressed = compressed;
+	}
+
+	public String getLangtag() {
+		return langTag;
+	}
+
+	public void setLangtag(String langtag) {
+		this.langTag = langtag;
+	}
+
+	public String getTranslatedTag() {
+		return translatedTag;
+	}
+
+	public void setTranslatedTag(String translatedTag) {
+		this.translatedTag = translatedTag;
+	}
+}
diff --git a/src/jogl/classes/jogamp/opengl/util/pngj/chunks/PngChunkPHYS.java b/src/jogl/classes/jogamp/opengl/util/pngj/chunks/PngChunkPHYS.java
new file mode 100644
index 000000000..47e2c492c
--- /dev/null
+++ b/src/jogl/classes/jogamp/opengl/util/pngj/chunks/PngChunkPHYS.java
@@ -0,0 +1,108 @@
+package jogamp.opengl.util.pngj.chunks;
+
+import jogamp.opengl.util.pngj.ImageInfo;
+import jogamp.opengl.util.pngj.PngHelper;
+import jogamp.opengl.util.pngj.PngjException;
+
+public class PngChunkPHYS extends PngChunk {
+	// http://www.w3.org/TR/PNG/#11pHYs
+	private long pixelsxUnitX;
+	private long pixelsxUnitY;
+	private int units; // 0: unknown 1:metre
+
+	public PngChunkPHYS(ImageInfo info) {
+		super(ChunkHelper.pHYs, info);
+	}
+
+	@Override
+	public boolean mustGoBeforeIDAT() {
+		return true;
+	}
+
+	@Override
+	public ChunkRaw createChunk() {
+		ChunkRaw c = createEmptyChunk(9, true);
+		PngHelper.writeInt4tobytes((int) pixelsxUnitX, c.data, 0);
+		PngHelper.writeInt4tobytes((int) pixelsxUnitY, c.data, 4);
+		c.data[8] = (byte) units;
+		return c;
+	}
+
+	@Override
+	public void parseFromChunk(ChunkRaw chunk) {
+		if (chunk.len != 9)
+			throw new PngjException("bad chunk length " + chunk);
+		pixelsxUnitX = PngHelper.readInt4fromBytes(chunk.data, 0);
+		if (pixelsxUnitX < 0)
+			pixelsxUnitX += 0x100000000L;
+		pixelsxUnitY = PngHelper.readInt4fromBytes(chunk.data, 4);
+		if (pixelsxUnitY < 0)
+			pixelsxUnitY += 0x100000000L;
+		units = PngHelper.readInt1fromByte(chunk.data, 8);
+	}
+
+	@Override
+	public void cloneDataFromRead(PngChunk other) {
+		PngChunkPHYS otherx = (PngChunkPHYS) other;
+		this.pixelsxUnitX = otherx.pixelsxUnitX;
+		this.pixelsxUnitY = otherx.pixelsxUnitY;
+		this.units = otherx.units;
+	}
+
+	public long getPixelsxUnitX() {
+		return pixelsxUnitX;
+	}
+
+	public void setPixelsxUnitX(long pixelsxUnitX) {
+		this.pixelsxUnitX = pixelsxUnitX;
+	}
+
+	public long getPixelsxUnitY() {
+		return pixelsxUnitY;
+	}
+
+	public void setPixelsxUnitY(long pixelsxUnitY) {
+		this.pixelsxUnitY = pixelsxUnitY;
+	}
+
+	public int getUnits() {
+		return units;
+	}
+
+	public void setUnits(int units) {
+		this.units = units;
+	}
+
+	// special getters / setters
+
+	/**
+	 * returns -1 if the physicial unit is unknown, or X-Y are not equal
+	 */
+	public double getAsDpi() {
+		if (units != 1 || pixelsxUnitX != pixelsxUnitY)
+			return -1;
+		return ((double) pixelsxUnitX) * 0.0254;
+	}
+
+	/**
+	 * returns -1 if the physicial unit is unknown
+	 */
+	public double[] getAsDpi2() {
+		if (units != 1)
+			return new double[] { -1, -1 };
+		return new double[] { ((double) pixelsxUnitX) * 0.0254, ((double) pixelsxUnitY) * 0.0254 };
+	}
+
+	public void setAsDpi(double dpi) {
+		units = 1;
+		pixelsxUnitX = (long) (dpi / 0.0254 + 0.5);
+		pixelsxUnitY = pixelsxUnitX;
+	}
+
+	public void setAsDpi2(double dpix, double dpiy) {
+		units = 1;
+		pixelsxUnitX = (long) (dpix / 0.0254 + 0.5);
+		pixelsxUnitY = (long) (dpiy / 0.0254 + 0.5);
+	}
+
+}
diff --git a/src/jogl/classes/jogamp/opengl/util/pngj/chunks/PngChunkPLTE.java b/src/jogl/classes/jogamp/opengl/util/pngj/chunks/PngChunkPLTE.java
new file mode 100644
index 000000000..123080bb3
--- /dev/null
+++ b/src/jogl/classes/jogamp/opengl/util/pngj/chunks/PngChunkPLTE.java
@@ -0,0 +1,93 @@
+package jogamp.opengl.util.pngj.chunks;
+
+import jogamp.opengl.util.pngj.ImageInfo;
+import jogamp.opengl.util.pngj.PngjException;
+
+/*
+ * Palette chunk *this is critical*
+ */
+public class PngChunkPLTE extends PngChunk {
+	// http://www.w3.org/TR/PNG/#11PLTE
+	private int nentries = 0;
+	/**
+	 * RGB8 packed in one integer
+	 */
+	private int[] entries;
+
+	public PngChunkPLTE(ImageInfo info) {
+		super(ChunkHelper.PLTE, info);
+	}
+
+	@Override
+	public ChunkRaw createChunk() {
+		int len = 3 * nentries;
+		int[] rgb = new int[3];
+		ChunkRaw c = createEmptyChunk(len, true);
+		for (int n = 0, i = 0; n < nentries; n++) {
+			getEntryRgb(n, rgb);
+			c.data[i++] = (byte) rgb[0];
+			c.data[i++] = (byte) rgb[1];
+			c.data[i++] = (byte) rgb[2];
+		}
+		return c;
+	}
+
+	@Override
+	public void parseFromChunk(ChunkRaw chunk) {
+		setNentries(chunk.len / 3);
+		for (int n = 0, i = 0; n < nentries; n++) {
+			setEntry(n, (int) (chunk.data[i++] & 0xff), (int) (chunk.data[i++] & 0xff), (int) (chunk.data[i++] & 0xff));
+		}
+	}
+
+	@Override
+	public void cloneDataFromRead(PngChunk other) {
+		PngChunkPLTE otherx = (PngChunkPLTE) other;
+		this.setNentries(otherx.getNentries());
+		System.arraycopy(otherx.entries, 0, entries, 0, nentries);
+	}
+
+	public void setNentries(int n) {
+		nentries = n;
+		if (nentries < 1 || nentries > 256)
+			throw new PngjException("invalid pallette - nentries=" + nentries);
+		if (entries == null || entries.length != nentries) { // alloc
+			entries = new int[nentries];
+		}
+	}
+
+	public int getNentries() {
+		return nentries;
+	}
+
+	public void setEntry(int n, int r, int g, int b) {
+		entries[n] = ((r << 16) | (g << 8) | b);
+	}
+
+	public int getEntry(int n) {
+		return entries[n];
+	}
+
+	public void getEntryRgb(int n, int[] rgb) {
+		getEntryRgb(n, rgb, 0);
+	}
+
+	public void getEntryRgb(int n, int[] rgb, int offset) {
+		int v = entries[n];
+		rgb[offset + 0] = ((v & 0xff0000) >> 16);
+		rgb[offset + 1] = ((v & 0xff00) >> 8);
+		rgb[offset + 2] = (v & 0xff);
+	}
+
+	public int minBitDepth() {
+		if (nentries <= 2)
+			return 1;
+		else if (nentries <= 4)
+			return 2;
+		else if (nentries <= 16)
+			return 4;
+		else
+			return 8;
+	}
+
+}
diff --git a/src/jogl/classes/jogamp/opengl/util/pngj/chunks/PngChunkSBIT.java b/src/jogl/classes/jogamp/opengl/util/pngj/chunks/PngChunkSBIT.java
new file mode 100644
index 000000000..6850d260d
--- /dev/null
+++ b/src/jogl/classes/jogamp/opengl/util/pngj/chunks/PngChunkSBIT.java
@@ -0,0 +1,124 @@
+package jogamp.opengl.util.pngj.chunks;
+
+import jogamp.opengl.util.pngj.ImageInfo;
+import jogamp.opengl.util.pngj.PngHelper;
+import jogamp.opengl.util.pngj.PngjException;
+
+/*
+ */
+public class PngChunkSBIT extends PngChunk {
+	// http://www.w3.org/TR/PNG/#11sBIT
+	// this chunk structure depends on the image type
+
+	// significant bits
+	private int graysb, alphasb;
+	private int redsb, greensb, bluesb;
+
+	public PngChunkSBIT(ImageInfo info) {
+		super(ChunkHelper.sBIT, info);
+	}
+
+	@Override
+	public boolean mustGoBeforeIDAT() {
+		return true;
+	}
+
+	@Override
+	public boolean mustGoBeforePLTE() {
+		return true;
+	}
+
+	private int getLen() {
+		int len = imgInfo.greyscale ? 1 : 3;
+		if (imgInfo.alpha)
+			len += 1;
+		return len;
+	}
+
+	@Override
+	public void parseFromChunk(ChunkRaw c) {
+		if (c.len != getLen())
+			throw new PngjException("bad chunk length " + c);
+		if (imgInfo.greyscale) {
+			graysb = PngHelper.readInt1fromByte(c.data, 0);
+			if (imgInfo.alpha)
+				alphasb = PngHelper.readInt1fromByte(c.data, 1);
+		} else {
+			redsb = PngHelper.readInt1fromByte(c.data, 0);
+			greensb = PngHelper.readInt1fromByte(c.data, 1);
+			bluesb = PngHelper.readInt1fromByte(c.data, 2);
+			if (imgInfo.alpha)
+				alphasb = PngHelper.readInt1fromByte(c.data, 3);
+		}
+	}
+
+	@Override
+	public ChunkRaw createChunk() {
+		ChunkRaw c = null;
+		c = createEmptyChunk(getLen(), true);
+		if (imgInfo.greyscale) {
+			c.data[0] = (byte) graysb;
+			if (imgInfo.alpha)
+				c.data[1] = (byte) alphasb;
+		} else {
+			c.data[0] = (byte) redsb;
+			c.data[1] = (byte) greensb;
+			c.data[2] = (byte) bluesb;
+			if (imgInfo.alpha)
+				c.data[3] = (byte) alphasb;
+		}
+		return c;
+	}
+
+	@Override
+	public void cloneDataFromRead(PngChunk other) {
+		PngChunkSBIT otherx = (PngChunkSBIT) other;
+		graysb = otherx.graysb;
+		redsb = otherx.redsb;
+		greensb = otherx.greensb;
+		bluesb = otherx.bluesb;
+		alphasb = otherx.alphasb;
+	}
+
+	public void setGraysb(int gray) {
+		if (!imgInfo.greyscale)
+			throw new PngjException("only greyscale images support this");
+		graysb = gray;
+	}
+
+	public int getGraysb() {
+		if (!imgInfo.greyscale)
+			throw new PngjException("only greyscale images support this");
+		return graysb;
+	}
+
+	public void setAlphasb(int a) {
+		if (!imgInfo.alpha)
+			throw new PngjException("only images with alpha support this");
+		alphasb = a;
+	}
+
+	public int getAlphasb() {
+		if (!imgInfo.alpha)
+			throw new PngjException("only images with alpha support this");
+		return alphasb;
+	}
+
+	/**
+	 * Set rgb values
+	 * 
+	 */
+	public void setRGB(int r, int g, int b) {
+		if (imgInfo.greyscale || imgInfo.indexed)
+			throw new PngjException("only rgb or rgba images support this");
+		redsb = r;
+		greensb = g;
+		bluesb = b;
+	}
+
+	public int[] getRGB() {
+		if (imgInfo.greyscale || imgInfo.indexed)
+			throw new PngjException("only rgb or rgba images support this");
+		return new int[] { redsb, greensb, bluesb };
+	}
+}
diff --git a/src/jogl/classes/jogamp/opengl/util/pngj/chunks/PngChunkSPLT.java b/src/jogl/classes/jogamp/opengl/util/pngj/chunks/PngChunkSPLT.java
new file mode 100644
index 000000000..953adb7d9
--- /dev/null
+++ b/src/jogl/classes/jogamp/opengl/util/pngj/chunks/PngChunkSPLT.java
@@ -0,0 +1,139 @@
+package jogamp.opengl.util.pngj.chunks;
+
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+
+import jogamp.opengl.util.pngj.ImageInfo;
+import jogamp.opengl.util.pngj.PngHelper;
+import jogamp.opengl.util.pngj.PngjException;
+
+
+public class PngChunkSPLT extends PngChunk {
+	// http://www.w3.org/TR/PNG/#11sPLT
+
+	private String palName;
+	private int sampledepth; // 8/16
+	private int[] palette; // 5 elements per entry
+
+	public PngChunkSPLT(ImageInfo info) {
+		super(ChunkHelper.sPLT, info);
+	}
+
+	@Override
+	public boolean allowsMultiple() {
+		return true; // allows multiple, but pallete name should be different
+	}
+
+	@Override
+	public boolean mustGoBeforeIDAT() {
+		return true;
+	}
+
+	@Override
+	public ChunkRaw createChunk() {
+		try {
+			ByteArrayOutputStream ba = new ByteArrayOutputStream();
+			ba.write(palName.getBytes(PngHelper.charsetLatin1));
+			ba.write(0); // separator
+			ba.write((byte) sampledepth);
+			int nentries = getNentries();
+			for (int n = 0; n < nentries; n++) {
+				for (int i = 0; i < 4; i++) {
+					if (sampledepth == 8)
+						PngHelper.writeByte(ba, (byte) palette[n * 5 + i]);
+					else
+						PngHelper.writeInt2(ba, palette[n * 5 + i]);
+				}
+				PngHelper.writeInt2(ba, palette[n * 5 + 4]);
+			}
+			byte[] b = ba.toByteArray();
+			ChunkRaw chunk = createEmptyChunk(b.length, false);
+			chunk.data = b;
+			return chunk;
+		} catch (IOException e) {
+			throw new PngjException(e);
+		}
+	}
+
+	@Override
+	public void parseFromChunk(ChunkRaw c) {
+		int t = -1;
+		for (int i = 0; i < c.data.length; i++) { // look for first zero
+			if (c.data[i] == 0) {
+				t = i;
+				break;
+			}
+		}
+		if (t <= 0 || t > c.data.length - 2)
+			throw new PngjException("bad sPLT chunk: no separator found");
+		palName = new String(c.data, 0, t, PngHelper.charsetLatin1);
+		sampledepth = PngHelper.readInt1fromByte(c.data, t + 1);
+		t += 2;
+		int nentries = (c.data.length - t) / (sampledepth == 8 ? 6 : 10);
+		palette = new int[nentries * 5];
+		int r, g, b, a, f, ne;
+		ne = 0;
+		for (int i = 0; i < nentries; i++) {
+			if (sampledepth == 8) {
+				r = PngHelper.readInt1fromByte(c.data, t++);
+				g = PngHelper.readInt1fromByte(c.data, t++);
+				b = PngHelper.readInt1fromByte(c.data, t++);
+				a = PngHelper.readInt1fromByte(c.data, t++);
+			} else {
+				r = PngHelper.readInt2fromBytes(c.data, t);
+				t += 2;
+				g = PngHelper.readInt2fromBytes(c.data, t);
+				t += 2;
+				b = PngHelper.readInt2fromBytes(c.data, t);
+				t += 2;
+				a = PngHelper.readInt2fromBytes(c.data, t);
+				t += 2;
+			}
+			f = PngHelper.readInt2fromBytes(c.data, t);
+			t += 2;
+			palette[ne++] = r;
+			palette[ne++] = g;
+			palette[ne++] = b;
+			palette[ne++] = a;
+			palette[ne++] = f;
+		}
+	}
+
+	@Override
+	public void cloneDataFromRead(PngChunk other) {
+		PngChunkSPLT otherx = (PngChunkSPLT) other;
+		palName = otherx.palName;
+		sampledepth = otherx.sampledepth;
+		palette = new int[otherx.palette.length];
+		System.arraycopy(otherx.palette, 0, palette, 0, palette.length);
+	}
+
+	public int getNentries() {
+		return palette.length / 5;
+	}
+
+	public String getPalName() {
+		return palName;
+	}
+
+	public void setPalName(String palName) {
+		this.palName = palName;
+	}
+
+	public int getSampledepth() {
+		return sampledepth;
+	}
+
+	public void setSampledepth(int sampledepth) {
+		this.sampledepth = sampledepth;
+	}
+
+	public int[] getPalette() {
+		return palette;
+	}
+
+	public void setPalette(int[] palette) {
+		this.palette = palette;
+	}
+
+}
diff --git a/src/jogl/classes/jogamp/opengl/util/pngj/chunks/PngChunkSRGB.java b/src/jogl/classes/jogamp/opengl/util/pngj/chunks/PngChunkSRGB.java
new file mode 100644
index 000000000..774558785
--- /dev/null
+++ b/src/jogl/classes/jogamp/opengl/util/pngj/chunks/PngChunkSRGB.java
@@ -0,0 +1,61 @@
+package jogamp.opengl.util.pngj.chunks;
+
+import jogamp.opengl.util.pngj.ImageInfo;
+import jogamp.opengl.util.pngj.PngHelper;
+import jogamp.opengl.util.pngj.PngjException;
+
+/*
+ */
+public class PngChunkSRGB extends PngChunk {
+	// http://www.w3.org/TR/PNG/#11sRGB
+
+	public static final int RENDER_INTENT_Perceptual = 0;
+	public static final int RENDER_INTENT_Relative_colorimetric = 1;
+	public static final int RENDER_INTENT_Saturation = 2;
+	public static final int RENDER_INTENT_Absolute_colorimetric = 3;
+
+	private int intent;
+
+	public PngChunkSRGB(ImageInfo info) {
+		super(ChunkHelper.sRGB, info);
+	}
+
+	@Override
+	public boolean mustGoBeforeIDAT() {
+		return true;
+	}
+
+	@Override
+	public boolean mustGoBeforePLTE() {
+		return true;
+	}
+
+	@Override
+	public void parseFromChunk(ChunkRaw c) {
+		if (c.len != 1)
+			throw new PngjException("bad chunk length " + c);
+		intent = PngHelper.readInt1fromByte(c.data, 0);
+	}
+
+	@Override
+	public ChunkRaw createChunk() {
+		ChunkRaw c = null;
+		c = createEmptyChunk(1, true);
+		c.data[0] = (byte) intent;
+		return c;
+	}
+
+	@Override
+	public void cloneDataFromRead(PngChunk other) {
+		PngChunkSRGB otherx = (PngChunkSRGB) other;
+		intent = otherx.intent;
+	}
+
+	public int getIntent() {
+		return intent;
+	}
+
+	public void setIntent(int intent) {
+		this.intent = intent;
+	}
+}
diff --git a/src/jogl/classes/jogamp/opengl/util/pngj/chunks/PngChunkTEXT.java b/src/jogl/classes/jogamp/opengl/util/pngj/chunks/PngChunkTEXT.java
new file mode 100644
index 000000000..c535fe34a
--- /dev/null
+++ b/src/jogl/classes/jogamp/opengl/util/pngj/chunks/PngChunkTEXT.java
@@ -0,0 +1,34 @@
+package jogamp.opengl.util.pngj.chunks;
+
+import jogamp.opengl.util.pngj.ImageInfo;
+import jogamp.opengl.util.pngj.PngHelper;
+
+public class PngChunkTEXT extends PngChunkTextVar {
+	public PngChunkTEXT(ImageInfo info) {
+		super(ChunkHelper.tEXt, info);
+	}
+
+	@Override
+	public ChunkRaw createChunk() {
+		if (val.isEmpty() || key.isEmpty())
+			return null;
+		byte[] b = (key + "\0" + val).getBytes(PngHelper.charsetLatin1);
+		ChunkRaw chunk = createEmptyChunk(b.length, false);
+		chunk.data = b;
+		return chunk;
+	}
+
+	@Override
+	public void parseFromChunk(ChunkRaw c) {
+		String[] k = (new String(c.data, PngHelper.charsetLatin1)).split("\0");
+		key = k[0];
+		val = k[1];
+	}
+
+	@Override
+	public void cloneDataFromRead(PngChunk other) {
+		PngChunkTEXT otherx = (PngChunkTEXT) other;
+		key = otherx.key;
+		val = otherx.val;
+	}
+}
diff --git a/src/jogl/classes/jogamp/opengl/util/pngj/chunks/PngChunkTIME.java b/src/jogl/classes/jogamp/opengl/util/pngj/chunks/PngChunkTIME.java
new file mode 100644
index 000000000..37e617acb
--- /dev/null
+++ b/src/jogl/classes/jogamp/opengl/util/pngj/chunks/PngChunkTIME.java
@@ -0,0 +1,83 @@
+package jogamp.opengl.util.pngj.chunks;
+
+import java.util.Calendar;
+
+import jogamp.opengl.util.pngj.ImageInfo;
+import jogamp.opengl.util.pngj.PngHelper;
+import jogamp.opengl.util.pngj.PngjException;
+
+
+public class PngChunkTIME extends PngChunk {
+	// http://www.w3.org/TR/PNG/#11tIME
+	private int year, mon, day, hour, min, sec;
+
+	public PngChunkTIME(ImageInfo info) {
+		super(ChunkHelper.tIME, info);
+	}
+
+	@Override
+	public ChunkRaw createChunk() {
+		ChunkRaw c = createEmptyChunk(7, true);
+		PngHelper.writeInt2tobytes(year, c.data, 0);
+		c.data[2] = (byte) mon;
+		c.data[3] = (byte) day;
+		c.data[4] = (byte) hour;
+		c.data[5] = (byte) min;
+		c.data[6] = (byte) sec;
+		return c;
+	}
+
+	@Override
+	public void parseFromChunk(ChunkRaw chunk) {
+		if (chunk.len != 7)
+			throw new PngjException("bad chunk " + chunk);
+		year = PngHelper.readInt2fromBytes(chunk.data, 0);
+		mon = PngHelper.readInt1fromByte(chunk.data, 2);
+		day = PngHelper.readInt1fromByte(chunk.data, 3);
+		hour = PngHelper.readInt1fromByte(chunk.data, 4);
+		min = PngHelper.readInt1fromByte(chunk.data, 5);
+		sec = PngHelper.readInt1fromByte(chunk.data, 6);
+	}
+
+	@Override
+	public void cloneDataFromRead(PngChunk other) {
+		PngChunkTIME x = (PngChunkTIME) other;
+		year = x.year;
+		mon = x.mon;
+		day = x.day;
+		hour = x.hour;
+		min = x.min;
+		sec = x.sec;
+	}
+
+	public void setNow(int secsAgo) {
+		Calendar d = Calendar.getInstance();
+		d.setTimeInMillis(System.currentTimeMillis() - 1000 * (long) secsAgo);
+		year = d.get(Calendar.YEAR);
+		mon = d.get(Calendar.MONTH) + 1;
+		day = d.get(Calendar.DAY_OF_MONTH);
+		hour = d.get(Calendar.HOUR_OF_DAY);
+		min = d.get(Calendar.MINUTE);
+		sec = d.get(Calendar.SECOND);
+	}
+
+	public void setYMDHMS(int yearx, int monx, int dayx, int hourx, int minx, int secx) {
+		year = yearx;
+		mon = monx;
+		day = dayx;
+		hour = hourx;
+		min = minx;
+		sec = secx;
+	}
+	public int[] getYMDHMS() {
+		return new int[] { year, mon, day, hour, min, sec };
+	}
+
+	/** format YYYY/MM/DD HH:mm:SS */
+	public String getAsString() {
+		return String.format("%04/%02d/%02d %02d:%02d:%02d", year, mon, day, hour, min, sec);
+	}
+
+	
+
+}
diff --git a/src/jogl/classes/jogamp/opengl/util/pngj/chunks/PngChunkTRNS.java b/src/jogl/classes/jogamp/opengl/util/pngj/chunks/PngChunkTRNS.java
new file mode 100644
index 000000000..9365e5e8e
--- /dev/null
+++ b/src/jogl/classes/jogamp/opengl/util/pngj/chunks/PngChunkTRNS.java
@@ -0,0 +1,129 @@
+package jogamp.opengl.util.pngj.chunks;
+
+import jogamp.opengl.util.pngj.ImageInfo;
+import jogamp.opengl.util.pngj.PngHelper;
+import jogamp.opengl.util.pngj.PngjException;
+
+/*
+ */
+public class PngChunkTRNS extends PngChunk {
+	// http://www.w3.org/TR/PNG/#11tRNS
+	// this chunk structure depends on the image type
+	// only one of these is meaningful
+	private int gray;
+	private int red, green, blue;
+	private int[] paletteAlpha = new int[] {};
+
+	public PngChunkTRNS(ImageInfo info) {
+		super(ChunkHelper.tRNS, info);
+	}
+
+	@Override
+	public boolean mustGoBeforeIDAT() {
+		return true;
+	}
+
+	@Override
+	public boolean mustGoAfterPLTE() {
+		return true;
+	}
+
+	@Override
+	public ChunkRaw createChunk() {
+		ChunkRaw c = null;
+		if (imgInfo.greyscale) {
+			c = createEmptyChunk(2, true);
+			PngHelper.writeInt2tobytes(gray, c.data, 0);
+		} else if (imgInfo.indexed) {
+			c = createEmptyChunk(paletteAlpha.length, true);
+			for (int n = 0; n < c.len; n++) {
+				c.data[n] = (byte) paletteAlpha[n];
+			}
+		} else {
+			c = createEmptyChunk(6, true);
+			PngHelper.writeInt2tobytes(red, c.data, 0);
+			PngHelper.writeInt2tobytes(green, c.data, 0);
+			PngHelper.writeInt2tobytes(blue, c.data, 0);
+		}
+		return c;
+	}
+
+	@Override
+	public void parseFromChunk(ChunkRaw c) {
+		if (imgInfo.greyscale) {
+			gray = PngHelper.readInt2fromBytes(c.data, 0);
+		} else if (imgInfo.indexed) {
+			int nentries = c.data.length;
+			paletteAlpha = new int[nentries];
+			for (int n = 0; n < nentries; n++) {
+				paletteAlpha[n] = (int) (c.data[n] & 0xff);
+			}
+		} else {
+			red = PngHelper.readInt2fromBytes(c.data, 0);
+			green = PngHelper.readInt2fromBytes(c.data, 2);
+			blue = PngHelper.readInt2fromBytes(c.data, 4);
+		}
+	}
+
+	@Override
+	public void cloneDataFromRead(PngChunk other) {
+		PngChunkTRNS otherx = (PngChunkTRNS) other;
+		gray = otherx.gray;
+		red = otherx.red;
+		green = otherx.red;
+		blue = otherx.red;
+		if (otherx.paletteAlpha != null) {
+			paletteAlpha = new int[otherx.paletteAlpha.length];
+			System.arraycopy(otherx.paletteAlpha, 0, paletteAlpha, 0, paletteAlpha.length);
+		}
+	}
+
+	/**
+	 * Set rgb values
+	 * 
+	 */
+	public void setRGB(int r, int g, int b) {
+		if (imgInfo.greyscale || imgInfo.indexed)
+			throw new PngjException("only rgb or rgba images support this");
+		red = r;
+		green = g;
+		blue = b;
+	}
+
+	public int[] getRGB() {
+		if (imgInfo.greyscale || imgInfo.indexed)
+			throw new PngjException("only rgb or rgba images support this");
+		return new int[] { red, green, blue };
+	}
+
+	public void setGray(int g) {
+		if (!imgInfo.greyscale)
+			throw new PngjException("only grayscale images support this");
+		gray = g;
+	}
+
+	public int getGray() {
+		if (!imgInfo.greyscale)
+			throw new PngjException("only grayscale images support this");
+		return gray;
+	}
+
+	/**
+	 * WARNING: non deep copy
+	 */
+	public void setPalletteAlpha(int[] palAlpha) {
+		if (!imgInfo.indexed)
+			throw new PngjException("only indexed images support this");
+		paletteAlpha = palAlpha;
+	}
+
+	/**
+	 * WARNING: non deep copy
+	 */
+	public int[] getPalletteAlpha() {
+		if (!imgInfo.indexed)
+			throw new PngjException("only indexed images support this");
+		return paletteAlpha;
+	}
+
+}
diff --git a/src/jogl/classes/jogamp/opengl/util/pngj/chunks/PngChunkTextVar.java b/src/jogl/classes/jogamp/opengl/util/pngj/chunks/PngChunkTextVar.java
new file mode 100644
index 000000000..3d92a806f
--- /dev/null
+++ b/src/jogl/classes/jogamp/opengl/util/pngj/chunks/PngChunkTextVar.java
@@ -0,0 +1,61 @@
+package jogamp.opengl.util.pngj.chunks;
+
+import jogamp.opengl.util.pngj.ImageInfo;
+
+/**
+ * superclass for three textual chunks (TEXT, ITXT, ZTXT)
+ * 
+ * @author Hernan J Gonzalez
+ */
+public abstract class PngChunkTextVar extends PngChunk {
+	protected String key; // key/val: only for tEXt. lazy computed
+	protected String val;
+
+	// http://www.w3.org/TR/PNG/#11keywords
+	public final static String KEY_Title = "Title"; // Short (one line) title or caption for image
+	public final static String KEY_Author = "Author"; // Name of image's creator
+	public final static String KEY_Description = "Description"; // Description of image (possibly long)
+	public final static String KEY_Copyright = "Copyright"; // Copyright notice
+	public final static String KEY_Creation_Time = "Creation Time"; // Time of original image creation
+	public final static String KEY_Software = "Software"; // Software used to create the image
+	public final static String KEY_Disclaimer = "Disclaimer"; // Legal disclaimer
+	public final static String KEY_Warning = "Warning"; // Warning of nature of content
+	public final static String KEY_Source = "Source"; // Device used to create the image
+	public final static String KEY_Comment = "Comment"; // Miscellaneous comment
+
+	protected PngChunkTextVar(String id, ImageInfo info) {
+		super(id, info);
+	}
+
+	@Override
+	public boolean allowsMultiple() {
+		return true;
+	}
+
+	public static class PngTxtInfo {
+		public String title;
+		public String author;
+		public String description;
+		public String creation_time;// = (new Date()).toString();
+		public String software;
+		public String disclaimer;
+		public String warning;
+		public String source;
+		public String comment;
+
+	}
+
+	public String getKey() {
+		return key;
+	}
+
+	public String getVal() {
+		return val;
+	}
+
+	public void setKeyVal(String key, String val) {
+		this.key = key;
+		this.val = val;
+	}
+
+}
diff --git a/src/jogl/classes/jogamp/opengl/util/pngj/chunks/PngChunkUNKNOWN.java b/src/jogl/classes/jogamp/opengl/util/pngj/chunks/PngChunkUNKNOWN.java
new file mode 100644
index 000000000..15a35935a
--- /dev/null
+++ b/src/jogl/classes/jogamp/opengl/util/pngj/chunks/PngChunkUNKNOWN.java
@@ -0,0 +1,51 @@
+package jogamp.opengl.util.pngj.chunks;
+
+import jogamp.opengl.util.pngj.ImageInfo;
+
+public class PngChunkUNKNOWN extends PngChunk { // unkown, custom or not
+
+	private byte[] data;
+
+	public PngChunkUNKNOWN(String id, ImageInfo info) {
+		super(id, info);
+	}
+
+	@Override
+	public boolean allowsMultiple() {
+		return true;
+	}
+
+	private PngChunkUNKNOWN(PngChunkUNKNOWN c, ImageInfo info) {
+		super(c.id, info);
+		System.arraycopy(c.data, 0, data, 0, c.data.length);
+	}
+
+	@Override
+	public ChunkRaw createChunk() {
+		ChunkRaw p = createEmptyChunk(data.length, false);
+		p.data = this.data;
+		return p;
+	}
+
+	@Override
+	public void parseFromChunk(ChunkRaw c) {
+		data = c.data;
+	}
+
+	/* does not copy! */
+	public byte[] getData() {
+		return data;
+	}
+
+	/* does not copy! */
+	public void setData(byte[] data) {
+		this.data = data;
+	}
+
+	@Override
+	public void cloneDataFromRead(PngChunk other) {
+		// THIS SHOULD NOT BE CALLED IF ALREADY CLONED WITH COPY CONSTRUCTOR
+		PngChunkUNKNOWN c = (PngChunkUNKNOWN) other;
+		data = c.data; // not deep copy
+	}
+}
diff --git a/src/jogl/classes/jogamp/opengl/util/pngj/chunks/PngChunkZTXT.java b/src/jogl/classes/jogamp/opengl/util/pngj/chunks/PngChunkZTXT.java
new file mode 100644
index 000000000..fd6c08273
--- /dev/null
+++ b/src/jogl/classes/jogamp/opengl/util/pngj/chunks/PngChunkZTXT.java
@@ -0,0 +1,62 @@
+package jogamp.opengl.util.pngj.chunks;
+
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+
+import jogamp.opengl.util.pngj.ImageInfo;
+import jogamp.opengl.util.pngj.PngHelper;
+import jogamp.opengl.util.pngj.PngjException;
+
+
+public class PngChunkZTXT extends PngChunkTextVar {
+	// http://www.w3.org/TR/PNG/#11zTXt
+	public PngChunkZTXT(ImageInfo info) {
+		super(ChunkHelper.zTXt, info);
+	}
+
+	@Override
+	public ChunkRaw createChunk() {
+		if (val.isEmpty() || key.isEmpty())
+			return null;
+		try {
+			ByteArrayOutputStream ba = new ByteArrayOutputStream();
+			ba.write(key.getBytes(PngHelper.charsetLatin1));
+			ba.write(0); // separator
+			ba.write(0); // compression method: 0
+			byte[] textbytes = ChunkHelper.compressBytes(val.getBytes(PngHelper.charsetLatin1), true);
+			ba.write(textbytes);
+			byte[] b = ba.toByteArray();
+			ChunkRaw chunk = createEmptyChunk(b.length, false);
+			chunk.data = b;
+			return chunk;
+		} catch (IOException e) {
+			throw new PngjException(e);
+		}
+	}
+
+	@Override
+	public void parseFromChunk(ChunkRaw c) {
+		int nullsep = -1;
+		for (int i = 0; i < c.data.length; i++) { // look for first zero
+			if (c.data[i] != 0)
+				continue;
+			nullsep = i;
+			break;
+		}
+		if (nullsep < 0 || nullsep > c.data.length - 2)
+			throw new PngjException("bad zTXt chunk: no separator found");
+		key = new String(c.data, 0, nullsep, PngHelper.charsetLatin1);
+		int compmet = (int) c.data[nullsep + 1];
+		if (compmet != 0)
+			throw new PngjException("bad zTXt chunk: unknown compression method");
+		byte[] uncomp = ChunkHelper.compressBytes(c.data, nullsep + 2, c.data.length - nullsep - 2, false); // uncompress
+		val = new String(uncomp, PngHelper.charsetLatin1);
+	}
+
+	@Override
+	public void cloneDataFromRead(PngChunk other) {
+		PngChunkZTXT otherx = (PngChunkZTXT) other;
+		key = otherx.key;
+		val = otherx.val;
+	}
+}
diff --git a/src/jogl/classes/jogamp/opengl/util/pngj/chunks/PngMetadata.java b/src/jogl/classes/jogamp/opengl/util/pngj/chunks/PngMetadata.java
new file mode 100644
index 000000000..a82754588
--- /dev/null
+++ b/src/jogl/classes/jogamp/opengl/util/pngj/chunks/PngMetadata.java
@@ -0,0 +1,135 @@
+package jogamp.opengl.util.pngj.chunks;
+
+import jogamp.opengl.util.pngj.PngHelper;
+import jogamp.opengl.util.pngj.PngjException;
+
+/**
+ * We consider "image metadata" every info inside the image except for the most basic image info (IHDR chunk - ImageInfo
+ * class) and the pixels values.
+ * 
+ * This includes the palette (if present) and all the ancillary chunks
+ * 
+ * This class provides a wrapper over the collection of chunks of a image (read or to write) and provides some high
+ * level methods to access them
+ * 
+ */
+public class PngMetadata {
+	private final ChunkList chunkList;
+	private final boolean readonly;
+
+	public PngMetadata(ChunkList chunks, boolean readonly) {
+		this.chunkList = chunks;
+		this.readonly = readonly;
+	}
+
+	/**
+	 * Queues the chunk at the writer
+	 */
+	public boolean setChunk(PngChunk c, boolean overwriteIfPresent) {
+		if (readonly)
+			throw new PngjException("cannot set chunk : readonly metadata");
+		return chunkList.setChunk(c, overwriteIfPresent);
+	}
+
+	
+	/**
+	 * Returns only one chunk or null if nothing found - does not include queued chunks
+	 *
+	 * If more than one chunk (after filtering by inner id) is found, then an exception is thrown (failifMultiple=true)
+	 * or the last one is returned (failifMultiple=false)
+	 * 
+	 * @param id Chunk id
+	 * @param innerid if not null, the chunk is assumed to be PngChunkTextVar or PngChunkSPLT, and filtered by that 'internal id'
+	 * @param failIfMultiple throw exception if more that one
+	 * @return chunk (not cloned)
+	 */
+	public PngChunk getChunk1(String id, String innerid, boolean failIfMultiple) {
+		return chunkList.getChunk1(id, innerid, failIfMultiple);
+	}
+
+	/**
+	 *  Same as  getChunk1(id,  innerid=null, failIfMultiple=true);
+	 */
+	public PngChunk getChunk1(String id) {
+		return chunkList.getChunk1(id);
+	}
+
+	// ///// high level utility methods follow ////////////
+
+	// //////////// DPI
+
+	/** 
+	 * returns -1 if not found or dimension unknown 
+	 **/
+	public double[] getDpi() {
+		PngChunk c = getChunk1(ChunkHelper.pHYs, null, true);
+		if (c == null)
+			return new double[] { -1, -1 };
+		else
+			return ((PngChunkPHYS) c).getAsDpi2();
+	}
+
+	public void setDpi(double x) {
+		setDpi(x, x);
+	}
+
+	public void setDpi(double x, double y) {
+		PngChunkPHYS c = new PngChunkPHYS(chunkList.imageInfo);
+		c.setAsDpi2(x, y);
+		setChunk(c, true);
+	}
+
+	// //////////// TIME
+
+	public void setTimeNow(int secsAgo) {
+		PngChunkTIME c = new PngChunkTIME(chunkList.imageInfo);
+		c.setNow(secsAgo);
+		setChunk(c, true);
+	}
+
+	public void setTimeYMDHMS(int yearx, int monx, int dayx, int hourx, int minx, int secx) {
+		PngChunkTIME c = new PngChunkTIME(chunkList.imageInfo);
+		c.setYMDHMS(yearx, monx, dayx, hourx, minx, secx);
+		setChunk(c, true);
+	}
+
+	public String getTimeAsString() {
+		PngChunk c = getChunk1(ChunkHelper.tIME, null, true);
+		return c != null ? ((PngChunkTIME) c).getAsString() : "";
+	}
+
+	// //////////// TEXT
+
+	public void setText(String k, String val, boolean useLatin1, boolean compress) {
+		if (compress && !useLatin1)
+			throw new PngjException("cannot compress non latin text");
+		PngChunkTextVar c;
+		if (useLatin1) {
+			if (compress) {
+				c = new PngChunkZTXT(chunkList.imageInfo);
+			} else {
+				c = new PngChunkTEXT(chunkList.imageInfo);
+			}
+		} else {
+			c = new PngChunkITXT(chunkList.imageInfo);
+			((PngChunkITXT) c).setLangtag(k); // we use the same orig tag (this is not quite right)
+		}
+		c.setKeyVal(k, val);
+		setChunk(c, true);
+	}
+
+	public void setText(String k, String val) {
+		setText(k, val, false, val.length() > 400);
+	}
+
+	/** tries all text chunks - returns null if not found */
+	public String getTxtForKey(String k) {
+		PngChunk c = getChunk1(ChunkHelper.tEXt, k, true);
+		if (c == null)
+			c = getChunk1(ChunkHelper.zTXt, k, true);
+		if (c == null)
+			c = getChunk1(ChunkHelper.iTXt, k, true);
+		return c != null ? ((PngChunkTextVar) c).getVal() : null;
+	}
+
+}
diff --git a/src/jogl/classes/jogamp/opengl/util/pngj/chunks/package.html b/src/jogl/classes/jogamp/opengl/util/pngj/chunks/package.html
new file mode 100644
index 000000000..137406695
--- /dev/null
+++ b/src/jogl/classes/jogamp/opengl/util/pngj/chunks/package.html
@@ -0,0 +1,9 @@
+<html>
+<body bgcolor="white">
+<p>
+Contains the code related to chunk management for the PNGJ library.</p>
+<p>
+Only needed by client code if some special chunk handling is required.
+</p>
+</body>
+</html>
diff --git a/src/jogl/classes/jogamp/opengl/util/pngj/package.html b/src/jogl/classes/jogamp/opengl/util/pngj/package.html
new file mode 100644
index 000000000..209b39c59
--- /dev/null
+++ b/src/jogl/classes/jogamp/opengl/util/pngj/package.html
@@ -0,0 +1,11 @@
+<html>
+<body bgcolor="white">
+<p>
+Contains the main classes for the PNGJ library.<p>
+Client code should rarely need more than the public members of this package.
+</p>
+<p>
+See also the <code>nosandbox</code> package if available.
+</p>
+</body>
+</html>
-- 
cgit v1.2.3